Refactor shell layout; add shellspec + authority

- Introduce lib/shell helpers and keep etc/* scripts thin via compatibility shims

- Move Kerberos validation to scripts/validation/check_kerberos.sh and update callers

- Add deterministic shellspec unit tests under tests/shellspec/ and wire Maven to run them

- Add minimal Spring Boot authority module with startup + /health endpoint and Maven wiring

- Document the new layout in docs/layout.md

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
chrisfu 2026-03-13 09:19:56 -07:00
parent 502f039c0b
commit 909e92109e
535 changed files with 33687 additions and 862 deletions

49
authority/pom.xml Normal file
View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<groupId>org.prole</groupId>
<artifactId>authority</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>prole-authority</name>
<description>Minimal Authority service for Prole</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,11 @@
package org.prole.authority;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AuthorityApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorityApplication.class, args);
}
}

View File

@ -0,0 +1,13 @@
package org.prole.authority;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HealthController {
@GetMapping("/health")
public String health() {
return "ok";
}
}

View File

@ -0,0 +1,3 @@
server.port=8080
management.endpoints.web.exposure.include=health,info

View File

@ -0,0 +1,12 @@
package org.prole.authority;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class AuthorityApplicationTests {
@Test
void contextLoads() {
}
}

21
docs/layout.md Normal file
View File

@ -0,0 +1,21 @@
# Repository layout (first-pass)
This repo is being refactored toward a clearer separation between runtime scripts, reusable shell libraries, operator validation scripts, and tests.
## Top-level conventions
- `etc/`
- Runtime/bootstrap/config scripts only.
- May contain thin compatibility shims that forward to new locations.
- `lib/shell/`
- Reusable shell helper libraries intended to be sourced by CLI/runtime scripts.
- Prefer keeping logic here deterministic/testable; keep entrypoint scripts thin.
- `scripts/validation/`
- Operator-run validation/check scripts.
- These may invoke cluster tooling (`kubectl`, etc.), but should source shared helpers from `lib/shell/`.
- `tests/shellspec/`
- Shellspec tests for deterministic helper behavior only (no cluster access).
- Focus areas: config parsing, env fallback resolution, realm normalization, secret precedence, guardrails, command assembly.
- `authority/`
- Minimal Maven-backed Spring Boot service.
- Provides application startup and a basic health endpoint.

3
env.sh
View File

@ -3,8 +3,7 @@
# This file is generated by the installer. Source it in new shells, or execute as a wrapper:
# "$PROLE_HOME/env.sh" <command> [args…]
# shellcheck shell=bash
PROLE_HOME="${PROLE_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export PROLE_HOME
export PROLE_HOME="$HOME/dev/prole"
export PROLE_CONF="${PROLE_CONF:-$PROLE_HOME/conf}"
export PROLE_DATA="${PROLE_DATA:-$HOME/.prole/data}"
export PROLE_LOGS="${PROLE_LOGS:-/opt/prole/logs/${USER}}"

View File

@ -1,150 +1,6 @@
#!/usr/bin/env bash
# Shared helpers for common core init scripts (ArgoCD, OpenBao, OpenTofu, Garage).
# Responsibilities:
# - handle [-c|--config] early so prole_cfg.sh loads the right prole.cfg
# - parse standard actions/options
# - resolve and apply namespaces consistently
# Compatibility shim: shared shell libraries live in `lib/shell/`.
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# Actions supported by all common core scripts
COMMON_CORE_ACTIONS="start|stop|status|restart|initialize|update|reload"
# Parse -c/--config before loading prole_cfg.sh so PROLE_CONF is set in time.
# Sets:
# COMMON_CORE_CONFIG_PATH - path to prole.cfg (if provided)
# COMMON_CORE_ARGS - original arguments minus -c/--config
common_core_preparse_config() {
COMMON_CORE_CONFIG_PATH=""
COMMON_CORE_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--config)
shift
if [[ -z "${1:-}" ]]; then
echo "ERROR: -c/--config requires a file path" >&2
exit 2
fi
COMMON_CORE_CONFIG_PATH="$1"
;;
-c=*|--config=*)
COMMON_CORE_CONFIG_PATH="${1#*=}"
;;
*)
COMMON_CORE_ARGS+=("$1")
;;
esac
shift
done
if [[ -n "$COMMON_CORE_CONFIG_PATH" ]]; then
if [[ ! -f "$COMMON_CORE_CONFIG_PATH" ]]; then
echo "ERROR: config file not found: $COMMON_CORE_CONFIG_PATH" >&2
exit 2
fi
local cfg_dir
cfg_dir=$(cd "$(dirname "$COMMON_CORE_CONFIG_PATH")" && pwd)
PROLE_CONF="$cfg_dir"
export PROLE_CONF
fi
}
# Parse standard options and action.
# Sets (always):
# COMMON_CORE_ACTION
# COMMON_CORE_NAMESPACE
# COMMON_CORE_HELP (0/1)
# COMMON_CORE_PARSE_ERROR (empty or message)
common_core_parse_args() {
COMMON_CORE_ACTION=""
COMMON_CORE_NAMESPACE=""
COMMON_CORE_HELP=0
COMMON_CORE_PARSE_ERROR=""
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--mode)
shift
prole_set_mode "${1:-}"
;;
-m=*|--mode=*)
prole_set_mode "${1#*=}"
;;
-n|--namespace)
shift
if [[ -z "${1:-}" ]]; then
COMMON_CORE_PARSE_ERROR="--namespace requires a value"
break
fi
COMMON_CORE_NAMESPACE="$1"
;;
-n=*|--namespace=*)
COMMON_CORE_NAMESPACE="${1#*=}"
;;
start|stop|status|restart|initialize|update|reload)
COMMON_CORE_ACTION="$1"
;;
-h|--help)
COMMON_CORE_HELP=1
;;
--)
shift
break
;;
*)
if [[ -z "$COMMON_CORE_ACTION" && "$1" != -* ]]; then
COMMON_CORE_ACTION="$1"
else
COMMON_CORE_PARSE_ERROR="Unknown argument: $1"
break
fi
;;
esac
shift
done
if [[ -z "$COMMON_CORE_ACTION" && "$COMMON_CORE_HELP" -eq 0 && -z "$COMMON_CORE_PARSE_ERROR" ]]; then
COMMON_CORE_PARSE_ERROR="Action is required (${COMMON_CORE_ACTIONS//|/, })"
fi
}
common_core_usage() {
local prog="${1:-$0}"
echo "Usage: ${prog##*/} [${COMMON_CORE_ACTIONS//|/|}] [-n namespace] [--mode k3d|k3s|k8s] [-c conf/prole.cfg]" >&2
}
# Resolve namespace with precedence: CLI override -> SERVICE_NAMESPACE -> NAMESPACE -> provided default
common_core_resolve_namespace() {
local default_ns="${1:-default}"
local ns="${COMMON_CORE_NAMESPACE:-}"
[[ -z "$ns" && -n "${SERVICE_NAMESPACE:-}" ]] && ns="$SERVICE_NAMESPACE"
[[ -z "$ns" && -n "${NAMESPACE:-}" ]] && ns="$NAMESPACE"
[[ -z "$ns" ]] && ns="$default_ns"
# In k3s mode, common core services must not default into the `default` namespace.
# If the caller didn't supply an explicit namespace, normalize `default` -> SERVICE_NAMESPACE
# from prole.cfg (Explicit `-n default` is treated as a legacy/mistake and will be corrected).
local mode="${PROLE_MODE:-}"
if declare -F prole_normalize_mode >/dev/null 2>&1; then
mode="$(prole_normalize_mode "$mode")"
fi
if [[ "$mode" == "k3s" && "$ns" == "default" ]]; then
if [[ -n "${SERVICE_NAMESPACE:-}" && "${SERVICE_NAMESPACE}" != "default" ]]; then
ns="$SERVICE_NAMESPACE"
elif [[ -n "${PROLE_NAMESPACE:-}" && "${PROLE_NAMESPACE}" != "default" ]]; then
ns="$PROLE_NAMESPACE"
else
echo "ERROR: k3s mode requires a non-default service namespace. Set SERVICE_NAMESPACE in prole.cfg or pass -n." >&2
exit 2
fi
fi
echo "$ns"
}
# Apply a resolved namespace to both NAMESPACE and SERVICE_NAMESPACE for consistency
common_core_apply_namespace() {
local ns="$1"
if [[ -n "$ns" ]]; then
NAMESPACE="$ns"
SERVICE_NAMESPACE="$ns"
export NAMESPACE SERVICE_NAMESPACE
fi
}
# shellcheck disable=SC1091
source "$SCRIPT_DIR/../lib/shell/common_core_lib.sh"

View File

@ -1034,7 +1034,12 @@ run_test() {
ensure_krb5_conf_configmap
apply_krb5_conf_to_test_pod
if [[ -x "$SCRIPT_DIR/init_kerberos_test.sh" ]]; then
local kerberos_check_script="$SCRIPT_DIR/../scripts/validation/check_kerberos.sh"
if [[ ! -x "$kerberos_check_script" ]]; then
kerberos_check_script="$SCRIPT_DIR/init_kerberos_test.sh"
fi
if [[ -x "$kerberos_check_script" ]]; then
local test_samba_dns="${SAMBA_DNS_SERVER:-}"
if [[ "${KRB5_AD_PORT_FORWARD}" == "1" ]]; then
test_samba_dns="$effective_kdc"
@ -1050,7 +1055,7 @@ run_test() {
KRB5_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" KRB5_PASSWORD="$KRB5_PASSWORD" \
SAMBA_ADMIN_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" SAMBA_ADMIN_PASSWORD="$KRB5_PASSWORD" \
SAMBA_DNS_SERVER="$test_samba_dns" \
"$SCRIPT_DIR/init_kerberos_test.sh" test; then
"$kerberos_check_script" test; then
return 0
fi
@ -1085,7 +1090,7 @@ run_test() {
exit 1
done
else
err "init_kerberos_test.sh not found."
err "Kerberos validation script not found (expected $SCRIPT_DIR/../scripts/validation/check_kerberos.sh)."
exit 1
fi

View File

@ -2,570 +2,13 @@
set -euo pipefail
# init_kerberos_test.sh
# Purpose:
# - Run Kerberos authentication checks inside the in-cluster KDC pod
# Initialize SCRIPT_DIR
# Compatibility shim: Kerberos validation scripts live under `scripts/validation/`.
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ORIG_ARGS=("$@")
# Load env
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$PROLE_HOME/env.sh"
set -- "${ORIG_ARGS[@]}"
elif [[ -f "$HOME/.prole/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$HOME/.prole/env.sh"
set -- "${ORIG_ARGS[@]}"
check_script="$SCRIPT_DIR/../scripts/validation/check_kerberos.sh"
if [[ -x "$check_script" ]]; then
exec "$check_script" "$@"
fi
# Load config values from prole.cfg before applying defaults/CLI overrides.
# shellcheck disable=SC1090
source "$SCRIPT_DIR/prole_cfg.sh"
if [[ "${1:-}" == "--mode" || "${1:-}" == "-m" ]]; then
prole_set_mode "${2:-}"
shift 2
elif [[ "${1:-}" == --mode=* || "${1:-}" == -m=* ]]; then
prole_set_mode "${1#*=}"
shift
fi
ACTION=${1:-test}
NAMESPACE="${PROLE_NAMESPACE}"
SERVICE_NAMESPACE=${SERVICE_NAMESPACE:-${NAMESPACE}}
KRB5_REALM=${KRB5_REALM:-${REALM:-}}
KRB5_KDC=${KRB5_KDC:-}
KRB5_USER=${KRB5_USER:-${KRB5_USERNAME:-}}
KRB5_PASSWORD=${KRB5_PASSWORD:-}
PROLE_KDC_NAME=${PROLE_KDC_NAME:-auth}
PROLE_KDC_SERVICE=${PROLE_KDC_SERVICE:-auth}
SAMBA_ADMIN_USER=${SAMBA_ADMIN_USER:-}
SAMBA_ADMIN_PASSWORD=${SAMBA_ADMIN_PASSWORD:-}
SAMBA_DNS_SERVER=${SAMBA_DNS_SERVER:-}
KDC_POD_READY_TIMEOUT=${KDC_POD_READY_TIMEOUT:-60}
KDC_POD_READY_RETRIES=${KDC_POD_READY_RETRIES:-5}
PROLE_KDC_PRECHECK_TIMEOUT=${PROLE_KDC_PRECHECK_TIMEOUT:-5}
ensure_tools() {
for t in kubectl curl jq; do
command -v "$t" >/dev/null || { echo "Missing required tool: $t" >&2; exit 1; }
done
}
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
echo "Creating namespace '$NAMESPACE' ..."
kubectl create namespace "$NAMESPACE" >/dev/null 2>&1 || true
fi
}
write_krb5_conf_to_pod() {
local kdc_ns="$1"
local pod_name="$2"
if [[ -z "$kdc_ns" || -z "$pod_name" ]]; then
return 0
fi
if [[ -z "$KRB5_REALM" || -z "$KRB5_KDC" ]]; then
return 0
fi
local admin_val domain_val tmp
admin_val="${KRB5_ADMIN:-$KRB5_KDC}"
domain_val=$(printf '%s' "$KRB5_REALM" | tr '[:upper:]' '[:lower:]')
tmp=$(mktemp)
cat >"$tmp" <<EOF
[libdefaults]
default_realm = $KRB5_REALM
dns_lookup_realm = false
dns_lookup_kdc = false
udp_preference_limit = 1
[realms]
$KRB5_REALM = {
kdc = $KRB5_KDC
admin_server = $admin_val
}
[domain_realm]
.$domain_val = $KRB5_REALM
$domain_val = $KRB5_REALM
EOF
echo "Updating /etc/krb5.conf in test pod ${pod_name} for realm ${KRB5_REALM} ..."
if ! kubectl -n "$kdc_ns" exec -i "$pod_name" -- sh -c 'cat > /etc/krb5.conf' <"$tmp"; then
echo "WARN: Unable to write /etc/krb5.conf in ${pod_name}." >&2
fi
rm -f "$tmp"
}
get_ready_kdc_pod() {
local kdc_ns="$1"
kubectl -n "$kdc_ns" wait --for=condition=Ready pod -l "app=${PROLE_KDC_NAME}" --timeout="${KDC_POD_READY_TIMEOUT}s" >/dev/null 2>&1 || true
kubectl -n "$kdc_ns" get pod -l "app=${PROLE_KDC_NAME}" --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true
}
wait_for_kdc_pod() {
local kdc_ns="$1"
local attempt pod
attempt=1
while [[ $attempt -le $KDC_POD_READY_RETRIES ]]; do
pod=$(get_ready_kdc_pod "$kdc_ns")
if [[ -n "$pod" ]]; then
printf '%s' "$pod"
return 0
fi
echo "Waiting for KDC pod '${PROLE_KDC_NAME}' to be ready... (${attempt}/${KDC_POD_READY_RETRIES})" >&2
sleep 2
attempt=$((attempt + 1))
done
return 1
}
# Return 0 if pod is in CrashLoopBackOff (any container), else 1
is_pod_crashloop() {
local kdc_ns="$1"
local pod_name="$2"
if [[ -z "$pod_name" ]]; then
return 1
fi
local reason
reason=$(kubectl -n "$kdc_ns" get pod "$pod_name" -o jsonpath='{range .status.containerStatuses[*]}{.state.waiting.reason}{"\n"}{end}' 2>/dev/null | tr -d '\r' | tr -s '\n' ' ' || true)
if echo "${reason}" | grep -q "CrashLoopBackOff"; then
return 0
fi
return 1
}
debug_kdc_pod() {
local kdc_ns="$1"
local pod_name="$2"
if [[ -z "$pod_name" ]]; then
return 0
fi
echo "--- describe pod ${pod_name} ---" >&2
kubectl -n "$kdc_ns" describe pod "$pod_name" 1>&2 || true
echo "--- last 200 log lines from ${pod_name} ---" >&2
kubectl -n "$kdc_ns" logs "$pod_name" --tail=200 1>&2 || true
}
kdc_exec() {
local kdc_ns="$1"
local pod_name="$2"
shift 2
local output=""
if output=$(kubectl -n "$kdc_ns" exec "$pod_name" -- "$@" 2>&1); then
printf '%s' "$output"
return 0
fi
if echo "$output" | grep -qi "completed pod"; then
return 2
fi
if echo "$output" | grep -qi "not found"; then
return 2
fi
echo "$output" >&2
return 1
}
run_samba_join() {
local kdc_ns="$1"
local pod_name="$2"
local domain="$3"
local output=""
if output=$(kubectl -n "$kdc_ns" exec "$pod_name" -- \
env SAMBA_ADMIN_USER="$SAMBA_ADMIN_USER" SAMBA_ADMIN_PASSWORD="$SAMBA_ADMIN_PASSWORD" \
SAMBA_DNS_SERVER="$SAMBA_DNS_SERVER" SAMBA_REALM="${KRB5_REALM:-}" \
/bin/sh -c 'realm_flag=""; [ -n "${SAMBA_REALM}" ] && realm_flag="--realm=${SAMBA_REALM}"; samba-tool domain join "'"$domain"'" MEMBER -U"${SAMBA_ADMIN_USER}%${SAMBA_ADMIN_PASSWORD}" --server="${SAMBA_DNS_SERVER}" ${realm_flag}' 2>&1); then
printf '%s' "$output"
return 0
fi
if echo "$output" | grep -qi "completed pod"; then
return 2
fi
if echo "$output" | grep -qi "not found"; then
return 2
fi
if [[ -n "$output" ]]; then
echo "$output" >&2
else
echo "samba-tool produced no output." >&2
fi
return 1
}
run_kinit() {
local kdc_ns="$1"
local pod_name="$2"
local output=""
# Prefer /etc/krb5.conf if it exists, otherwise fallback to /opt/prole-kdc/krb5.conf
local krb5_config="/etc/krb5.conf"
if ! kubectl -n "$kdc_ns" exec "$pod_name" -- ls /etc/krb5.conf >/dev/null 2>&1; then
krb5_config="/opt/prole-kdc/krb5.conf"
fi
echo "Running kinit for ${KRB5_USER}@${KRB5_REALM} inside ${PROLE_KDC_NAME} (${pod_name}) using ${krb5_config} ..."
if output=$(printf '%s\n' "$KRB5_PASSWORD" | kubectl -n "$kdc_ns" exec -i "$pod_name" -- \
env KRB5_CONFIG="$krb5_config" kinit "${KRB5_USER}@${KRB5_REALM}" 2>&1); then
printf '%s' "$output"
return 0
fi
if echo "$output" | grep -qi "completed pod"; then
return 2
fi
if echo "$output" | grep -qi "not found"; then
return 2
fi
if [[ -n "$output" ]]; then
echo "$output" >&2
else
echo "kinit produced no output." >&2
fi
return 1
}
check_samba_connectivity() {
local kdc_ns="$1"
local pod_name="$2"
if [[ -z "$SAMBA_DNS_SERVER" ]]; then
return 0
fi
echo "Checking connectivity from ${PROLE_KDC_NAME} to ${SAMBA_DNS_SERVER} ..."
local ports="88 389 445 464"
local port
for port in $ports; do
local out=""
out=$(kubectl -n "$kdc_ns" exec "$pod_name" -- /bin/sh -c "timeout 3 bash -c '</dev/tcp/${SAMBA_DNS_SERVER}/${port}'" 2>&1) || true
if [[ -z "$out" ]]; then
echo "OK: ${SAMBA_DNS_SERVER}:${port}"
else
echo "WARN: ${SAMBA_DNS_SERVER}:${port} unreachable: ${out}"
fi
done
}
check_kdc_direct_connectivity() {
local kdc_ns="$1"
local pod_name="$2"
local kdc_ip="${AD_DC_IP:-${KDC_ANSIBLE_DETECTED:-${KDC_AUTO_DETECTED:-}}}"
if [[ -z "$kdc_ip" ]]; then
return 0
fi
echo "Checking connectivity from ${PROLE_KDC_NAME} to KDC IP ${kdc_ip} ..."
local ports="88 389 445 464"
local port
for port in $ports; do
local out=""
out=$(kubectl -n "$kdc_ns" exec "$pod_name" -- /bin/sh -c "timeout 3 bash -c '</dev/tcp/${kdc_ip}/${port}'" 2>&1) || true
if [[ -z "$out" ]]; then
echo "OK: ${kdc_ip}:${port}"
else
echo "WARN: ${kdc_ip}:${port} unreachable: ${out}"
fi
done
}
openbao_url() {
if [[ -n "${PROLE_OPENBAO_URL:-}" ]]; then
echo "$PROLE_OPENBAO_URL"
return 0
fi
if prole_is_in_cluster; then
echo "http://openbao.${SERVICE_NAMESPACE:-${NAMESPACE:-default}}.svc.cluster.local:8200"
return 0
fi
if curl -sS "http://127.0.0.1:8200/v1/sys/health" >/dev/null 2>&1; then
echo "http://127.0.0.1:8200"
return 0
elif curl -sS "http://127.0.0.1:8200/v1/sys/health" >/dev/null 2>&1; then
echo "http://127.0.0.1:8200"
return 0
else
echo ""
return 0
fi
}
openbao_token() {
if [[ -f "$PROLE_SERVICE/secrets/openbao-root-token" ]]; then
cat "$PROLE_SERVICE/secrets/openbao-root-token"
else
echo "${OPENBAO_ROOT_TOKEN:-}"
fi
}
fetch_openbao_secret() {
local path="$1"
local key="$2"
local token url
token=$(openbao_token)
url=$(openbao_url)
if [[ -z "$token" || -z "$url" ]]; then
echo ""
return 0
fi
curl -sS -H "X-Vault-Token: $token" "$url/v1/kv/data/$path" | jq -r ".data.data.\"$key\"" || echo ""
}
resolve_krb5_password() {
if [[ -z "${KRB5_PASSWORD}" || "${KRB5_PASSWORD}" == '${OPENBAO:'* || "${KRB5_PASSWORD}" == '${PROLE_SECRET:'* ]]; then
local path="prole/${NAMESPACE:-default}/kerberos"
local fetched
fetched=$(fetch_openbao_secret "$path" "password")
if [[ -n "$fetched" && "$fetched" != "null" ]]; then
KRB5_PASSWORD="$fetched"
fi
fi
}
resolve_samba_vars() {
if [[ -n "${SAMBA_ADMIN_USER}" && -n "${SAMBA_ADMIN_PASSWORD}" && -n "${SAMBA_DNS_SERVER}" ]]; then
return 0
fi
# 1. Fallback for DNS server from prole.cfg env (AD_DC_HOST)
if [[ -z "$SAMBA_DNS_SERVER" && -n "${AD_DC_HOST:-}" ]]; then
SAMBA_DNS_SERVER="$AD_DC_HOST"
echo "Using SAMBA_DNS_SERVER=${SAMBA_DNS_SERVER} from AD_DC_HOST"
fi
# 2. Try to resolve from Ansible inventory if possible
local root
if [[ -n "${PROLE_HOME:-}" && -d "${PROLE_HOME}" ]]; then
root="${PROLE_HOME}"
else
root="${SCRIPT_DIR}/.."
fi
local vars_file="${root}/infrastructure/inventory/group_vars/ad_dc/vars.yml"
if [[ -f "$vars_file" ]]; then
if [[ -z "$SAMBA_DNS_SERVER" ]]; then
SAMBA_DNS_SERVER=$(awk -F: '/^[[:space:]]*samba_dns_server[[:space:]]*:/ {sub(/^[^:]+:[[:space:]]*/, "", $0); gsub(/"/, "", $0); print $0; exit}' "$vars_file")
fi
if [[ -z "$SAMBA_ADMIN_USER" ]]; then
SAMBA_ADMIN_USER=$(awk -F: '/^[[:space:]]*samba_dns_admin_user[[:space:]]*:/ {sub(/^[^:]+:[[:space:]]*/, "", $0); gsub(/"/, "", $0); print $0; exit}' "$vars_file")
fi
fi
# 3. Fallback for Admin User
if [[ -z "$SAMBA_ADMIN_USER" ]]; then
if [[ -n "${AD_DC_USER:-}" ]]; then
SAMBA_ADMIN_USER="$AD_DC_USER"
echo "Using SAMBA_ADMIN_USER=${SAMBA_ADMIN_USER} from AD_DC_USER"
else
SAMBA_ADMIN_USER="administrator"
echo "Defaulting SAMBA_ADMIN_USER to administrator"
fi
fi
# 4. Try to resolve password from Ansible Vault
if [[ -z "$SAMBA_ADMIN_PASSWORD" ]]; then
local vault_file="${root}/infrastructure/inventory/group_vars/ad_dc/vault.yml"
local vault_pass_file=""
if [[ -n "${ANSIBLE_VAULT_PASSWORD_FILE:-}" && -f "${ANSIBLE_VAULT_PASSWORD_FILE}" ]]; then
vault_pass_file="${ANSIBLE_VAULT_PASSWORD_FILE}"
elif [[ -n "${PROLE_HOME:-}" && -f "${PROLE_HOME}/.vault_pass" ]]; then
vault_pass_file="${PROLE_HOME}/.vault_pass"
elif [[ -f "${root}/.vault_pass" ]]; then
vault_pass_file="${root}/.vault_pass"
fi
if [[ -f "$vault_file" && -n "$vault_pass_file" && -x "$(command -v ansible-vault)" ]]; then
local vault_out
vault_out=$(ansible-vault view "$vault_file" --vault-password-file "$vault_pass_file" 2>/dev/null || true)
if [[ -n "$vault_out" ]]; then
SAMBA_ADMIN_PASSWORD=$(printf '%s\n' "$vault_out" | awk -F: '/^[[:space:]]*vault_samba_dns_admin_pass[[:space:]]*:/ {sub(/^[^:]+:[[:space:]]*/, "", $0); gsub(/"/, "", $0); print $0; exit}')
fi
fi
fi
# 5. Fallback for Password from OpenBao
if [[ -z "$SAMBA_ADMIN_PASSWORD" ]]; then
local path="prole/${NAMESPACE:-default}/ad_dc"
local fetched
fetched=$(fetch_openbao_secret "$path" "password")
if [[ -n "$fetched" && "$fetched" != "null" ]]; then
SAMBA_ADMIN_PASSWORD="$fetched"
echo "Fetched SAMBA_ADMIN_PASSWORD from OpenBao (${path})"
else
# Try kerberos path as fallback
path="prole/${NAMESPACE:-default}/kerberos"
fetched=$(fetch_openbao_secret "$path" "samba_password")
if [[ -n "$fetched" && "$fetched" != "null" ]]; then
SAMBA_ADMIN_PASSWORD="$fetched"
echo "Fetched SAMBA_ADMIN_PASSWORD from OpenBao (${path}#samba_password)"
fi
fi
fi
}
join_samba_realm_if_needed() {
local kdc_ns="$1"
local pod_name="$2"
local domain=""
resolve_samba_vars
if [[ -z "$SAMBA_ADMIN_USER" || -z "$SAMBA_ADMIN_PASSWORD" || -z "$SAMBA_DNS_SERVER" ]]; then
echo "WARN: Missing Samba join info; skipping samba-tool realm join."
return 0
fi
check_samba_connectivity "$kdc_ns" "$pod_name"
if [[ -n "$KRB5_REALM" ]]; then
domain=$(printf '%s' "$KRB5_REALM" | tr '[:upper:]' '[:lower:]')
fi
if [[ -z "$domain" ]]; then
domain="$SAMBA_DNS_SERVER"
fi
if kubectl -n "$kdc_ns" exec "$pod_name" -- test -f /var/lib/samba/private/secrets.tdb >/dev/null 2>&1; then
echo "Samba already joined; skipping join."
return 0
fi
echo "Joining Samba realm '${domain}' (server ${SAMBA_DNS_SERVER}) ..."
local attempt=1
while true; do
if run_samba_join "$kdc_ns" "$pod_name" "$domain"; then
return 0
fi
if [[ $? -eq 2 ]]; then
if [[ $attempt -ge $KDC_POD_READY_RETRIES ]]; then
echo "WARN: KDC pod completed before join could run; skipping samba-tool join." >&2
return 0
fi
pod_name=$(wait_for_kdc_pod "$kdc_ns" || true)
if [[ -z "$pod_name" ]]; then
echo "WARN: KDC pod unavailable for samba-tool join." >&2
return 0
fi
attempt=$((attempt + 1))
continue
fi
echo "WARN: samba-tool domain join failed; continuing to Kerberos test." >&2
return 0
done
}
run_test() {
ensure_tools
ensure_namespace
resolve_krb5_password
# Support both names for the toggle from prole.cfg
local enabled="${KERBEROS_ENABLED:-${ENABLED:-false}}"
if [[ "$enabled" == "false" || "$enabled" == "0" || "$enabled" == "False" ]]; then
echo "Kerberos is disabled (KERBEROS_ENABLED=$enabled). Skipping test."
return 0
fi
if [[ -z "$KRB5_REALM" || -z "$KRB5_USER" || -z "$KRB5_PASSWORD" || -z "$KRB5_KDC" ]]; then
echo "ERROR: Missing Kerberos configuration. Ensure KRB5_REALM, KRB5_KDC, KRB5_USER, KRB5_PASSWORD are set." >&2
exit 1
fi
# Quick pre-check: if an existing 'auth' deployment is present but not Available, remove it
# to avoid starting a rollout that is likely to time out on a stale/broken replica set.
local kdc_ns
kdc_ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}"
if kubectl -n "$kdc_ns" get deploy "$PROLE_KDC_NAME" >/dev/null 2>&1; then
# Try a fast availability wait; if it fails, treat as unhealthy and clean up before proceeding.
if ! kubectl -n "$kdc_ns" wait --for=condition=Available deploy/"$PROLE_KDC_NAME" --timeout="${PROLE_KDC_PRECHECK_TIMEOUT}s" >/dev/null 2>&1; then
echo "Detected unhealthy '$PROLE_KDC_NAME' deployment in namespace '$kdc_ns'; removing before Kerberos test rollout to avoid timeout..." >&2
if [[ -x "$SCRIPT_DIR/init_kdc.sh" ]]; then
SERVICE_NAMESPACE="$SERVICE_NAMESPACE" "$SCRIPT_DIR/init_kdc.sh" cleanup || true
else
kubectl -n "$kdc_ns" delete deploy "$PROLE_KDC_NAME" --ignore-not-found || true
kubectl -n "$kdc_ns" delete svc "$PROLE_KDC_SERVICE" --ignore-not-found || true
kubectl -n "$kdc_ns" delete configmap prole-kdc-config --ignore-not-found || true
fi
fi
fi
if [[ -x "$SCRIPT_DIR/init_kdc.sh" ]]; then
SERVICE_NAMESPACE="$SERVICE_NAMESPACE" KRB5_REALM="$KRB5_REALM" KRB5_KDC="$KRB5_KDC" \
KRB5_ADMIN="$KRB5_KDC" KRB5_USER="$KRB5_USER" KRB5_PASSWORD="$KRB5_PASSWORD" \
"$SCRIPT_DIR/init_kdc.sh" update || true
fi
local pod_name
pod_name=$(wait_for_kdc_pod "$kdc_ns" || true)
if [[ -z "$pod_name" ]]; then
echo "ERROR: KDC pod '${PROLE_KDC_NAME}' not found in namespace '$kdc_ns'. Run init_kdc.sh update first." >&2
exit 1
fi
# If the pod is crashlooping, dump logs and fail fast
if is_pod_crashloop "$kdc_ns" "$pod_name"; then
echo "ERROR: KDC pod '${pod_name}' is in CrashLoopBackOff. Dumping diagnostics..." >&2
debug_kdc_pod "$kdc_ns" "$pod_name"
exit 1
fi
write_krb5_conf_to_pod "$kdc_ns" "$pod_name"
check_kdc_direct_connectivity "$kdc_ns" "$pod_name"
join_samba_realm_if_needed "$kdc_ns" "$pod_name"
echo "Running kinit for ${KRB5_USER}@${KRB5_REALM} inside ${PROLE_KDC_NAME} (${pod_name}) ..."
local attempt=1
while true; do
if run_kinit "$kdc_ns" "$pod_name"; then
break
fi
if [[ $? -eq 2 ]] || kubectl -n "$kdc_ns" get pod "$pod_name" -o jsonpath='{.status.phase}' 2>/dev/null | grep -qi "Succeeded"; then
if [[ $attempt -ge $KDC_POD_READY_RETRIES ]]; then
echo "ERROR: KDC pod completed before kinit could run." >&2
exit 1
fi
pod_name=$(wait_for_kdc_pod "$kdc_ns" || true)
if [[ -z "$pod_name" ]]; then
echo "ERROR: KDC pod unavailable for kinit." >&2
exit 1
fi
attempt=$((attempt + 1))
continue
fi
# If the pod is crashlooping, show logs to aid troubleshooting
if is_pod_crashloop "$kdc_ns" "$pod_name"; then
echo "ERROR: KDC pod '${pod_name}' entered CrashLoopBackOff during kinit. Dumping diagnostics..." >&2
debug_kdc_pod "$kdc_ns" "$pod_name"
fi
echo "ERROR: kinit failed for ${KRB5_USER}@${KRB5_REALM}." >&2
exit 1
done
echo "Kerberos ticket cache:"
# Determine krb5 config path in the pod similar to run_kinit()
local krb5_config_in_pod="/etc/krb5.conf"
if ! kubectl -n "$kdc_ns" exec "$pod_name" -- ls /etc/krb5.conf >/dev/null 2>&1; then
krb5_config_in_pod="/opt/prole-kdc/krb5.conf"
fi
kubectl -n "$kdc_ns" exec "$pod_name" -- env KRB5_CONFIG="$krb5_config_in_pod" klist || true
}
case "$ACTION" in
test)
run_test
;;
cleanup)
echo "No test pods to cleanup (KDC-based test)."
;;
*)
echo "Usage: $0 {test|cleanup}" >&2
exit 2
;;
esac
echo "ERROR: validation script not found or not executable: $check_script" >&2
exit 1

View File

@ -4,6 +4,11 @@ k3s_state: present
k3s_version: ""
k3s_guard_token_drift: true
# k3s service startup can be slow on first boot (image pulls, containerd unpacking, etc.).
# The service task is best-effort and then Ansible polls `systemctl is-active` using these knobs.
k3s_service_start_retries: 60
k3s_service_start_delay: 5
# Destructive repair/refresh workflow (server only)
# A refresh wipes the k3s installation/state to a blank slate but preserves the
# join token and CA material so existing agents can rejoin without changing the token.

View File

@ -1,13 +1,20 @@
---
- name: Check if controller has conf/prole.cfg (optional)
ansible.builtin.stat:
path: "{{ role_path }}/../../../conf/prole.cfg"
register: _k3s_prole_cfg_stat
delegate_to: localhost
- name: Load CNPG version from prole.cfg (pinned)
ansible.builtin.set_fact:
cnpg_version_raw: >-
{{ lookup(
'ansible.builtin.ini',
'CNPG_VERSION section=Global file=' + playbook_dir + '/../../conf/prole.cfg',
default=(k3s_cnpg_version_default | default('1.28.1'))
) }}
'CNPG_VERSION section=Global file=' ~ (role_path ~ '/../../../conf/prole.cfg'),
default=(k3s_cnpg_version_default | default('1.28.1')),
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else (k3s_cnpg_version_default | default('1.28.1')) }}
- name: Normalize CNPG version
ansible.builtin.set_fact:
@ -23,7 +30,13 @@
- name: Load k3s server URL from prole.cfg
ansible.builtin.set_fact:
cnpg_prole_k3s_server_cfg: "{{ lookup('ansible.builtin.ini', 'PROLE_K3S_SERVER section=Global file=' + playbook_dir + '/../../conf/prole.cfg', default='') }}"
cnpg_prole_k3s_server_cfg: >-
{{ lookup(
'ansible.builtin.ini',
'PROLE_K3S_SERVER section=Global file=' ~ (role_path ~ '/../../../conf/prole.cfg'),
default='',
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else '' }}
- name: Resolve k3s API URL for kubectl
ansible.builtin.set_fact:

View File

@ -12,9 +12,21 @@
ansible.builtin.set_fact:
k3s_service_name: "{{ 'k3s' if k3s_role == 'server' else 'k3s-agent' }}"
- name: Check if controller has conf/prole.cfg (optional)
ansible.builtin.stat:
path: "{{ role_path }}/../../../conf/prole.cfg"
register: _k3s_prole_cfg_stat
delegate_to: localhost
- name: Load service namespace from prole.cfg (controller)
ansible.builtin.set_fact:
k3s_prole_service_namespace_cfg: "{{ lookup('ansible.builtin.ini', 'SERVICE_NAMESPACE section=Global file=' + playbook_dir + '/../../conf/prole.cfg', default='') }}"
k3s_prole_service_namespace_cfg: >-
{{ lookup(
'ansible.builtin.ini',
'SERVICE_NAMESPACE section=Global file=' ~ (role_path ~ '/../../../conf/prole.cfg'),
default='',
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else '' }}
changed_when: false
- name: Resolve Kong namespaces from prole.cfg (avoid implicit default namespace)

View File

@ -510,10 +510,23 @@
register: k3s_bin
- name: Ensure k3s service state
ansible.builtin.service:
name: "{{ k3s_service_name }}"
state: "{{ 'started' if k3s_state == 'present' else 'stopped' }}"
enabled: "{{ k3s_state == 'present' }}"
block:
- name: Start/stop k3s service (best-effort)
ansible.builtin.service:
name: "{{ k3s_service_name }}"
state: "{{ 'started' if k3s_state == 'present' else 'stopped' }}"
enabled: "{{ k3s_state == 'present' }}"
register: _k3s_service_manage
failed_when: false
- name: Wait for k3s service to become active
ansible.builtin.command: "systemctl is-active --quiet {{ k3s_service_name }}"
register: _k3s_service_active
until: _k3s_service_active.rc == 0
retries: "{{ k3s_service_start_retries | default(60) }}"
delay: "{{ k3s_service_start_delay | default(5) }}"
changed_when: false
when: k3s_state == 'present'
when: k3s_service.stat.exists or k3s_bin.stat.exists
- name: Flush k3s restart handler before Kubernetes operations

View File

@ -1,132 +1,6 @@
#!/usr/bin/env bash
# Shared helpers for common core init scripts (ArgoCD, OpenBao, OpenTofu, Garage).
# Responsibilities:
# - handle [-c|--config] early so prole_cfg.sh loads the right prole.cfg
# - parse standard actions/options
# - resolve and apply namespaces consistently
# Compatibility shim: shared shell libraries live in `lib/shell/`.
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# Actions supported by all common core scripts
COMMON_CORE_ACTIONS="start|stop|status|restart|initialize|update|reload"
# Parse -c/--config before loading prole_cfg.sh so PROLE_CONF is set in time.
# Sets:
# COMMON_CORE_CONFIG_PATH - path to prole.cfg (if provided)
# COMMON_CORE_ARGS - original arguments minus -c/--config
common_core_preparse_config() {
COMMON_CORE_CONFIG_PATH=""
COMMON_CORE_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--config)
shift
if [[ -z "${1:-}" ]]; then
echo "ERROR: -c/--config requires a file path" >&2
exit 2
fi
COMMON_CORE_CONFIG_PATH="$1"
;;
-c=*|--config=*)
COMMON_CORE_CONFIG_PATH="${1#*=}"
;;
*)
COMMON_CORE_ARGS+=("$1")
;;
esac
shift
done
if [[ -n "$COMMON_CORE_CONFIG_PATH" ]]; then
if [[ ! -f "$COMMON_CORE_CONFIG_PATH" ]]; then
echo "ERROR: config file not found: $COMMON_CORE_CONFIG_PATH" >&2
exit 2
fi
local cfg_dir
cfg_dir=$(cd "$(dirname "$COMMON_CORE_CONFIG_PATH")" && pwd)
PROLE_CONF="$cfg_dir"
export PROLE_CONF
fi
}
# Parse standard options and action.
# Sets (always):
# COMMON_CORE_ACTION
# COMMON_CORE_NAMESPACE
# COMMON_CORE_HELP (0/1)
# COMMON_CORE_PARSE_ERROR (empty or message)
common_core_parse_args() {
COMMON_CORE_ACTION=""
COMMON_CORE_NAMESPACE=""
COMMON_CORE_HELP=0
COMMON_CORE_PARSE_ERROR=""
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--mode)
shift
prole_set_mode "${1:-}"
;;
-m=*|--mode=*)
prole_set_mode "${1#*=}"
;;
-n|--namespace)
shift
if [[ -z "${1:-}" ]]; then
COMMON_CORE_PARSE_ERROR="--namespace requires a value"
break
fi
COMMON_CORE_NAMESPACE="$1"
;;
-n=*|--namespace=*)
COMMON_CORE_NAMESPACE="${1#*=}"
;;
start|stop|status|restart|initialize|update|reload)
COMMON_CORE_ACTION="$1"
;;
-h|--help)
COMMON_CORE_HELP=1
;;
--)
shift
break
;;
*)
if [[ -z "$COMMON_CORE_ACTION" && "$1" != -* ]]; then
COMMON_CORE_ACTION="$1"
else
COMMON_CORE_PARSE_ERROR="Unknown argument: $1"
break
fi
;;
esac
shift
done
if [[ -z "$COMMON_CORE_ACTION" && "$COMMON_CORE_HELP" -eq 0 && -z "$COMMON_CORE_PARSE_ERROR" ]]; then
COMMON_CORE_PARSE_ERROR="Action is required (${COMMON_CORE_ACTIONS//|/, })"
fi
}
common_core_usage() {
local prog="${1:-$0}"
echo "Usage: ${prog##*/} [${COMMON_CORE_ACTIONS//|/|}] [-n namespace] [--mode k3d|k3s|k8s] [-c conf/prole.cfg]" >&2
}
# Resolve namespace with precedence: CLI override -> SERVICE_NAMESPACE -> NAMESPACE -> provided default
common_core_resolve_namespace() {
local default_ns="${1:-default}"
local ns="${COMMON_CORE_NAMESPACE:-}"
[[ -z "$ns" && -n "${SERVICE_NAMESPACE:-}" ]] && ns="$SERVICE_NAMESPACE"
[[ -z "$ns" && -n "${NAMESPACE:-}" ]] && ns="$NAMESPACE"
[[ -z "$ns" ]] && ns="$default_ns"
echo "$ns"
}
# Apply a resolved namespace to both NAMESPACE and SERVICE_NAMESPACE for consistency
common_core_apply_namespace() {
local ns="$1"
if [[ -n "$ns" ]]; then
NAMESPACE="$ns"
SERVICE_NAMESPACE="$ns"
export NAMESPACE SERVICE_NAMESPACE
fi
}
# shellcheck disable=SC1091
source "$SCRIPT_DIR/../lib/shell/common_core_lib.sh"

View File

@ -1011,7 +1011,12 @@ run_test() {
ensure_krb5_conf_configmap
apply_krb5_conf_to_test_pod
if [[ -x "$SCRIPT_DIR/init_kerberos_test.sh" ]]; then
local kerberos_check_script="$SCRIPT_DIR/../scripts/validation/check_kerberos.sh"
if [[ ! -x "$kerberos_check_script" ]]; then
kerberos_check_script="$SCRIPT_DIR/init_kerberos_test.sh"
fi
if [[ -x "$kerberos_check_script" ]]; then
local test_samba_dns="${SAMBA_DNS_SERVER:-}"
if [[ "${KRB5_AD_PORT_FORWARD}" == "1" ]]; then
test_samba_dns="$effective_kdc"
@ -1027,7 +1032,7 @@ run_test() {
KRB5_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" KRB5_PASSWORD="$KRB5_PASSWORD" \
SAMBA_ADMIN_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" SAMBA_ADMIN_PASSWORD="$KRB5_PASSWORD" \
SAMBA_DNS_SERVER="$test_samba_dns" \
"$SCRIPT_DIR/init_kerberos_test.sh" test; then
"$kerberos_check_script" test; then
return 0
fi
@ -1062,7 +1067,7 @@ run_test() {
exit 1
done
else
err "init_kerberos_test.sh not found."
err "Kerberos validation script not found (expected $SCRIPT_DIR/../scripts/validation/check_kerberos.sh)."
exit 1
fi

44
pom.xml
View File

@ -19,6 +19,8 @@
<properties>
<java.version>24</java.version>
<spring-modulith.version>1.0.0</spring-modulith.version>
<skip.shellspec>false</skip.shellspec>
<skip.authority>false</skip.authority>
</properties>
<dependencyManagement>
<dependencies>
@ -196,6 +198,48 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>shellspec</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<skip>${skip.shellspec}</skip>
<executable>bash</executable>
<arguments>
<argument>${project.basedir}/tests/shellspec/run.sh</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>authority</id>
<phase>verify</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<skip>${skip.authority}</skip>
<executable>mvn</executable>
<arguments>
<argument>-f</argument>
<argument>${project.basedir}/authority/pom.xml</argument>
<argument>clean</argument>
<argument>test</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -1 +1 @@
107
108

View File

@ -0,0 +1,582 @@
#!/usr/bin/env bash
set -euo pipefail
# check_kerberos.sh
# Purpose:
# - Run Kerberos authentication checks inside the in-cluster KDC pod
# Initialize SCRIPT_DIR
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ETC_DIR=$(cd "$SCRIPT_DIR/../../etc" && pwd)
LIB_SHELL_DIR=$(cd "$SCRIPT_DIR/../../lib/shell" && pwd)
ORIG_ARGS=("$@")
# Load env
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$PROLE_HOME/env.sh"
set -- "${ORIG_ARGS[@]}"
elif [[ -f "$HOME/.prole/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$HOME/.prole/env.sh"
set -- "${ORIG_ARGS[@]}"
fi
# Load config values from prole.cfg before applying defaults/CLI overrides.
# shellcheck disable=SC1090
source "$ETC_DIR/prole_cfg.sh"
# shellcheck disable=SC1090
source "$LIB_SHELL_DIR/prole_env.sh"
# shellcheck disable=SC1090
source "$LIB_SHELL_DIR/prole_secrets.sh"
# shellcheck disable=SC1090
source "$LIB_SHELL_DIR/prole_string.sh"
# shellcheck disable=SC1090
source "$LIB_SHELL_DIR/prole_yaml.sh"
if [[ "${1:-}" == "--mode" || "${1:-}" == "-m" ]]; then
prole_set_mode "${2:-}"
shift 2
elif [[ "${1:-}" == --mode=* || "${1:-}" == -m=* ]]; then
prole_set_mode "${1#*=}"
shift
fi
ACTION=${1:-test}
NAMESPACE="${PROLE_NAMESPACE}"
SERVICE_NAMESPACE=${SERVICE_NAMESPACE:-${NAMESPACE}}
KRB5_REALM=${KRB5_REALM:-${REALM:-}}
KRB5_KDC=${KRB5_KDC:-}
KRB5_USER=${KRB5_USER:-${KRB5_USERNAME:-}}
KRB5_PASSWORD=${KRB5_PASSWORD:-}
PROLE_KDC_NAME=${PROLE_KDC_NAME:-auth}
PROLE_KDC_SERVICE=${PROLE_KDC_SERVICE:-auth}
SAMBA_ADMIN_USER=${SAMBA_ADMIN_USER:-}
SAMBA_ADMIN_PASSWORD=${SAMBA_ADMIN_PASSWORD:-}
SAMBA_DNS_SERVER=${SAMBA_DNS_SERVER:-}
KDC_POD_READY_TIMEOUT=${KDC_POD_READY_TIMEOUT:-60}
KDC_POD_READY_RETRIES=${KDC_POD_READY_RETRIES:-5}
PROLE_KDC_PRECHECK_TIMEOUT=${PROLE_KDC_PRECHECK_TIMEOUT:-5}
KRB5_REALM=$(prole_normalize_realm "${KRB5_REALM:-}")
ensure_tools() {
for t in kubectl curl jq; do
command -v "$t" >/dev/null || { echo "Missing required tool: $t" >&2; exit 1; }
done
}
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
echo "Creating namespace '$NAMESPACE' ..."
kubectl create namespace "$NAMESPACE" >/dev/null 2>&1 || true
fi
}
write_krb5_conf_to_pod() {
local kdc_ns="$1"
local pod_name="$2"
if [[ -z "$kdc_ns" || -z "$pod_name" ]]; then
return 0
fi
if [[ -z "$KRB5_REALM" || -z "$KRB5_KDC" ]]; then
return 0
fi
local admin_val domain_val tmp
admin_val="${KRB5_ADMIN:-$KRB5_KDC}"
domain_val=$(printf '%s' "$KRB5_REALM" | tr '[:upper:]' '[:lower:]')
tmp=$(mktemp)
cat >"$tmp" <<EOF
[libdefaults]
default_realm = $KRB5_REALM
dns_lookup_realm = false
dns_lookup_kdc = false
udp_preference_limit = 1
[realms]
$KRB5_REALM = {
kdc = $KRB5_KDC
admin_server = $admin_val
}
[domain_realm]
.$domain_val = $KRB5_REALM
$domain_val = $KRB5_REALM
EOF
echo "Updating /etc/krb5.conf in test pod ${pod_name} for realm ${KRB5_REALM} ..."
if ! kubectl -n "$kdc_ns" exec -i "$pod_name" -- sh -c 'cat > /etc/krb5.conf' <"$tmp"; then
echo "WARN: Unable to write /etc/krb5.conf in ${pod_name}." >&2
fi
rm -f "$tmp"
}
get_ready_kdc_pod() {
local kdc_ns="$1"
kubectl -n "$kdc_ns" wait --for=condition=Ready pod -l "app=${PROLE_KDC_NAME}" --timeout="${KDC_POD_READY_TIMEOUT}s" >/dev/null 2>&1 || true
kubectl -n "$kdc_ns" get pod -l "app=${PROLE_KDC_NAME}" --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true
}
wait_for_kdc_pod() {
local kdc_ns="$1"
local attempt pod
attempt=1
while [[ $attempt -le $KDC_POD_READY_RETRIES ]]; do
pod=$(get_ready_kdc_pod "$kdc_ns")
if [[ -n "$pod" ]]; then
printf '%s' "$pod"
return 0
fi
echo "Waiting for KDC pod '${PROLE_KDC_NAME}' to be ready... (${attempt}/${KDC_POD_READY_RETRIES})" >&2
sleep 2
attempt=$((attempt + 1))
done
return 1
}
# Return 0 if pod is in CrashLoopBackOff (any container), else 1
is_pod_crashloop() {
local kdc_ns="$1"
local pod_name="$2"
if [[ -z "$pod_name" ]]; then
return 1
fi
local reason
reason=$(kubectl -n "$kdc_ns" get pod "$pod_name" -o jsonpath='{range .status.containerStatuses[*]}{.state.waiting.reason}{"\n"}{end}' 2>/dev/null | tr -d '\r' | tr -s '\n' ' ' || true)
if echo "${reason}" | grep -q "CrashLoopBackOff"; then
return 0
fi
return 1
}
debug_kdc_pod() {
local kdc_ns="$1"
local pod_name="$2"
if [[ -z "$pod_name" ]]; then
return 0
fi
echo "--- describe pod ${pod_name} ---" >&2
kubectl -n "$kdc_ns" describe pod "$pod_name" 1>&2 || true
echo "--- last 200 log lines from ${pod_name} ---" >&2
kubectl -n "$kdc_ns" logs "$pod_name" --tail=200 1>&2 || true
}
kdc_exec() {
local kdc_ns="$1"
local pod_name="$2"
shift 2
local output=""
if output=$(kubectl -n "$kdc_ns" exec "$pod_name" -- "$@" 2>&1); then
printf '%s' "$output"
return 0
fi
if echo "$output" | grep -qi "completed pod"; then
return 2
fi
if echo "$output" | grep -qi "not found"; then
return 2
fi
echo "$output" >&2
return 1
}
run_samba_join() {
local kdc_ns="$1"
local pod_name="$2"
local domain="$3"
local output=""
if output=$(kubectl -n "$kdc_ns" exec "$pod_name" -- \
env SAMBA_ADMIN_USER="$SAMBA_ADMIN_USER" SAMBA_ADMIN_PASSWORD="$SAMBA_ADMIN_PASSWORD" \
SAMBA_DNS_SERVER="$SAMBA_DNS_SERVER" SAMBA_REALM="${KRB5_REALM:-}" \
/bin/sh -c 'realm_flag=""; [ -n "${SAMBA_REALM}" ] && realm_flag="--realm=${SAMBA_REALM}"; samba-tool domain join "'"$domain"'" MEMBER -U"${SAMBA_ADMIN_USER}%${SAMBA_ADMIN_PASSWORD}" --server="${SAMBA_DNS_SERVER}" ${realm_flag}' 2>&1); then
printf '%s' "$output"
return 0
fi
if echo "$output" | grep -qi "completed pod"; then
return 2
fi
if echo "$output" | grep -qi "not found"; then
return 2
fi
if [[ -n "$output" ]]; then
echo "$output" >&2
else
echo "samba-tool produced no output." >&2
fi
return 1
}
run_kinit() {
local kdc_ns="$1"
local pod_name="$2"
local output=""
# Prefer /etc/krb5.conf if it exists, otherwise fallback to /opt/prole-kdc/krb5.conf
local krb5_config="/etc/krb5.conf"
if ! kubectl -n "$kdc_ns" exec "$pod_name" -- ls /etc/krb5.conf >/dev/null 2>&1; then
krb5_config="/opt/prole-kdc/krb5.conf"
fi
echo "Running kinit for ${KRB5_USER}@${KRB5_REALM} inside ${PROLE_KDC_NAME} (${pod_name}) using ${krb5_config} ..."
if output=$(printf '%s\n' "$KRB5_PASSWORD" | kubectl -n "$kdc_ns" exec -i "$pod_name" -- \
env KRB5_CONFIG="$krb5_config" kinit "${KRB5_USER}@${KRB5_REALM}" 2>&1); then
printf '%s' "$output"
return 0
fi
if echo "$output" | grep -qi "completed pod"; then
return 2
fi
if echo "$output" | grep -qi "not found"; then
return 2
fi
if [[ -n "$output" ]]; then
echo "$output" >&2
else
echo "kinit produced no output." >&2
fi
return 1
}
check_samba_connectivity() {
local kdc_ns="$1"
local pod_name="$2"
if [[ -z "$SAMBA_DNS_SERVER" ]]; then
return 0
fi
echo "Checking connectivity from ${PROLE_KDC_NAME} to ${SAMBA_DNS_SERVER} ..."
local ports="88 389 445 464"
local port
for port in $ports; do
local out=""
out=$(kubectl -n "$kdc_ns" exec "$pod_name" -- /bin/sh -c "timeout 3 bash -c '</dev/tcp/${SAMBA_DNS_SERVER}/${port}'" 2>&1) || true
if [[ -z "$out" ]]; then
echo "OK: ${SAMBA_DNS_SERVER}:${port}"
else
echo "WARN: ${SAMBA_DNS_SERVER}:${port} unreachable: ${out}"
fi
done
}
check_kdc_direct_connectivity() {
local kdc_ns="$1"
local pod_name="$2"
local kdc_ip="${AD_DC_IP:-${KDC_ANSIBLE_DETECTED:-${KDC_AUTO_DETECTED:-}}}"
if [[ -z "$kdc_ip" ]]; then
return 0
fi
echo "Checking connectivity from ${PROLE_KDC_NAME} to KDC IP ${kdc_ip} ..."
local ports="88 389 445 464"
local port
for port in $ports; do
local out=""
out=$(kubectl -n "$kdc_ns" exec "$pod_name" -- /bin/sh -c "timeout 3 bash -c '</dev/tcp/${kdc_ip}/${port}'" 2>&1) || true
if [[ -z "$out" ]]; then
echo "OK: ${kdc_ip}:${port}"
else
echo "WARN: ${kdc_ip}:${port} unreachable: ${out}"
fi
done
}
openbao_url() {
if [[ -n "${PROLE_OPENBAO_URL:-}" ]]; then
echo "$PROLE_OPENBAO_URL"
return 0
fi
if prole_is_in_cluster; then
echo "http://openbao.${SERVICE_NAMESPACE:-${NAMESPACE:-default}}.svc.cluster.local:8200"
return 0
fi
if curl -sS "http://127.0.0.1:8200/v1/sys/health" >/dev/null 2>&1; then
echo "http://127.0.0.1:8200"
return 0
elif curl -sS "http://127.0.0.1:8200/v1/sys/health" >/dev/null 2>&1; then
echo "http://127.0.0.1:8200"
return 0
else
echo ""
return 0
fi
}
openbao_token() {
if [[ -f "$PROLE_SERVICE/secrets/openbao-root-token" ]]; then
cat "$PROLE_SERVICE/secrets/openbao-root-token"
else
echo "${OPENBAO_ROOT_TOKEN:-}"
fi
}
fetch_openbao_secret() {
local path="$1"
local key="$2"
local token url
token=$(openbao_token)
url=$(openbao_url)
if [[ -z "$token" || -z "$url" ]]; then
echo ""
return 0
fi
curl -sS -H "X-Vault-Token: $token" "$url/v1/kv/data/$path" | jq -r ".data.data.\"$key\"" || echo ""
}
resolve_krb5_password() {
if prole_secret_needs_resolution "${KRB5_PASSWORD:-}"; then
local path="prole/${NAMESPACE:-default}/kerberos"
local fetched
fetched=$(fetch_openbao_secret "$path" "password")
KRB5_PASSWORD=$(prole_secret_choose_value "${KRB5_PASSWORD:-}" "$fetched")
fi
}
resolve_samba_vars() {
if [[ -n "${SAMBA_ADMIN_USER}" && -n "${SAMBA_ADMIN_PASSWORD}" && -n "${SAMBA_DNS_SERVER}" ]]; then
return 0
fi
# 1. Fallback for DNS server from prole.cfg env (AD_DC_HOST)
if [[ -z "$SAMBA_DNS_SERVER" && -n "${AD_DC_HOST:-}" ]]; then
SAMBA_DNS_SERVER="$AD_DC_HOST"
echo "Using SAMBA_DNS_SERVER=${SAMBA_DNS_SERVER} from AD_DC_HOST"
fi
# 2. Try to resolve from Ansible inventory if possible
local root
if [[ -n "${PROLE_HOME:-}" && -d "${PROLE_HOME}" ]]; then
root="${PROLE_HOME}"
else
root="${SCRIPT_DIR}/.."
fi
local vars_file="${root}/infrastructure/inventory/group_vars/ad_dc/vars.yml"
if [[ -f "$vars_file" ]]; then
if [[ -z "$SAMBA_DNS_SERVER" ]]; then
SAMBA_DNS_SERVER=$(prole_yaml_get_scalar "$vars_file" "samba_dns_server")
fi
if [[ -z "$SAMBA_ADMIN_USER" ]]; then
SAMBA_ADMIN_USER=$(prole_yaml_get_scalar "$vars_file" "samba_dns_admin_user")
fi
fi
# 3. Fallback for Admin User
if [[ -z "$SAMBA_ADMIN_USER" ]]; then
if [[ -n "${AD_DC_USER:-}" ]]; then
SAMBA_ADMIN_USER="$AD_DC_USER"
echo "Using SAMBA_ADMIN_USER=${SAMBA_ADMIN_USER} from AD_DC_USER"
else
SAMBA_ADMIN_USER="administrator"
echo "Defaulting SAMBA_ADMIN_USER to administrator"
fi
fi
# 4. Try to resolve password from Ansible Vault
if [[ -z "$SAMBA_ADMIN_PASSWORD" ]]; then
local vault_file="${root}/infrastructure/inventory/group_vars/ad_dc/vault.yml"
local vault_pass_file=""
if [[ -n "${ANSIBLE_VAULT_PASSWORD_FILE:-}" && -f "${ANSIBLE_VAULT_PASSWORD_FILE}" ]]; then
vault_pass_file="${ANSIBLE_VAULT_PASSWORD_FILE}"
elif [[ -n "${PROLE_HOME:-}" && -f "${PROLE_HOME}/.vault_pass" ]]; then
vault_pass_file="${PROLE_HOME}/.vault_pass"
elif [[ -f "${root}/.vault_pass" ]]; then
vault_pass_file="${root}/.vault_pass"
fi
if [[ -f "$vault_file" && -n "$vault_pass_file" && -x "$(command -v ansible-vault)" ]]; then
local vault_out
vault_out=$(ansible-vault view "$vault_file" --vault-password-file "$vault_pass_file" 2>/dev/null || true)
if [[ -n "$vault_out" ]]; then
SAMBA_ADMIN_PASSWORD=$(printf '%s\n' "$vault_out" | awk -F: '/^[[:space:]]*vault_samba_dns_admin_pass[[:space:]]*:/ {sub(/^[^:]+:[[:space:]]*/, "", $0); gsub(/"/, "", $0); print $0; exit}')
fi
fi
fi
# 5. Fallback for Password from OpenBao
if [[ -z "$SAMBA_ADMIN_PASSWORD" ]]; then
local path="prole/${NAMESPACE:-default}/ad_dc"
local fetched
fetched=$(fetch_openbao_secret "$path" "password")
if [[ -n "$fetched" && "$fetched" != "null" ]]; then
SAMBA_ADMIN_PASSWORD="$fetched"
echo "Fetched SAMBA_ADMIN_PASSWORD from OpenBao (${path})"
else
# Try kerberos path as fallback
path="prole/${NAMESPACE:-default}/kerberos"
fetched=$(fetch_openbao_secret "$path" "samba_password")
if [[ -n "$fetched" && "$fetched" != "null" ]]; then
SAMBA_ADMIN_PASSWORD="$fetched"
echo "Fetched SAMBA_ADMIN_PASSWORD from OpenBao (${path}#samba_password)"
fi
fi
fi
}
join_samba_realm_if_needed() {
local kdc_ns="$1"
local pod_name="$2"
local domain=""
resolve_samba_vars
if [[ -z "$SAMBA_ADMIN_USER" || -z "$SAMBA_ADMIN_PASSWORD" || -z "$SAMBA_DNS_SERVER" ]]; then
echo "WARN: Missing Samba join info; skipping samba-tool realm join."
return 0
fi
check_samba_connectivity "$kdc_ns" "$pod_name"
if [[ -n "$KRB5_REALM" ]]; then
domain=$(printf '%s' "$KRB5_REALM" | tr '[:upper:]' '[:lower:]')
fi
if [[ -z "$domain" ]]; then
domain="$SAMBA_DNS_SERVER"
fi
if kubectl -n "$kdc_ns" exec "$pod_name" -- test -f /var/lib/samba/private/secrets.tdb >/dev/null 2>&1; then
echo "Samba already joined; skipping join."
return 0
fi
echo "Joining Samba realm '${domain}' (server ${SAMBA_DNS_SERVER}) ..."
local attempt=1
while true; do
if run_samba_join "$kdc_ns" "$pod_name" "$domain"; then
return 0
fi
if [[ $? -eq 2 ]]; then
if [[ $attempt -ge $KDC_POD_READY_RETRIES ]]; then
echo "WARN: KDC pod completed before join could run; skipping samba-tool join." >&2
return 0
fi
pod_name=$(wait_for_kdc_pod "$kdc_ns" || true)
if [[ -z "$pod_name" ]]; then
echo "WARN: KDC pod unavailable for samba-tool join." >&2
return 0
fi
attempt=$((attempt + 1))
continue
fi
echo "WARN: samba-tool domain join failed; continuing to Kerberos test." >&2
return 0
done
}
run_test() {
ensure_tools
ensure_namespace
resolve_krb5_password
# Support both names for the toggle from prole.cfg
local enabled="${KERBEROS_ENABLED:-${ENABLED:-false}}"
if [[ "$enabled" == "false" || "$enabled" == "0" || "$enabled" == "False" ]]; then
echo "Kerberos is disabled (KERBEROS_ENABLED=$enabled). Skipping test."
return 0
fi
if [[ -z "$KRB5_REALM" || -z "$KRB5_USER" || -z "$KRB5_PASSWORD" || -z "$KRB5_KDC" ]]; then
echo "ERROR: Missing Kerberos configuration. Ensure KRB5_REALM, KRB5_KDC, KRB5_USER, KRB5_PASSWORD are set." >&2
exit 1
fi
# Quick pre-check: if an existing 'auth' deployment is present but not Available, remove it
# to avoid starting a rollout that is likely to time out on a stale/broken replica set.
local kdc_ns
kdc_ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}"
if kubectl -n "$kdc_ns" get deploy "$PROLE_KDC_NAME" >/dev/null 2>&1; then
# Try a fast availability wait; if it fails, treat as unhealthy and clean up before proceeding.
if ! kubectl -n "$kdc_ns" wait --for=condition=Available deploy/"$PROLE_KDC_NAME" --timeout="${PROLE_KDC_PRECHECK_TIMEOUT}s" >/dev/null 2>&1; then
echo "Detected unhealthy '$PROLE_KDC_NAME' deployment in namespace '$kdc_ns'; removing before Kerberos test rollout to avoid timeout..." >&2
if [[ -x "$SCRIPT_DIR/init_kdc.sh" ]]; then
SERVICE_NAMESPACE="$SERVICE_NAMESPACE" "$SCRIPT_DIR/init_kdc.sh" cleanup || true
else
kubectl -n "$kdc_ns" delete deploy "$PROLE_KDC_NAME" --ignore-not-found || true
kubectl -n "$kdc_ns" delete svc "$PROLE_KDC_SERVICE" --ignore-not-found || true
kubectl -n "$kdc_ns" delete configmap prole-kdc-config --ignore-not-found || true
fi
fi
fi
if [[ -x "$SCRIPT_DIR/init_kdc.sh" ]]; then
SERVICE_NAMESPACE="$SERVICE_NAMESPACE" KRB5_REALM="$KRB5_REALM" KRB5_KDC="$KRB5_KDC" \
KRB5_ADMIN="$KRB5_KDC" KRB5_USER="$KRB5_USER" KRB5_PASSWORD="$KRB5_PASSWORD" \
"$SCRIPT_DIR/init_kdc.sh" update || true
fi
local pod_name
pod_name=$(wait_for_kdc_pod "$kdc_ns" || true)
if [[ -z "$pod_name" ]]; then
echo "ERROR: KDC pod '${PROLE_KDC_NAME}' not found in namespace '$kdc_ns'. Run init_kdc.sh update first." >&2
exit 1
fi
# If the pod is crashlooping, dump logs and fail fast
if is_pod_crashloop "$kdc_ns" "$pod_name"; then
echo "ERROR: KDC pod '${pod_name}' is in CrashLoopBackOff. Dumping diagnostics..." >&2
debug_kdc_pod "$kdc_ns" "$pod_name"
exit 1
fi
write_krb5_conf_to_pod "$kdc_ns" "$pod_name"
check_kdc_direct_connectivity "$kdc_ns" "$pod_name"
join_samba_realm_if_needed "$kdc_ns" "$pod_name"
echo "Running kinit for ${KRB5_USER}@${KRB5_REALM} inside ${PROLE_KDC_NAME} (${pod_name}) ..."
local attempt=1
while true; do
if run_kinit "$kdc_ns" "$pod_name"; then
break
fi
if [[ $? -eq 2 ]] || kubectl -n "$kdc_ns" get pod "$pod_name" -o jsonpath='{.status.phase}' 2>/dev/null | grep -qi "Succeeded"; then
if [[ $attempt -ge $KDC_POD_READY_RETRIES ]]; then
echo "ERROR: KDC pod completed before kinit could run." >&2
exit 1
fi
pod_name=$(wait_for_kdc_pod "$kdc_ns" || true)
if [[ -z "$pod_name" ]]; then
echo "ERROR: KDC pod unavailable for kinit." >&2
exit 1
fi
attempt=$((attempt + 1))
continue
fi
# If the pod is crashlooping, show logs to aid troubleshooting
if is_pod_crashloop "$kdc_ns" "$pod_name"; then
echo "ERROR: KDC pod '${pod_name}' entered CrashLoopBackOff during kinit. Dumping diagnostics..." >&2
debug_kdc_pod "$kdc_ns" "$pod_name"
fi
echo "ERROR: kinit failed for ${KRB5_USER}@${KRB5_REALM}." >&2
exit 1
done
echo "Kerberos ticket cache:"
# Determine krb5 config path in the pod similar to run_kinit()
local krb5_config_in_pod="/etc/krb5.conf"
if ! kubectl -n "$kdc_ns" exec "$pod_name" -- ls /etc/krb5.conf >/dev/null 2>&1; then
krb5_config_in_pod="/opt/prole-kdc/krb5.conf"
fi
kubectl -n "$kdc_ns" exec "$pod_name" -- env KRB5_CONFIG="$krb5_config_in_pod" klist || true
}
case "$ACTION" in
test)
run_test
;;
cleanup)
echo "No test pods to cleanup (KDC-based test)."
;;
*)
echo "Usage: $0 {test|cleanup}" >&2
exit 2
;;
esac

View File

@ -7,16 +7,18 @@ set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
SOURCE_ETC_DIR="$PROLE_HOME/etc"
SOURCE_LIB_SHELL_DIR="$PROLE_HOME/lib/shell"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
WORK_DIR="$TMP_DIR/work"
BIN_DIR="$TMP_DIR/bin"
mkdir -p "$WORK_DIR/etc" "$WORK_DIR/conf" "$WORK_DIR/k8s/prole" "$BIN_DIR"
mkdir -p "$WORK_DIR/etc" "$WORK_DIR/conf" "$WORK_DIR/k8s/prole" "$WORK_DIR/lib/shell" "$BIN_DIR"
cp "$SOURCE_ETC_DIR/init_garage_store.sh" "$WORK_DIR/etc/init_garage_store.sh"
cp "$SOURCE_ETC_DIR/common_core_lib.sh" "$WORK_DIR/etc/common_core_lib.sh"
cp "$SOURCE_LIB_SHELL_DIR/common_core_lib.sh" "$WORK_DIR/lib/shell/common_core_lib.sh"
cp "$SOURCE_ETC_DIR/prole_cfg.sh" "$WORK_DIR/etc/prole_cfg.sh"
chmod +x "$WORK_DIR/etc/init_garage_store.sh"

View File

@ -7,16 +7,18 @@ set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
SOURCE_ETC_DIR="$PROLE_HOME/etc"
SOURCE_LIB_SHELL_DIR="$PROLE_HOME/lib/shell"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
WORK_DIR="$TMP_DIR/work"
BIN_DIR="$TMP_DIR/bin"
mkdir -p "$WORK_DIR/etc" "$WORK_DIR/conf" "$BIN_DIR"
mkdir -p "$WORK_DIR/etc" "$WORK_DIR/conf" "$WORK_DIR/lib/shell" "$BIN_DIR"
cp "$SOURCE_ETC_DIR/init_kong.sh" "$WORK_DIR/etc/init_kong.sh"
cp "$SOURCE_ETC_DIR/common_core_lib.sh" "$WORK_DIR/etc/common_core_lib.sh"
cp "$SOURCE_LIB_SHELL_DIR/common_core_lib.sh" "$WORK_DIR/lib/shell/common_core_lib.sh"
cp "$SOURCE_ETC_DIR/prole_cfg.sh" "$WORK_DIR/etc/prole_cfg.sh"
chmod +x "$WORK_DIR/etc/init_kong.sh"

View File

@ -7,16 +7,18 @@ set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
SOURCE_ETC_DIR="$PROLE_HOME/etc"
SOURCE_LIB_SHELL_DIR="$PROLE_HOME/lib/shell"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
WORK_DIR="$TMP_DIR/work"
BIN_DIR="$TMP_DIR/bin"
mkdir -p "$WORK_DIR/etc" "$WORK_DIR/conf" "$BIN_DIR"
mkdir -p "$WORK_DIR/etc" "$WORK_DIR/conf" "$WORK_DIR/lib/shell" "$BIN_DIR"
cp "$SOURCE_ETC_DIR/init_openbao.sh" "$WORK_DIR/etc/init_openbao.sh"
cp "$SOURCE_ETC_DIR/common_core_lib.sh" "$WORK_DIR/etc/common_core_lib.sh"
cp "$SOURCE_LIB_SHELL_DIR/common_core_lib.sh" "$WORK_DIR/lib/shell/common_core_lib.sh"
cp "$SOURCE_ETC_DIR/prole_cfg.sh" "$WORK_DIR/etc/prole_cfg.sh"
chmod +x "$WORK_DIR/etc/init_openbao.sh"

View File

@ -0,0 +1,59 @@
version: 2
jobs:
lint:
docker:
- image: koalaman/shellcheck-alpine:v0.7.1
steps:
- checkout
- run: shellcheck shellspec $(find lib libexec spec examples -name '*.sh')
test:
working_directory: ~/shellspec
docker:
- image: alpine
steps:
- run: apk add --no-progress --no-cache ca-certificates
- checkout
- run: ./shellspec --task fixture:stat:prepare
- run: ./shellspec -o tap -o junit
- run:
command: |
mkdir -p ~/report/shellspec
cp report/results_junit.xml ~/report/shellspec/
when: always
- store_test_results:
path: ~/report
- store_artifacts:
path: report
coverage:
working_directory: ~/shellspec
docker:
- image: shellspec/kcov
steps:
- checkout
- run: ./shellspec --task fixture:stat:prepare
- run: ./shellspec --kcov
- store_artifacts:
path: coverage
workflows:
version: 2
lint_test_and_coverage:
jobs:
- lint
- test
- coverage:
requires:
- lint
- test
daily_update_schedule:
jobs:
- lint
- coverage:
requires:
- lint
triggers:
- schedule:
cron: "0 0 * * *"
filters:
branches:
only:
- master

View File

@ -0,0 +1,80 @@
env:
FORCE_COLOR: 1
freebsd_task:
freebsd_instance:
matrix:
- image: freebsd-10-4-release-amd64
- image_family: freebsd-11-4
- image_family: freebsd-12-2
# - image_family: freebsd-13-0-snap
install_script: pkg install -y dash bash zsh ksh93 mksh oksh
prepare_script:
- ./shellspec --task fixture:stat:prepare
script:
- contrib/all.sh contrib/various_test.sh
# gitbash_task:
# timeout_in: 120m
# windows_container:
# image: cirrusci/windowsservercore:2019
# os_version: 2019
# env:
# PATH: $ProgramFiles\Git\bin;$PATH
# install_script:
# - git --version
# prepare_script:
# - bash -c "env | grep -E '^(LC_|LANG|CYGWIN|MSYS)'"
# - bash -c "mount"
# - bash -c "./shellspec --task fixture:stat:prepare"
# script:
# - bash -c "contrib/all.sh shellspec"
# msys_task:
# timeout_in: 120m
# windows_container:
# image: cirrusci/windowsservercore:2019
# os_version: 2019
# env:
# PATH: C:\tools\msys64\usr\bin;$PATH
# install_script:
# - choco install -y --no-progress msys2
# - pacman.exe -Syu --noprogressbar --noconfirm
# - pacman.exe -S --noprogressbar --noconfirm dash bash busybox mksh zsh
# - pacman.exe -Q
# prepare_script:
# - bash -c "env | grep -E '^(LC_|LANG|CYGWIN|MSYS)'"
# - bash -c "mount"
# - bash -c "./shellspec --task fixture:stat:prepare"
# script:
# - bash -c "contrib/all.sh shellspec"
# cygwin_task:
# timeout_in: 120m
# windows_container:
# image: cirrusci/windowsservercore:2019
# os_version: 2019
# env:
# PATH: C:\tools\cygwin\bin;$PATH
# install_script:
# - choco install -y --no-progress cygwin cyg-get
# - cyg-get nc dash bash busybox mksh posh zsh
# - cygcheck -c
# prepare_script:
# - bash -c "env | grep -E '^(LC_|LANG|CYGWIN|MSYS)'"
# - bash -c "mount"
# - bash -c "./shellspec --task fixture:stat:prepare"
# # I don't know why but tests using below files fail in cirrus ci environment.
# - bash -c "rm -f ./spec/fixture/stat/{readable,writable}"
# script:
# - bash -c 'contrib/all.sh shellspec'
# busybox_task:
# timeout_in: 120m
# windows_container:
# image: cirrusci/windowsservercore:2019
# os_version: 2019
# install_script:
# - choco install -y --no-progress busybox
# script:
# - busybox ash -c "contrib/all.sh shellspec"

View File

@ -0,0 +1,6 @@
coverage:
status:
project:
default:
target: 30%
patch: false

View File

@ -0,0 +1,82 @@
# Build two images with Automated Builds using Docker Hub hooks.
# See https://github.com/shellspec/shellspec/tree/master/.dockerhub/hooks
# ======================================================================
# source code
# ======================================================================
FROM scratch as source
COPY shellspec LICENSE /opt/shellspec/
COPY lib /opt/shellspec/lib
COPY libexec /opt/shellspec/libexec
# ======================================================================
# Kcov builder
# ======================================================================
FROM alpine:3.12 as builder
ENV KCOV=v38 CXXFLAGS=-D__ptrace_request=int
WORKDIR /usr/local/src
RUN apk add --no-cache build-base cmake ninja python3 \
binutils-dev curl-dev elfutils-dev
RUN wget -q https://github.com/SimonKagstrom/kcov/archive/$KCOV.tar.gz
RUN tar xzf $KCOV.tar.gz -C ./ --strip-components 1
RUN mkdir build && cd build \
&& cmake -G Ninja .. && cmake --build . --target install
# ======================================================================
# Kcov image
# TAG: shellspec:kcov, shellspec:[VERSION]-kcov
# ======================================================================
FROM alpine:3.12 as kcov
RUN apk add --no-cache bash binutils-dev curl-dev elfutils-libelf
COPY --from=builder /usr/local/bin/kcov* /usr/local/bin/
COPY --from=builder /usr/local/share/doc/kcov /usr/local/share/doc/kcov
COPY --from=source /opt/shellspec /opt/shellspec
COPY .dockerhub/shellspec-docker-entrypoint.sh /shellspec-docker
ENV PATH /opt/shellspec/:$PATH
WORKDIR /src
ENTRYPOINT [ "shellspec" ]
ARG CREATED
ARG AUTHORS
ARG VERSION
ARG REVISION
ARG REFNAME
LABEL org.opencontainers.image.created=$CREATED \
org.opencontainers.image.authors=$AUTHORS \
org.opencontainers.image.url="https://shellspec.info/" \
org.opencontainers.image.documentation="https://github.com/shellspec/shellspec" \
org.opencontainers.image.source="https://github.com/shellspec/shellspec.git" \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$REVISION \
org.opencontainers.image.vendor="shellspec" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.ref.name=$REFNAME \
org.opencontainers.image.title="shellspec+kcov" \
org.opencontainers.image.description="Shellspec (Alpine based with Kcov)"
# ======================================================================
# Standard image (default)
# TAG: shellspec:latest, shellspec:[VERSION]
# ======================================================================
FROM alpine:3.12 as standard
COPY --from=source /opt/shellspec /opt/shellspec
COPY .dockerhub/shellspec-docker-entrypoint.sh /shellspec-docker
ENV PATH /opt/shellspec/:$PATH
WORKDIR /src
ENTRYPOINT [ "shellspec" ]
ARG CREATED
ARG AUTHORS
ARG VERSION
ARG REVISION
ARG REFNAME
LABEL org.opencontainers.image.created=$CREATED \
org.opencontainers.image.authors=$AUTHORS \
org.opencontainers.image.url="https://shellspec.info/" \
org.opencontainers.image.documentation="https://github.com/shellspec/shellspec" \
org.opencontainers.image.source="https://github.com/shellspec/shellspec.git" \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$REVISION \
org.opencontainers.image.vendor="shellspec" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.ref.name=$REFNAME \
org.opencontainers.image.title="shellspec" \
org.opencontainers.image.description="Shellspec (Alpine based)"

View File

@ -0,0 +1,85 @@
# Build two images with Automated Builds using Docker Hub hooks.
# See https://github.com/shellspec/shellspec/tree/master/.dockerhub/hooks
# ======================================================================
# source code
# ======================================================================
FROM scratch as source
COPY shellspec LICENSE /opt/shellspec/
COPY lib /opt/shellspec/lib
COPY libexec /opt/shellspec/libexec
# ======================================================================
# Kcov builder
# ======================================================================
FROM debian:10 AS builder
ENV KCOV=v38 DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes
WORKDIR /usr/local/src
RUN apt-get update && apt-get install -y \
build-essential cmake ninja-build python3 wget \
binutils-dev libcurl4-openssl-dev zlib1g-dev libdw-dev libiberty-dev
RUN wget -q https://github.com/SimonKagstrom/kcov/archive/$KCOV.tar.gz
RUN tar xzf $KCOV.tar.gz -C ./ --strip-components 1
RUN mkdir build && cd build \
&& cmake -G Ninja .. && cmake --build . --target install
# ======================================================================
# Kcov image
# TAG: shellspec-debian:kcov, shellspec-debian:[VERSION]-kcov
# ======================================================================
FROM debian:10 as kcov
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes \
&& apt-get update && apt-get install -y binutils libcurl4 zlib1g libdw1 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/bin/kcov* /usr/local/bin/
COPY --from=builder /usr/local/share/doc/kcov /usr/local/share/doc/kcov
COPY --from=source /opt/shellspec /opt/shellspec
COPY .dockerhub/shellspec-docker-entrypoint.sh /shellspec-docker
ENV PATH /opt/shellspec/:$PATH
WORKDIR /src
ENTRYPOINT [ "shellspec" ]
ARG CREATED
ARG AUTHORS
ARG VERSION
ARG REVISION
ARG REFNAME
LABEL org.opencontainers.image.created=$CREATED \
org.opencontainers.image.authors=$AUTHORS \
org.opencontainers.image.url="https://shellspec.info/" \
org.opencontainers.image.documentation="https://github.com/shellspec/shellspec" \
org.opencontainers.image.source="https://github.com/shellspec/shellspec.git" \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$REVISION \
org.opencontainers.image.vendor="shellspec" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.ref.name=$REFNAME \
org.opencontainers.image.title="shellspec-debian+kcov" \
org.opencontainers.image.description="Shellspec (Debian based with Kcov)"
# ======================================================================
# Standard image (default)
# TAG: shellspec-debian:latest, shellspec-debian:[VERSION]
# ======================================================================
FROM debian:10 as standard
COPY --from=source /opt/shellspec /opt/shellspec
COPY .dockerhub/shellspec-docker-entrypoint.sh /shellspec-docker
ENV PATH /opt/shellspec/:$PATH
WORKDIR /src
ENTRYPOINT [ "shellspec" ]
ARG CREATED
ARG AUTHORS
ARG VERSION
ARG REVISION
ARG REFNAME
LABEL org.opencontainers.image.created=$CREATED \
org.opencontainers.image.authors=$AUTHORS \
org.opencontainers.image.url="https://shellspec.info/" \
org.opencontainers.image.documentation="https://github.com/shellspec/shellspec" \
org.opencontainers.image.source="https://github.com/shellspec/shellspec.git" \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$REVISION \
org.opencontainers.image.vendor="shellspec" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.ref.name=$REFNAME \
org.opencontainers.image.title="shellspec-debian" \
org.opencontainers.image.description="Shellspec (Debian based)"

View File

@ -0,0 +1,44 @@
# ======================================================================
# source code
# ======================================================================
FROM scratch as source
COPY shellspec LICENSE /opt/shellspec/
COPY lib /opt/shellspec/lib
COPY libexec /opt/shellspec/libexec
# ======================================================================
# Fake kcov target to pass the test
# ======================================================================
FROM busybox as kcov
RUN echo "#!/bin/true" > /bin/sh
# ======================================================================
# Use standard target to test source code
# ======================================================================
FROM alpine as standard
ENV PATH /opt/shellspec/:$PATH
COPY --from=source /opt/shellspec /opt/shellspec
# ======================================================================
# Source image (default)
# TAG: shellspec-scratch:latest, shellspec-scratch:[VERSION]
# ======================================================================
FROM scratch
COPY --from=source /opt/shellspec /opt/shellspec
ARG CREATED
ARG AUTHORS
ARG VERSION
ARG REVISION
ARG REFNAME
LABEL org.opencontainers.image.created=$CREATED \
org.opencontainers.image.authors=$AUTHORS \
org.opencontainers.image.url="https://shellspec.info/" \
org.opencontainers.image.documentation="https://github.com/shellspec/shellspec" \
org.opencontainers.image.source="https://github.com/shellspec/shellspec.git" \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$REVISION \
org.opencontainers.image.vendor="shellspec" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.ref.name=$REFNAME \
org.opencontainers.image.title="shellspec-scratch" \
org.opencontainers.image.description="Shellspec (Scratch based)"

View File

@ -0,0 +1,55 @@
# ShellSpec docker images
[ShellSpec](https://shellspec.info/) is a full-featured BDD unit testing framework for shell scripts.
These docker images are for running ShellSpec easily and suitable for incorporating into CI.
The tag with `kcov` means [Kcov](https://github.com/SimonKagstrom/kcov) is included. The tag `latest` is latest release version.
## Alpine based
![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/shellspec/shellspec)
![Docker Cloud Automated build](https://img.shields.io/docker/cloud/automated/shellspec/shellspec)
![Docker Pulls](https://img.shields.io/docker/pulls/shellspec/shellspec)
https://hub.docker.com/r/shellspec/shellspec
Tags:
- shellspec/shellspec:latest
- shellspec/shellspec:kcov
- shellspec/shellspec:master
- shellspec/shellspec:master-kcov
- shellspec/shellspec:[VERSION]
- shellspec/shellspec:[VERSION]-kcov
## Debian based
![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/shellspec/shellspec-debian)
![Docker Cloud Automated build](https://img.shields.io/docker/cloud/automated/shellspec/shellspec-debian)
![Docker Pulls](https://img.shields.io/docker/pulls/shellspec/shellspec-debian)
https://hub.docker.com/r/shellspec/shellspec-debian
Tags:
- shellspec/shellspec-debian:latest
- shellspec/shellspec-debian:kcov
- shellspec/shellspec-debian:master
- shellspec/shellspec-debian:master-kcov
- shellspec/shellspec-debian:[VERSION]
- shellspec/shellspec-debian:[VERSION]-kcov
## Scratch based
![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/shellspec/shellspec-scratch)
![Docker Cloud Automated build](https://img.shields.io/docker/cloud/automated/shellspec/shellspec-scratch)
![Docker Pulls](https://img.shields.io/docker/pulls/shellspec/shellspec-scratch)
https://hub.docker.com/r/shellspec/shellspec-scratch
Tags:
- shellspec/shellspec-scratch:latest
- shellspec/shellspec-scratch:master
- shellspec/shellspec-scratch:[VERSION]

View File

@ -0,0 +1,14 @@
version: "3.4"
services:
sut:
build:
context: ..
dockerfile: ${DOCKERFILE_BASE:-}${DOCKERFILE_PATH:-Dockerfile}
target: kcov
entrypoint: /bin/sh -eux -c
command:
- |
shellspec --version
shellspec --init spec
shellspec -f tap
kcov --version

View File

@ -0,0 +1,13 @@
version: "3.4"
services:
sut:
build:
context: ..
dockerfile: ${DOCKERFILE_BASE:-}${DOCKERFILE_PATH:-Dockerfile}
target: standard
entrypoint: /bin/sh -eux -c
command:
- |
shellspec --version
shellspec --init spec
shellspec -f tap

View File

@ -0,0 +1,12 @@
#!/bin/sh -eux
CREATED=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
VERSION=$(../shellspec --version)
REVISION=$(echo "$SOURCE_COMMIT" | cut -c-10)
docker build "$@" -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" .. \
--build-arg CREATED="$CREATED" \
--build-arg AUTHORS="$AUTHORS" \
--build-arg VERSION="$VERSION" \
--build-arg REVISION="$REVISION" \
--build-arg REFNAME="$DOCKER_TAG"

View File

@ -0,0 +1,17 @@
#!/bin/sh -eux
# IMAGE_NAME ($DOCKER_REPO:$DOCKER_TAG)
# shellspec/shellspec:<TAG>
# shellspec/shellspec-<VARIANT>:<VERSION>
case $DOCKER_REPO in
*-scratch) exit
esac
# Build kcov image
case $DOCKER_TAG in
latest) IMAGE_NAME="$DOCKER_REPO:kcov" ;;
* ) IMAGE_NAME="$IMAGE_NAME-kcov" ;;
esac
DOCKER_TAG=${IMAGE_NAME#*:}
"$(dirname "$0")/build" --target kcov

View File

@ -0,0 +1,16 @@
#!/bin/sh -eux
# IMAGE_NAME ($DOCKER_REPO:$DOCKER_TAG)
# shellspec/shellspec:<TAG>
# shellspec/shellspec-<VARIANT>:<VERSION>
case $DOCKER_REPO in
*-scratch) exit
esac
# Push kcov image
case $DOCKER_TAG in
latest) IMAGE_NAME="$DOCKER_REPO:kcov" ;;
* ) IMAGE_NAME="$IMAGE_NAME-kcov" ;;
esac
docker push "$IMAGE_NAME"

View File

@ -0,0 +1,19 @@
#!/bin/sh
set -eu
if [ "${1:-}" = "-" ] && shift; then
[ $# -gt 0 ] && exec "$@"
type bash >/dev/null 2>&1 && exec /bin/bash -l
exec /bin/sh -l
fi
if [ -e .shellspec-docker/pre-test ]; then
.shellspec-docker/pre-test
fi
shellspec "$@"
if [ -e .shellspec-docker/post-test ]; then
.shellspec-docker/post-test
fi

View File

@ -0,0 +1,19 @@
*
!.dockerhub/shellspec-docker-entrypoint.sh
!bin
!contrib/bugs.sh
!lib
!libexec
!dockerfiles/.shellspec-entrypoint.sh
!examples
!helper
!spec
!stub
helper/fixture/stat/*
!.shellspec
!shellspec
!install.sh
!LICENSE
!Makefile
!package.json
!*.sh

View File

@ -0,0 +1,3 @@
* text=auto eol=lf
helper/fixture/** -text
dockerfiles/* linguist-language=Dockerfile

View File

@ -0,0 +1,27 @@
name: macOS Homebrew
on: [push, pull_request]
env:
FORCE_COLOR: 1
jobs:
macos-brew:
runs-on: macos-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: dash, package: dash}
- {shell: bash, package: bash}
- {shell: ksh, package: ksh}
- {shell: mksh, package: mksh}
- {shell: yash, package: yash}
- {shell: zsh, package: zsh}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: brew upgrade
- run: brew install ${{ matrix.shells.package }}
- run: ${{ matrix.shells.shell }} ./shellspec --shell "${{ matrix.shells.shell }}" --task fixture:stat:prepare
- run: SH="${{ matrix.shells.shell }}" contrib/various_test.sh

View File

@ -0,0 +1,24 @@
name: macOS Catalina
on: [push, pull_request]
env:
FORCE_COLOR: 1
jobs:
macos-catalina:
runs-on: macos-10.15
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: sh}
- {shell: dash}
- {shell: bash}
- {shell: ksh}
- {shell: zsh}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: ${{ matrix.shells.shell }} ./shellspec --shell "${{ matrix.shells.shell }}" --task fixture:stat:prepare
- run: SH="${{ matrix.shells.shell }}" contrib/various_test.sh

View File

@ -0,0 +1,33 @@
name: Release
on:
push:
tags:
- '[0-9]*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Make dist archive
run: make dist
- name: Create release
id: create_release
uses: actions/create-release@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
prerelease: ${{ contains(github.ref, '-') }}
- name: Upload release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: shellspec-dist.tar.gz
asset_name: shellspec-dist.tar.gz
asset_content_type: application/gzip

View File

@ -0,0 +1,28 @@
name: Ubuntu Bionic Beaver
on: [push, pull_request]
env:
FORCE_COLOR: 1
jobs:
ubuntu-bionic:
runs-on: ubuntu-18.04
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: dash, package: dash}
- {shell: bash, package: bash}
- {shell: busybox ash, package: busybox}
- {shell: ksh, package: ksh}
- {shell: mksh, package: mksh}
- {shell: posh, package: posh}
- {shell: yash, package: yash}
- {shell: zsh, package: zsh}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: sudo apt-get install -y ${{ matrix.shells.package }}
- run: sudo ${{ matrix.shells.shell }} ./shellspec --task fixture:stat:prepare
- run: SH="${{ matrix.shells.shell }}" contrib/various_test.sh

View File

@ -0,0 +1,28 @@
name: Ubuntu Focal Fossa
on: [push, pull_request]
env:
FORCE_COLOR: 1
jobs:
ubuntu-focal:
runs-on: ubuntu-20.04
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: dash, package: dash}
- {shell: bash, package: bash}
- {shell: busybox ash, package: busybox}
- {shell: ksh, package: ksh}
- {shell: mksh, package: mksh}
- {shell: posh, package: posh}
- {shell: yash, package: yash}
- {shell: zsh, package: zsh}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: sudo apt-get install -y ${{ matrix.shells.package }}
- run: sudo ${{ matrix.shells.shell }} ./shellspec --task fixture:stat:prepare
- run: SH="${{ matrix.shells.shell }}" contrib/various_test.sh

View File

@ -0,0 +1,28 @@
name: Ubuntu Xenial Xerus
on: [push, pull_request]
env:
FORCE_COLOR: 1
jobs:
ubuntu-xenial:
runs-on: ubuntu-16.04
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: dash, package: dash}
- {shell: bash, package: bash}
- {shell: busybox ash, package: busybox}
- {shell: ksh, package: ksh}
- {shell: mksh, package: mksh}
- {shell: posh, package: posh}
- {shell: yash, package: yash}
- {shell: zsh, package: zsh}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: sudo apt-get install -y ${{ matrix.shells.package }}
- run: sudo ${{ matrix.shells.shell }} ./shellspec --task fixture:stat:prepare
- run: SH="${{ matrix.shells.shell }}" contrib/various_test.sh

View File

@ -0,0 +1,28 @@
name: Windows busybox
on: [push, pull_request]
env:
PATH: 'C:\Program Files\Git\bin;C:\Windows\System32;C:\Windows;C:\ProgramData\Chocolatey\bin'
FORCE_COLOR: 1
defaults:
run:
shell: cmd
jobs:
windows-busybox:
runs-on: windows-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: sh}
- {shell: ash}
- {shell: bash}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: choco install -y --no-progress busybox
- run: busybox ${{ matrix.shells.shell }} ./shellspec --task fixture:stat:prepare
- run: busybox ${{ matrix.shells.shell }} ./shellspec

View File

@ -0,0 +1,35 @@
name: Windows Cygwin
on: [push, pull_request]
env:
PATH: 'C:\tools\cygwin\bin;C:\Program Files\Git\bin;C:\Windows\System32;C:\Windows;C:\Windows\System32\WindowsPowerShell\v1.0;C:\ProgramData\Chocolatey\bin'
FORCE_COLOR: 1
defaults:
run:
shell: cmd
jobs:
windows-cygwin:
runs-on: windows-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: sh, package: sh}
- {shell: dash, package: dash}
- {shell: bash, package: bash}
- {shell: ash, package: busybox}
- {shell: mksh, package: mksh}
- {shell: posh, package: posh}
- {shell: zsh, package: zsh}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: choco install -y --no-progress cygwin cyg-get
- run: cyg-get nc ${{ matrix.shells.package }}
- run: cygcheck -c
- run: ${{ matrix.shells.shell }} ./shellspec --task fixture:stat:prepare
- run: ${{ matrix.shells.shell }} -c "rm helper/fixture/stat/no-permission"
- run: ${{ matrix.shells.shell }} ./shellspec

View File

@ -0,0 +1,27 @@
name: Windows GitBash
on: [push, pull_request]
env:
PATH: 'C:\Program Files\Git\bin;C:\windows\system32;C:\windows'
FORCE_COLOR: 1
defaults:
run:
shell: bash
jobs:
windows-gitbash:
runs-on: windows-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: sh}
- {shell: dash}
- {shell: bash}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: ${{ matrix.shells.shell }} ./shellspec --task fixture:stat:prepare
- run: ${{ matrix.shells.shell }} ./shellspec

View File

@ -0,0 +1,32 @@
name: Windows MSYS
on: [push, pull_request]
env:
PATH: 'C:\msys64\usr\bin;C:\Program Files\Git\bin;C:\windows\system32;C:\windows'
FORCE_COLOR: 1
defaults:
run:
shell: cmd
jobs:
windows-msys:
runs-on: windows-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
shells:
- {shell: sh, package: sh}
- {shell: dash, package: dash}
- {shell: bash, package: bash}
- {shell: busybox ash, package: busybox}
- {shell: mksh, package: mksh}
- {shell: zsh, package: zsh}
fail-fast: false
steps:
- uses: actions/checkout@v2
- run: pacman.exe -Syu --noprogressbar --noconfirm
- run: pacman.exe -S --noprogressbar --noconfirm ${{ matrix.shells.package }}
- run: ${{ matrix.shells.shell }} ./shellspec --task fixture:stat:prepare
- run: ${{ matrix.shells.shell }} ./shellspec

View File

@ -0,0 +1,7 @@
.env
.shellspec-local
.shellspec-quick.log
/*.tar.gz
report
coverage*
ttyrecord

View File

@ -0,0 +1 @@
v0.7.1

View File

@ -0,0 +1,11 @@
--require spec_helper
--require ksh_workaround
--helperdir helper
--sandbox
--skip-message moderate
--fail-no-examples
--env-from helper/env.sh
--kcov-options "--include-pattern="
--kcov-options "--include-path=./shellspec,./install.sh,./lib,./libexec"
--kcov-options "--exclude-pattern=/.,_generated.sh"
--hide-deprecations

View File

@ -0,0 +1,67 @@
language: shell
os: linux
dist: bionic
env:
global:
- PATH=${PATH}:${HOME}/kcov/bin
- CC_TEST_REPORTER_URL=https://codeclimate.com/downloads/test-reporter/test-reporter-latest-darwin-amd64
- CC_TEST_REPORTER_ID=67e0534ee993a6bbd10ae205ef4e807956c01564564ed64738932db123b1b87a
addons:
apt:
update: true
jobs:
include:
# - os: linux # Ubuntu 12.04
# dist: precise
# env: PACKAGES="dash bash zsh ksh mksh yash busybox"
# - os: linux # Ubuntu 14.04
# dist: trusty
# env: PACKAGES="dash bash zsh ksh mksh yash posh busybox"
# - os: linux # Ubuntu 16.04
# dist: xenial
# env: PACKAGES="dash bash zsh ksh mksh yash posh busybox"
# - os: linux # Ubuntu 18.04
# dist: bionic
# env: PACKAGES="dash bash zsh ksh mksh yash posh busybox"
# - os: linux # Ubuntu 20.04
# dist: focal
# env: PACKAGES="dash bash zsh ksh mksh yash posh busybox"
# - os: osx # macOS Homebrew
# osx_image: xcode11.5
# env: FORMULAS="dash bash zsh ksh mksh yash"
- os: osx # macOS 10.10 Yosemite 2014-06
osx_image: xcode6.4
env: SHELLS="sh bash zsh ksh"
- os: osx # macOS 10.11 El Capitan 2015-06
osx_image: xcode8
env: SHELLS="sh bash zsh ksh"
- os: osx # macOS 10.12 Sierra 2016-06
osx_image: xcode9.2
env: SHELLS="sh bash zsh ksh"
- os: osx # macOS 10.13 High Sierra 2017-06
osx_image: xcode10.1
env: SHELLS="sh bash zsh ksh"
- os: osx # macOS 10.14 Mojave 2018-06
osx_image: xcode10.2
env: SHELLS="sh bash zsh ksh"
# - os: osx # macOS 10.15.4 Catalina 2020-03
# osx_image: xcode11.5
# env: SHELLS="sh bash zsh ksh"
- os: osx # coverage
osx_image: xcode12.2
env: FORMULAS="bash kcov" COVERAGE=1 CODECOV=1 CC=1
before_install:
- if [ "$PACKAGES" ]; then sudo apt-get install -y $PACKAGES; fi
- if [ "$FORMULAS" ]; then brew update; brew install $FORMULAS; fi
- if [ "$CC" ]; then curl -sSL $CC_TEST_REPORTER_URL > ./cc-test-reporter; fi
- if [ "$CC" ]; then chmod +x ./cc-test-reporter; fi
- if [ "$CC" ]; then ./cc-test-reporter before-build; fi
before_script:
- ./shellspec --shell sh --task fixture:stat:prepare
script:
- if [ ! "$COVERAGE" ]; then contrib/all.sh contrib/various_test.sh; fi
- if [ "$COVERAGE" ]; then ./shellspec --shell bash --kcov --kcov-options "--coveralls-id=${TRAVIS_JOB_ID}"; fi
after_success:
- if [ "$CODECOV" ]; then bash <(curl -s https://codecov.io/bash) -s coverage; fi
- if [ "$CC" ]; then ./cc-test-reporter format-coverage coverage/cobertura.xml -t cobertura; fi
- if [ "$CC" ]; then ./cc-test-reporter upload-coverage; fi

View File

@ -0,0 +1,576 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.28.1] - 2021-01-11
### Changed
- Improved documentation. Thanks to Yohei Kawahara (#173).
### Fixed
- bash: Fixed an unexpected error message in self-executable specfile.
- Fixed a bug that test command cannot redefine when using "when run source"
- Fixed a bug when specified relative tmp directory path.
- Fixed option parsing
## [0.28.0] - 2021-01-05
### Added
- Added `BeforeEach` / `AfterEach` as synonym for `Before` / `After`.
- Added `FORCE_COLOR` environment variable.
- Added `--tmpdir` option.
- Added `--execdir` option to specify the directory for specfile execution.
- Added `-c` (`--chdir`) and `-C` (`--directory`) options to change directory at startup.
- Added path syntax (`*/` and `**/`) and `-L` (`--dereference`) option to match recursive directories.
- Added `--helperdir` to specify the location of `spec_helper` etc.
- Added the environment variable `SHELLSPEC_HELPERDIR` to indicate the location of `spec_helper` etc.
- Added `--reportdir` and `--covdir` options.
- Added `-O` (`--option`) option.
- Added `-I` (`--load-path`) option.
- Added `<module>_precheck` callback and some helper functions to `spec_helper` for pre-checking.
- Added `<module>_loaded` callback to be called after `spec_helper` loading.
### Changed
- Replaced with a new option parser [getoptions](https://github.com/ko1nksm/getoptions) which supports the standard option syntax.
- Replaced `--keep-tempdir` with `--keep-tmpdir`.
- Specifying a file in the shellspec's argument is now ignored if it does not match `--pattern`.
- Filled in system-out and system-err in junit xml.
- Allowed run the tests from any subdirectory.
- The `-D` option has been deprecated (Replace with the `--default-path` option).
- The environment variable `SHELLSPEC_SPECDIR` has been deprecated since there is not always a single directory for specfiles.
- Rename `.shellspec-profiler.log` to `profiler.log` for profiler log.
- Accept `banner.md` as a banner file
- `Include` and `import` (`shellspec_import`) can now pass arguments.
- The delimiter of the environment variable `SHELLSPEC_REQUIRES` has been changed from `:` to space.
- Improved documentation. Thanks to ldicarlo (#117, #119), Antoni Marcinek (#120, #139), Stuart R. Jefferys (#155), Leon Stafford (#159).
### Removed
- Removed `--kcov-common-options` option.
### Fixed
- bash 4.1 - 4.3: Fixed a bug that `run script` could not get the exit status.
- zsh < 4.2.0: Fixed a bug when extendedglob is enabled.
- Fixed possibility of I/O error in satisfy matcher (GitHub Actions only?).
- Fixed a bug in which zsh on macOS occasionally exits with exit code 147 (SIGCONT).
- Fixed several bugs related to the Windows path for busybox-w32.
- Fixed a bug when using metacharacters for tags.
## [0.27.2] - 2020-10-28
### Fixed
- Fixed a bug that didn't cause an error if there are fixed examples.
## [0.27.1] - 2020-09-30
### Removed
- Drop support for posh 0.8.5 due to signal handling broken.
### Fixed
- Fixed gray color.
- Fixed broken `--warning-as-failure`.
- Fixed a bug that can not CTRL-C with posh.
## [0.27.0] - 2020-09-25
### Added
- **Added `be exported` and `be readonly` matchers.**
- Added `%printf` and `%sleep` directives.
### Changed
- **Improved TAP formatter.**
- Supports `TODO` and `SKIP` directives.
- Use `Bail out!` on error.
- Added error details.
- `BeforeAll` / `AfterAll`: Avoid crashes due to hook errors.
- `Before` / `After`: Improved hook error handling.
- `BeforeCall` / `AfterCall`, `BeforeRun` / `AfterRun`: Minor changes.
### Removed
- Drop support for dash 0.5.3 due to unstable bug.
- Drop support for busybox < 1.20.0 due to unstable bug.
### Fixed
- Fixed a bug that "Parameter is not set" error in word modifier.
- Fixed a bug that satisfy matcher succeed even syntax error.
- Fixed a bug that can not CTRL-C with parallel execution on zsh.
- shellspec-syntax-check.sh: Some minor bug fixes.
## [0.26.1] - 2020-07-13
### Added
- **Added `--docker` option.** (EXPERIMENTAL)
## [0.26.0] - 2020-07-12
### Added
- **Added `Mock` helper (command-based mock).**
- **Added `%preserve` directive.**
- **Added `--sandbox`, `--sandbox-path` option.**
- Added `--path` option.
### Fixed
- Workaround when the Windows version of `sort.exe` is executed.
## [0.25.0] - 2020-06-21
### Added
- **Coverage support for zsh and ksh.** (#62)
- Respect `NO_COLOR` environment variable.
- Support [busybox-w32](https://frippery.org/busybox/) ash for windows.
- **Added `Assert` expectation to assert side effects of system environment.**
- Added `Dump` helper - dump stdout, stderr and status for debugging.
- Added `line` and `word` subject. (`of stdout (output)` can be omitted now)
- Added `--log-file` option to specify log file for `%logger` and trace.
- **Implement `--xtrace` (`--xtrace-only`) feature.**
### Changed
- Upgrade to alpine 3.12 for docker image and officially release `shellspec/kcov` docker image.
- Separate a file descriptor for reporting and stdout to able to use `echo` in specfile.
- Minor specification change of `result` modifier and `satisfy` matcher.
- `-r` option is now a short option for `--repair`, not `--require`.
- Use [debian/eol(https://hub.docker.com/r/debian/eol/) docker images for old debian tests.
### Fixed
- Before/After hooks should not consume stdin data (#82)
## [0.24.3] - 2020-06-06
### Fixed
- Fixes `BeforeAll` / `AfterAll` to share states
## [0.24.2] - 2020-05-27
### Fixed
- Fixed a bug "SHELLSPEC_GROUP_ID: unbound variable"
- Fixes when ran by "bash shellcpec" and "ksh shellspec"
## [0.24.1] - 2020-05-22
### Fixed
- Fixed broken `Todo`.
- Fixed a bug that caused an error when "--kcov" was specified and /dev/tty no be writable. (#67 Alexander Reitzel)
- Fixed a bug when enabled extendedglob for zsh.
## [0.24.0] - 2020-05-11
### Added
- Add `BeforeAll` and `AfterAll`. (#7)
- Expand parameter within Data helper. (#57)
- Add test for [GWSH shell](https://github.com/hvdijk/gwsh).
- Add manual test for OpenBSD ksh on OpenBSD 6.6.
- Add manual test for NetBSD sh on NetBSD 9.0.
### Removed
- Removed `match` matcher. Use `match pattern` matcher instead.
- Remove tests for unstable old shells (Bus Error, Bad address, Memory fault, etc).
- CI test for pdksh 5.2.14 on FreeBSD.
- Docker test for pdksh 5.2.14 on Debian 2.2r7.
- Docker test for ksh 93q on Debian 3.1r8.
- Remove tests for FreeBSD 13.0-current (Unstable due to work in progress).
## [0.23.0] - 2020-04-02
### Added
- New **quick execution** and related options (`--quick`, `--repair`, `--next`).
- New **failures formatter**.
- Support **self-executable specfile**. (#40)
- Add `--pending-message` and `--quiet` option.
- Add short options for focus and filters.
- Add `-w` short options for `--warning-as-failure`.
- Add `--boost` (joke) option.
- Reporter: Displays comments of 'temporary skip' and 'temporary pending'.
- Support windows line endings. (#45)
- Syntax check for missing `End` of parameters.
- shellspec --init: generate ignore file for cvs.
### Changed
- Run the specfile specified by arguments even not end with `_spec.sh`.
- Formatter: Change fixed color.
- Formatter: Change mark for fixed and pending of progress formatter.
### Fixed
- Fixed `--pattern` option to avoid syntax error.
- Return exit status code on the specfile properly.
- Fixed a bug that `start with` and `end with` match glob pattern.
- Formatter: Fixed not display correctly of documentation formatter when description is empty.
- Fixed an issue installer.sh fails in some environments. (#43)
### Deprecated
- Use `--require` long option instead of `-r` short option.
## [0.22.0] - 2020-02-22
### Added
- Improve kcov version detection.
- Colored TAP formatter. (#34 Kylie McClain)
- Added `--show-deprecations` and `--hide-deprecations` options.
### Changed
- New **kcov integration**.
- Do not create translated specfile in project directory.
- Suppress unnecessary coverage measurement to improve testing speed.
- Added `--coverage-report-info` to add extra information to coverage report.
- make install compatible with BSD and macOS.
- Suppress unnecessary before/after hooks of skipped examples.
- install.sh: Install to under $HOME/.local by default
- Use $HOME/.config if not defined XDG_CONFIG_HOME
### Deprecated
- `--kcov-common-options` is deprecated, merge into `--kcov-options`.
- Deprecates the `match` matcher due to cause many syntax errors. Use `match pattern` matcher instead.
### Fixed
- Fixed broken test in docker on Linux.
- Fixed `--example` option to avoid syntax error.
- Append to LOGFILE instead of overwriting.
## [0.21.0] - 2020-01-30
### Added
- Provide **docker images**.
- Provide **distribution archive**.
- Available ArchLinux package. (#15 Damien Flament)
### Changed
- docs: Improve English quality. (#16 Damien Flament)
## [0.20.2] - 2019-08-24
### Fixed
- Fixed wrong SHELLSPEC_TMPBASE
- Fixed for bug that some shell can not call external command same name as builtin.
## [0.20.1] - 2019-08-19
### Fixed
- Fixed for solaris.
## [0.20.0] - 2019-08-18
### Added
- Add **parameterized example**. (`Parameters` helper)
- Add `Set` helper for set shell option
- Add `BeforeCall` / `AfterCall` helper.
- Add `BeforeRun` / `AfterRun` helper.
- Use `hexdump` if `od` does not exist.
### Changed
- Redesign `run` evaluation. [**major breaking change**]
- Change the behavior to close to the `run` of bats.
- New `run` evaluation allows the execution of functions and commands.
- Use `run command` to execute only the commands. (old `run` -> use `run command`)
- Merge `invoke` evaluation to `run` evaluation. (old `invoke` -> use `run`)
- Merge `execute` evaluation to `run` evaluation. (old `execute` -> use `run source`)
- Export %const values to the translation process
### Removed
- Drop support for posh 0.10.2 and similar versions as the handling of the shell flag is broken.
### Fixed
- Fixed bug for related with tag.
- Fixed bug where coverage might not work on macOS.
## [0.19.1] - 2019-07-23
### Added
- Support install via make, bpkg, basher
## [0.19.0] - 2019-07-22
### Added
- Add installer (It has not been officially released, but you can used it already).
- Testing for **single script file** (Add `execute` evaluation, `Intercept` and `__SOURCED__` variable).
- Add `--keep-tempdir` option.
- Add `Data < <FILE>` syntax.
### Removed
- Drop support for busybox 1.1.3 and similar versions as it can not redefine builtin commands.
- Drop support for ash 0.3.8 and similar versions as it can not use retrun in sourced script.
- Remove `call`/`invoke` `<STRING>` syntax.
## [0.18.0] - 2019-07-09
### Added
- **Profiler feature** (`--profile`)
- Time attribute for JUnit XML.
## [0.17.0] - 2019-07-06
### Added
- **Coverage reporting**.
- Add `--fail-low-coverage` option.
## [0.16.0] - 2019-07-03
### Added
- **Coverage** support (kcov integration)
- Add **JUnit formatter** and **report generator**.
- Add `--warning-as-failure` option.
- Support [Unofficial Bash Strict Mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/).
- Support for [Schily Bourne Shell](http://schilytools.sourceforge.net/bosh.html) (`bosh`, `pbosh`).
### Changed
- Change `--skip-message none` to `--skip-message verbose`.
### Removed
- Remove `--warnings` option.
## [0.15.0] - 2019-05-26
### Added
- Add `be empty directory` (alias: `be empty dir`) matcher.
### Changed
- Rename `be empty` matcher to `be empty file` matcher. [breaking change]
### Fixed
- Fixed bug that `be empty` (renamed to `be empty file`) matcher matches not exists file, etc.
- Ensure call & invoke start with errno zero (#2 Rowan Thorpe)
## [0.14.0] - 2019-05-15
### Added
- Add `--random` option.
### Changed
- Improve `--example`, `--tag` option.
## [0.13.1] - 2019-05-13
### Fixed
- Fixed bug when --dry-run mode.
- Fixed documentation formatter.
## [0.13.0] - 2019-05-12
### Added
- Add `--list examples:id` option.
- Add `*_spec.sh:@ID` syntax the specify id with the filename.
- Add `--pattern`, `--example`, `--tag`, `--default-path` filter option.
### Changed
- Change `Logger` Helper to `%logger` directive.
- Merge `--list-specfiles`, `--list-examples` options to `--list` option.
- Redesign reporter to improve performance, maintainability.
## [0.12.0] - 2019-04-26
### Added
- Add `--list-specfiles`, `--list-examples` option.
- Add `--env-from` option.
- Add tests that for array if supported shells.
### Changed
- Change `--count` option output includes the number of specfiles.
- Change to the banner show only on shellspec-runner.
## [0.11.3] - 2019-04-24
### Fixed
- Fixed broken parallel executor.
## [0.11.2] - 2019-04-23
### Fixed
- Fixed bug that does not work with zsh 5.4.2.
## [0.11.1] - 2019-04-21
### Fixed
- Fixed ignored specified line number when parallel execution.
- Fixed documentation formatter when supplied multiple specfiles.
## [0.11.0] - 2019-04-20
### Added
- Run **the example by line number**. (`*_spec.sh:#`)
- Run **focused groups / examples**. (`fDescribe`, `fContext`, `fExample`, `fSpecify`, `fIt`)
- Add `--count` option for count the number of examples without running.
## [0.10.0] - 2019-04-17
### Added
- Support **parallel execution**. (`--jobs` option)
### Changed
- Separete syntax checker into tools.
- Improve syntax checker.
- Improve error handling.
- Improve ctrl-c handling.
### Removed
- Remove `Def` helper. (use `%putsn`, `%puts` directive instead)
## [0.9.0] - 2019-03-30
### Added
- Add `--syntax-check` option for syntax check of the specfile.
### Changed
- Change timing of loading external script by 'Include'. [breaking change]
### Fixed
- Fix for translation speed slowdown.
### Removed
- Remove shorthand of the variable subject.
## [0.8.0] - 2019-03-26
### Added
- Add `Constant definition`.
- Add `Data` helper, `Embedded text`.
- Add `Def` helper.
- Add `Logger` helper.
- Add `result` modifier.
- Add `Include` helper.
- Add shorthand for `function` subject and `variable` subject.
- Add failed message for `Before`/`After` each hook.
### Changed
- Change behavior of `line` and `lines` modifier to like "grep -c" not "wc -l".
- Change `function` subject to alias for `value` subject.
- Improve handling unexpected errors.
- Improve examples.
### Removed
- Remove `It` statement and change `It` is alias of `Example` now.
- Remove `Set` / `Unset` helper.
- Remove `Debug` helper.
- Remove `string` subject.
- Remove `exit status` subject. (use `status` subject)
## [0.7.0] - 2019-03-08
### Added
- Added `lines` modifier.
## [0.6.0] - 2019-02-19
### Added
- Added `match` matcher.
## [0.5.0] - 2019-02-06
### Added
- Initial public release.
[Unreleased]: https://github.com/shellspec/shellspec/compare/0.28.1...HEAD
[0.28.1]: https://github.com/shellspec/shellspec/compare/0.28.0...0.28.1
[0.28.0]: https://github.com/shellspec/shellspec/compare/0.27.2...0.28.0
[0.27.2]: https://github.com/shellspec/shellspec/compare/0.27.1...0.27.2
[0.27.1]: https://github.com/shellspec/shellspec/compare/0.27.0...0.27.1
[0.27.0]: https://github.com/shellspec/shellspec/compare/0.26.1...0.27.0
[0.26.1]: https://github.com/shellspec/shellspec/compare/0.26.0...0.26.1
[0.26.0]: https://github.com/shellspec/shellspec/compare/0.25.0...0.26.0
[0.25.0]: https://github.com/shellspec/shellspec/compare/0.24.3...0.25.0
[0.24.3]: https://github.com/shellspec/shellspec/compare/0.24.2...0.24.3
[0.24.2]: https://github.com/shellspec/shellspec/compare/0.24.1...0.24.2
[0.24.1]: https://github.com/shellspec/shellspec/compare/0.24.0...0.24.1
[0.24.0]: https://github.com/shellspec/shellspec/compare/0.23.0...0.24.0
[0.23.0]: https://github.com/shellspec/shellspec/compare/0.22.0...0.23.0
[0.22.0]: https://github.com/shellspec/shellspec/compare/0.21.0...0.22.0
[0.21.0]: https://github.com/shellspec/shellspec/compare/0.20.2...0.21.0
[0.20.2]: https://github.com/shellspec/shellspec/compare/0.20.1...0.20.2
[0.20.1]: https://github.com/shellspec/shellspec/compare/0.20.0...0.20.1
[0.20.0]: https://github.com/shellspec/shellspec/compare/0.19.0...0.20.0
[0.19.1]: https://github.com/shellspec/shellspec/compare/0.19.0...0.19.1
[0.19.0]: https://github.com/shellspec/shellspec/compare/0.18.0...0.19.0
[0.18.0]: https://github.com/shellspec/shellspec/compare/0.17.0...0.18.0
[0.17.0]: https://github.com/shellspec/shellspec/compare/0.16.0...0.17.0
[0.16.0]: https://github.com/shellspec/shellspec/compare/0.15.0...0.16.0
[0.15.0]: https://github.com/shellspec/shellspec/compare/0.14.0...0.15.0
[0.14.0]: https://github.com/shellspec/shellspec/compare/0.13.1...0.14.0
[0.13.1]: https://github.com/shellspec/shellspec/compare/0.13.0...0.13.1
[0.13.0]: https://github.com/shellspec/shellspec/compare/0.12.0...0.13.0
[0.12.0]: https://github.com/shellspec/shellspec/compare/0.11.3...0.12.0
[0.11.3]: https://github.com/shellspec/shellspec/compare/0.11.2...0.11.3
[0.11.2]: https://github.com/shellspec/shellspec/compare/0.11.1...0.11.2
[0.11.1]: https://github.com/shellspec/shellspec/compare/0.11.0...0.11.1
[0.11.0]: https://github.com/shellspec/shellspec/compare/0.10.0...0.11.0
[0.10.0]: https://github.com/shellspec/shellspec/compare/0.9.0...0.10.0
[0.9.0]: https://github.com/shellspec/shellspec/compare/0.8.0...0.9.0
[0.8.0]: https://github.com/shellspec/shellspec/compare/0.7.0...0.8.0
[0.7.0]: https://github.com/shellspec/shellspec/compare/0.6.0...0.7.0
[0.6.0]: https://github.com/shellspec/shellspec/compare/0.5.0...0.6.0
[0.5.0]: https://github.com/shellspec/shellspec/commits/0.5.0

View File

@ -0,0 +1,16 @@
# CONTRIBUTING
## For developer
1. To understand the [architecture](docs/architecture.md)
2. About [reporter](docs/reporter.md)
3. About various [shells](docs/shells.md)
4. How to [test](docs/test.md)
### About specfile translation process
The specfile is a valid shell script, but a translation process is performed to implement the scope,
line number etc. Each example group block and example block is translated to commands in a subshell.
Therefore changes inside those blocks do not affect the outside of the block. In other words it realizes
local variables and local functions in the specfile. This is very useful for describing a structured spec.
If you are interested in how to translate, use the `--translate` option.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Koichi Nakashima
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,66 @@
PREFIX ?= /usr/local
BINDIR := $(PREFIX)/bin
LIBDIR := $(PREFIX)/lib
GETOPTIONSCLI := getoptions-cli --indent=2 --shellcheck
OPTPARSERDIR := lib/libexec/optparser
.PHONY: coverage test dist build release
all: test check
dist: LICENSE shellspec lib libexec
tar -czf shellspec-dist.tar.gz $^ --transform 's,^,shellspec/,'
install:
install -d "$(BINDIR)" "$(LIBDIR)"
install stub/shellspec "$(BINDIR)/shellspec"
find lib libexec -type d -exec install -d "$(LIBDIR)/shellspec/{}" \;
find LICENSE lib -type f -exec install -m 644 {} "$(LIBDIR)/shellspec/{}" \;
find shellspec libexec -type f -exec install {} "$(LIBDIR)/shellspec/{}" \;
uninstall:
rm -rf "$(BINDIR)/shellspec" "$(LIBDIR)/shellspec"
package:
contrib/make_package_json.sh > package.json
optparser:
@printf "getoptions: "
@$(GETOPTIONSCLI) --version
$(GETOPTIONSCLI) $(OPTPARSERDIR)/parser_definition.sh \
optparser_parse SHELLSPEC optparser_error \
> $(OPTPARSERDIR)/parser_definition_generated.sh
demo:
ttyrec -e "ghostplay contrib/demo.sh"
seq2gif -l 5000 -h 32 -w 139 -p win -i ttyrecord -o docs/demo.gif
gifsicle -i docs/demo.gif -O3 -o docs/demo.gif
coverage:
contrib/coverage.sh --pull
check:
contrib/check.sh --pull
metrics:
contrib/metrics.sh
build:
contrib/build.sh .dockerhub/Dockerfile shellspec
contrib/build.sh .dockerhub/Dockerfile shellspec kcov
contrib/build.sh .dockerhub/Dockerfile.debian shellspec-debian
contrib/build.sh .dockerhub/Dockerfile.debian shellspec-debian kcov
contrib/build.sh .dockerhub/Dockerfile.scratch shellspec-scratch
test:
./shellspec
test_all:
contrib/all.sh shellspec
test_in_docker:
contrib/test_in_docker.sh --pull dockerfiles/* -- shellspec -j 2
release:
contrib/release.sh

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
../shellspec

View File

@ -0,0 +1,67 @@
#!/bin/sh
# Run in all shells
# This script is for development purposes.
# It provide as is, do not any support.
# It may change without notice.
# Example of use
# contrib/all.sh
# contrib/all.sh shellspec example/addition_spec.sh
# contrib/all.sh -c 'echo ok'
set -eu
: "${TARGET:=sh,ash,dash,bash,zsh,pdksh,ksh,ksh93,mksh,oksh,yash,posh,busybox ash}"
readlinkf() {
[ ${1:+x} ] || return 1; p=$1; until [ "${p%/}" = "$p" ]; do p=${p%/}; done
[ -e "$p" ] && p=$1; [ -d "$1" ] && p=$p/; set 10 "$PWD" "${OLDPWD:-}"
CDPATH="" cd -L "$2" && while [ "$1" -gt 0 ]; do set "$1" "$2" "$3" "${p%/*}"
[ "$p" = "$4" ] || { CDPATH="" cd -L "${4:-/}" || break; p=${p##*/}; }
[ ! -L "$p" ] && p=${PWD%/}${p:+/}$p && set "$@" "${p:-/}" && break
set $(($1-1)) "$2" "$3" "$p"; p=$(ls -dl "$p") || break; p=${p#*" $4 -> "}
done 2>/dev/null; cd -L "$2" && OLDPWD=$3 && [ ${5+x} ] && printf '%s\n' "$5"
}
each_shells() {
callback=$1 IFS=,
shift
for shell in $TARGET; do
shell_path='' real_path=''
# shellcheck disable=SC2230
shell_path=$(which "${shell%% *}" 2>/dev/null) || shell_path=''
[ -L "${shell_path%% *}" ] && real_path=$(readlinkf "${shell_path%% *}")
$callback "$@"
done
}
info() {
info=$shell_path
[ "$info" ] && info="$info${real_path:+ -> }$real_path"
printf '%8s : %s\n' "${shell%% *}" "${info:-----}"
}
run() {
if [ "$shell_path" ]; then
echo "--------------------------------------------------"
echo "$shell : $shell_path${real_path:+ -> }$real_path"
echo "$shell" "$@"
echo "--------------------------------------------------"
eval "SH=\$shell $shell \"\$@\""
else
echo "--------------------------------------------------"
echo "$shell : Skip, shell not found"
echo "--------------------------------------------------"
echo
fi
}
uname -a
echo "=================================================="
( each_shells info )
echo "=================================================="
if [ $# -gt 0 ]; then
( each_shells run "$@" )
fi

View File

@ -0,0 +1,409 @@
#!/bin/sh
# shellcheck disable=SC2004,SC2015,SC2016,SC2034,SC2086,SC2091,SC2123,SC2181,SC2194
# Bugs and compatibility check for shell
# This script is for development purposes.
# It provide as is, do not any support.
# It may change without notice.
# Example of use
# contrib/bugs.sh
# bash contrib/bugs.sh
# contrib/test_in_docker.sh [Dockerfile] -- bash contrib/bugs.sh
affect() { echo "[affect] $title"; }
no_problem() { echo "[------] $title"; }
skip() { echo "[skip ] $title [skip reason: $1]"; }
if { file=$(mktemp -t tmp.XXXXXX); } 2>/dev/null; then
maketemp() { mktemp -t tmp.XXXXXX; }
rm "$file"
else
maketemp() { mktemp tmp.XXXXXX; }
fi
(
title="01: set -e option is reset (posh)"
set -eu
foo() { eval 'return 0'; }
foo
case $- in
*e*) no_problem ;;
*) affect ;;
esac
)
(
title="02: double quoted string treated as a pattern (posh)"
string="abc[d]"
pattern="c[d]"
case $string in
*"$pattern"*) no_problem ;;
*) affect ;;
esac
)
(
title="03: @: parameter not set (posh, ksh <= around 93s)"
if (set -u; : "$@") 2>/dev/null; then no_problem; else affect; fi
)
return_true() { false; }
(
title="04: function not overrite (ksh)"
return_true() { true; }
if return_true; then no_problem; else affect; fi
)
(
title="05: exit not return exit code (zsh <= around 4.2.5)"
foo() { (exit 123) &&:; }; foo
if [ $? -eq 123 ]; then no_problem; else affect; fi
)
(
title="06: abort if unset not the defined variable (bash <= around 2.05a, zsh <= around 4.2.5, ksh <= around 93s)"
ok=$(
set -e
unset not_defined_variable
echo 1
)
if [ "$ok" ]; then no_problem; else affect; fi
)
(
title="07: limited arithmetic expansion (dash <= around 0.5.4, old ash)"
i=0
ok=$(eval 'i=$((i+1))' 2>/dev/null; echo 1)
if [ "$ok" ]; then no_problem; else affect; fi
)
(
title="08: Unsupported trap (posh: around 0.8.5)"
if (trap '' INT) 2>/dev/null; then no_problem; else affect; fi
)
(
title="09: different split by IFS (zsh, posh <= around 0.6.13)"
[ "${ZSH_VERSION:-}" ] && emulate -R sh
string="1,2,"
IFS=','
set -- $string
if [ $# -eq 2 ]; then no_problem; else affect; fi
)
(
title="10: variable reference in the same line (dash <= around 0.5.5)"
i=1
i=$(($i+1)) j=$i
if [ "$j" -eq 2 ]; then no_problem; else affect; fi
)
(
title="11: command time -p output LF at first (solaris)"
if (command time echo) >/dev/null 2>/dev/null; then
command time -p printf '' 2>&1 | {
IFS= read -r line
if [ "$line" ]; then no_problem; else affect; fi
}
else
skip 'time command not found'
fi
)
(
title="12: cat not ignore set -e (zsh = around 5.4.2)"
(
set -e
nagative() { false; }
if false; then :; else nagative && : || :; fi
)
[ $? -eq 0 ] && no_problem || affect
)
(
title="13: many unset cause a Segmentation fault (mksh = around 39)"
(
i=0
while [ $i -lt 30000 ]; do
unset v ||:
i=$(($i+1))
done
) 2>/dev/null
[ $? -eq 0 ] && no_problem || affect
)
(
title="14: here document does not expand parameters (ash = around 0.3.8)"
set -- value
foo() { cat; }
result=$(foo<<HERE
$1
HERE
)
[ "$result" = value ] && no_problem || affect
)
(
title="15: glob not working (posh <= around 0.12.6)"
files=$(echo "/"*)
[ "$files" != "/*" ] && no_problem || affect
)
(
title="16: 'command' can not prevent error (bash = 2.03)"
foo() {
set -e
command false &&:
echo ok
}
ret=$(foo)
[ "$ret" = "ok" ] && no_problem || affect
)
(
title="17: can not return within eval (posh = around 2.36)"
foo() {
eval 'return 0'
return 1
}
foo &&:
[ $? -eq 0 ] && no_problem || affect
)
(
title="18: do not glob with set -u (posh = around 0.10.2)"
set -u
[ "$(echo /*)" != "/*" ]
[ $? -eq 0 ] && no_problem || affect
)
(
title='19: can not get $POSH_VERSION (posh = around 0.8.5)'
if [ "${POSH_VERSION:-}" ]; then
[ "$POSH_VERSION" != "POSH_VERSION" ]
[ $? -eq 0 ] && no_problem || affect
else
skip 'this shell is not posh'
fi
)
(
title='20: do not remove leading space when read with one arguments (yash = around 2.36)'
IFS=' '
read -r line<<HERE
line
HERE
[ "$line" = "line" ]
[ $? -eq 0 ] && no_problem || affect
)
(
title='21: cat not ignore set -e with eval (pdksh = around 5.2.14 on debian 3.0)'
foo() {
set -e
bar() {
eval "false"
echo ok
}
bar ||:
}
ret=$(foo)
[ "$ret" = "ok" ] && no_problem || affect
)
(
title='22: internal error: j_async: bad nzombie (0) (posh = around 0.6.13)'
file=$(maketemp)
(
sleep 0 &
wait $!
) 2>"$file"
ret=$(cat "$file")
rm "$file"
case $ret in
*bad\ nzombie*) affect ;;
*) no_problem ;;
esac
)
(
title='23: can not read after reading null character (yash = around 2.46)'
file=$(maketemp)
printf 'foo\0bar' > "$file"
IFS= read -r ret < "$file"
IFS= read -r ret <<HERE
AAA
HERE
rm "$file"
[ "$ret" ] && no_problem || affect
)
(
title='24: variable expansion not working with the positional parameter (posh, pdksh)'
set -- 'foobar' 'bar'
[ "${1%$2}" = "foo" ] && no_problem || affect
)
(
title='25: printf can not handle octal numbers correctly (old pdksh, zsh <= around 4.0.4)'
# zsh 4.2.5: ch=\101
# pdksh on debian 3.0: \1: invalid escape
ch=$(printf "\101" 2>/dev/null) ||:
[ "$ch" = "A" ] && no_problem || affect
)
(
title='26: Segmentation fault in eval (ksh 93q, 93r)'
(
(
str="" i=0
step1() {
if (eval ':' 2>/dev/null); then :; fi
return 0
}
step2() { set --; eval "$(:)"; }
step3() { eval "str=\${str#}"; }
step1
step2
while [ $i -lt 100 ]; do
step3 # 10 Segmentation fault "$@
i=$(($i+1))
done
) &
wait $! # wait is not related this bug, it just prevent fail.
) 2>/dev/null
[ $? = 0 ] && no_problem || affect
)
(
title='27: set -C not working (posh = around 0.10.2)'
file=$(maketemp)
(set -C; : > "$file") 2>/dev/null &&:
ret=$?
rm "$file"
[ $ret -ne 0 ] && no_problem || affect
)
(
title='28: variable expansion not working within the double qoute (posh >= 0.8.5)'
a='foobar' b='bar'
[ "${a%"$b"*}" = "foo" ] && no_problem || affect
)
(
title='29: case will be aborted when exit status is error (mksh <= 35.2, pdksh 5.2.14 on debian 2.2)'
foo() { set -e; case 1 in (*) false &&: ;; esac; echo ok; }
ret=$(foo)
[ "$ret" = "ok" ] && no_problem || affect
)
(
title='30: eval interpret invalid -- option (posh = around 0.3.14)'
ret=$(eval echo -- foo)
[ "$ret" = "-- foo" ] && no_problem || affect
)
(
title='31: ${v%%=*} not working (zsh <= around 4.3.17)'
kv="key=value"
(ret="${kv%%=*}"; [ "$ret" = "key" ]) 2>/dev/null &&:
[ $? = 0 ] && no_problem || affect
)
(
title='32: last readonly always display "is read only" in subshell (ksh 93q, 93r)'
(
# readonly A # => not display
# readonly B # => display "B is read only"
readonly A; (readonly B) 2>/dev/null
[ $? = 0 ] && no_problem || affect
) &&:
) 2>/dev/null
(
title='33: fails when use readonly with value (pdksh 5.2.14 on debian 2.2)'
message=$( ( readonly value=123 ) 2>&1 )
[ "$message" ] && affect || no_problem
)
(
title='34: built-in commands can not redefine (busybox = around 1.1.3)'
whoami() { echo nobody; }
[ "$(whoami)" = nobody ] && no_problem || affect
)
(
title='35: exit status is not 127 when command not found (zsh <= around 4.0.4)'
no-such-command 2>/dev/null &&:
[ $? = 127 ] && no_problem || affect
)
(
title='36: exit status is not 127 when command not found and PATH='' (bash <= 4.3)'
no-such-command 2>/dev/null &&:
if [ $? = 1 ]; then
skip 'affected #35'
else
set -e
PATH=''
no-such-command 2>/dev/null &&:
[ $? = 127 ] && no_problem || affect
fi
)
(
title='37: wrong parse command substitution (bosh/posh = 20181030,20190311)'
$(
cat << HERE
HERE
)
ret=$( printf a; printf b )
[ "$ret" = "ab" ] && no_problem || affect
)
(
title='38: Freeze in pipe processing (bosh/posh = 20181030,20190311)'
printf '1\n2\n' | cat | while read -r line; do
# /usr/bin/printf '%s' "$line" # freeze
printf '%s' "$line" # not freeze
done > /dev/null
[ -e /usr/bin/printf ] && printf=/usr/bin/printf || printf=/bin/printf
file=$(maketemp)
# Code for detection. Output one line only.
{ echo 1; echo 2; } | {
while IFS= read -r line; do
echo "$line"
done | cat | while read -r line; do
$printf '%s' "$line"
done
} > $file
ret=$(cat "$file")
rm "$file"
[ "$ret" = "1" ] && affect || no_problem
)
(
title='39: can not set -e options with eval (pdksh, mksh, posh)'
set -e
eval "set +e"
case $- in
*e*) affect ;;
*) no_problem ;;
esac
)
(
title='40: can not return exit status from subshell (bash 4.1.5, 4.2)'
ret() { return $1; }
foo() { set -e; ret 123; }
set +e
(foo)
[ $? = 123 ] && no_problem || affect
)
echo Done

View File

@ -0,0 +1,66 @@
#!/bin/sh
set -eu
info() {
printf '\033[32m%s\033[0m\n' "$*"
}
if [ -e .env ]; then
default_compose_project_name=shellspec
while read -r line; do
case $line in (COMPOSE_PROJECT_NAME=*)
default_compose_project_name=''
esac
done < .env
if [ "$default_compose_project_name" ]; then
: "${COMPOSE_PROJECT_NAME:=$default_compose_project_name}"
export COMPOSE_PROJECT_NAME
fi
fi
target=${3:-}
export DOCKERFILE_BASE AUTHORS
DOCKERFILE_BASE="$(dirname "$1")/"
AUTHORS="$(git config user.name) <$(git config user.email)>"
# Emulate Docker Hub environment variables
export DOCKERFILE_PATH DOCKER_TAG IMAGE_NAME SOURCE_COMMIT
DOCKERFILE_PATH=$(basename "$1")
DOCKER_TAG=${3:-latest}
IMAGE_NAME=$2:$DOCKER_TAG
SOURCE_COMMIT=$(git rev-parse HEAD)
test="docker-compose${target:+.}$target.test.yml"
docker-compose -f ".dockerhub/$test" up --build --abort-on-container-exit
case $1 in
/*) DOCKERFILE_PATH=$1 ;;
*) DOCKERFILE_PATH=$PWD/$1 ;;
esac
cd .dockerhub
if [ "$target" ]; then
hooks/build --target "$target"
else
hooks/build
fi
size=$(docker inspect -f "{{.Size}}" "$IMAGE_NAME")
if [ "$size" -le $((1024 * 1024)) ]; then
size="$(echo "scale=2; $size / 1024" | bc) KB"
else
size="$(echo "scale=2; $size / 1024 / 1024" | bc) MB"
fi
labels=$(
#shellcheck disable=SC2016
format='{{range $k, $v := .Config.Labels}}{{printf "%s: %s\n" $k $v}}{{end}}'
#shellcheck disable=SC2005
echo "$(docker inspect -f "$format" "$IMAGE_NAME")"
)
info "============================================================"
info "Build succeeded: $IMAGE_NAME (size: $size)"
info "------------------------------------------------------------"
echo "$labels"
info "============================================================"

View File

@ -0,0 +1,165 @@
#!/bin/sh
# Detect builtin commands
# This script is for development purposes.
# It provide as is, do not any support.
# It may change without notice.
set -eu
PATH=":"
check() {
while read -r cmd && cmd=${cmd%% *}; do
if type "$cmd" >/dev/null 2>&1; then
if [ -x /usr/bin/printf ]; then
/usr/bin/printf "%s " "$cmd"
else
/bin/printf "%s " "$cmd"
fi
fi
done
echo
}
if ! type : >/dev/null 2>&1; then
# not implements type (posh)
type() {
case $1 in (. | exit | type) return 0; esac
$1
}
fi
check<<COMMANDS
. | Special Built-In
: | Special Built-In
[ | All Shell Built-In
alias | Almost Shell Built-In (not built-in: posh)
array |
autoload |
bg | Almost Shell Built-In (not built-in: posh)
bind |
bindkey |
break | Special Built-In
builtin |
bye |
caller |
cap |
cat | Include Posix Utilities
cd | All Shell Built-In
chdir |
clone |
command | All Shell Built-In
comparguments |
compcall |
compctl |
compdescribe |
compfiles |
compgroups |
compquote |
comptags |
comptry |
compvalues |
compgen |
complete |
compopt |
continue | Special Built-In
declare |
dirs |
disable |
disown |
echo | All Shell Built-In
echotc |
echoti |
emulate |
enable |
enum |
eval | Special Built-In
exec | Special Built-In
exit | Special Built-In
export | Special Built-In
false | All Shell Built-In
fc | Include Posix Utilities
fg | Almost Shell Built-In (not built-in: posh)
float |
functions |
getcap |
getconf | Include Posix Utilities
getln |
getops | Almost Shell Built-In (not built-in: busybox)
global |
hash | Include Posix Utilities
help |
hist |
history |
integer |
jobs | Almost Shell Built-In (not built-in: posh)
kill | Almost Shell Built-In (not built-in: old posh)
let |
limit |
local |
log |
logout |
mapfile |
mknod |
newgrp | Include Posix Utilities
noglob |
popd |
print |
printf | Almost Shell Built-In (not built-in: mksh, posh)
pushed |
pushln |
pwd | All Shell Built-In
r |
read | All Shell Built-In
readarray |
readonly | Special Built-In
realpath |
rehash |
rename |
return | Special Built-In
sched |
set | Special Built-In
setcap |
setopt |
shift | Special Built-In
shopt |
sleep | Include Posix Utilities
source |
stat |
suspend |
test | All Shell Built-In
time | Include Posix Utilities
times | Special Built-In
trap | Special Built-In
true | All Shell Built-In
ttyctl |
type | Almost Shell Built-In (not built-in: posh)
typeset |
ulimit | Almost Shell Built-In (not built-in: posh)
umask | All Shell Built-In
unalias | Almost Shell Built-In (not built-in: posh)
unfunction |
unhash |
unlimit |
unset | Special Built-In
unsetopt |
vared |
wait | All Shell Built-In
whence |
where |
which |
zcompile |
zformat |
zftp |
zle |
zmodload |
zparseopts |
zprof |
zpty |
zregexparse |
zsocket |
zstyle |
ztcp |
COMMANDS

View File

@ -0,0 +1,83 @@
#!/bin/sh
# Check shell script files
# This script is for development purposes.
# It provide as is, do not any support.
# It may change without notice.
# Example of use
# contrib/check.sh
set -eu
[ "${1:-}" = "--pull" ] && PULL=1 || PULL=""
sources() {
echo shellspec
echo install.sh
find lib libexec -name '*.sh'
}
helpers() {
find helper -name '*.sh'
}
specs() {
find spec -name '*.sh'
}
examples() {
find examples -name '*.sh'
}
count() {
printf '%7s: ' "$1"
shift
cat "$@" | wc -lc | {
read -r lines bytes
printf '%3s files, %5s lines, %3s KiB\n' $# "$lines" $((bytes / 1024))
}
}
echo ' # lines bytes name'
wc -lc $(sources; helpers; specs; examples) | nl | sed '$d'
echo
count source $(sources)
count helper $(helpers)
count spec $(specs)
count example $(examples)
count total $(sources; helpers; specs; examples)
echo
echo "Checking package.json..."
contrib/make_package_json.sh | diff -u package.json - &&:
package_json_status=$?
[ "$package_json_status" -eq 0 ] && echo "ok"
echo
if ! docker --version >/dev/null 2>&1; then
echo "You need docker to run shellcheck" >&2
exit 1
fi
echo "Checking scripts by shellcheck..."
tag="shellspec:shellcheck"
trap 'exit 1' INT
trap 'docker rmi "$tag" >/dev/null 2>&1' EXIT
# Do not use volume because can not be used on VolFs(lxfs) of WSL.
shellcheck_version=$(cat .shellcheck-version)
set -- -t "$tag" --build-arg "VERSION=$shellcheck_version"
[ "$PULL" ] && set -- "$@" --pull
docker build "$@" -f dockerfiles/.shellcheck .
docker run -i --rm "$tag" shellcheck --version
docker run -i --rm "$tag" shellcheck -C $(sources; helpers; specs; examples)
[ "$package_json_status" -ne 0 ] && exit "$package_json_status"
echo "ok"

View File

@ -0,0 +1,52 @@
#!/bin/sh
#ghostplay silent
# ttyrec -e "ghostplay contrib/demo.sh"
# seq2gif -l 5000 -h 32 -w 139 -p win -i ttyrecord -o demo.gif
# seq2gif -l 5000 -h 19 -w 83 -p win -i ttyrecord -o sns.gif
GP_HOSTNAME=ubuntu
highlight() {
command highlight -l -O xterm256 --syntax "$1"
}
#ghostplay end
shellspec
#ghostplay sleep 3
# Parallel execution
shellspec --jobs 4
#ghostplay sleep 3
cd contrib/demo
#ghostplay sleep 1
cat spec/demo_spec.sh | highlight sh
#ghostplay sleep 3
# It has one failure
shellspec
#ghostplay sleep 5
# Dry run with documentation formatter
shellspec --dry-run --format documentation
#ghostplay sleep 5
# Coverage and generate junit xml
shellspec --kcov --output junit
#ghostplay sleep 5
cat report/results_junit.xml | highlight xml
#ghostplay sleep 3
cd profile
# Profiler
shellspec --profile --format documentation

View File

@ -0,0 +1 @@
--require spec_helper

View File

@ -0,0 +1,15 @@
add() {
echo $(($1 * $2)) # bug: should be '+'
}
sub() {
echo $(($1 - $2))
}
mul() {
echo $(($1 * $2))
}
div() {
echo $(($1 / $2))
}

View File

@ -0,0 +1,2 @@
--require spec_helper
# --kcov-common-options "--path-strip-level=1 --include-path=. --include-pattern=.sh --exclude-pattern=/spec/,/coverage/,/report/"

View File

@ -0,0 +1,26 @@
Describe "Profiler example"
Example "1: sleep 0.1 (line 2)"
When call sleep 0.1
The status should be success
End
Example "2: sleep 0.4 (line 7)"
When call sleep 0.4
The status should be success
End
Example "3: sleep 0.3 (line 12)"
When call sleep 0.3
The status should be success
End
Example "4: sleep 0.2 (line 17)"
When call sleep 0.2
The status should be success
End
Example "5: sleep 0.5 (line 22)"
When call sleep 0.5
The status should be success
End
End

View File

@ -0,0 +1,14 @@
#shellcheck shell=sh
# set -eu
# shellspec_redefinable function_name
#
# shellspec_redefinable is workaround for ksh (Version AJM 93u+ 2012-08-01)
# ksh can not redefine existing function in some cases inside of sub shell.
# If you have trouble in redefine function on ksh, try using shellspec_redefinable.
shellspec_spec_helper_configure() {
# shellspec_import 'support/custom_matcher'
:
}

View File

@ -0,0 +1,17 @@
Describe 'example'
Describe 'bc command'
It 'performs addition'
Data '2 + 3'
When call bc
The output should eq 5
End
End
Describe 'add() function'
Include ./mylib.sh # add() function defined
It 'performs addition'
When call add 2 3
The output should eq 5
End
End
End

View File

@ -0,0 +1,10 @@
#shellcheck shell=sh
# set -eu
# shellspec_redefinable function_name
shellspec_spec_helper_configure() {
# shellspec_import 'support/custom_matcher'
:
}

View File

@ -0,0 +1,10 @@
FROM alpine
RUN apk add --no-cache gcc libc-dev
ADD https://github.com/ncopa/su-exec/archive/v0.2.tar.gz /
COPY ./src/ ./src
RUN gcc --static ./src/mksock.c -o /usr/local/bin/mksock \
&& gcc --static ./src/invokesh.c -o /usr/local/bin/invokesh \
&& cp ./src/fake-nc.sh /usr/local/bin/nc \
&& tar xzf v0.2.tar.gz \
&& gcc -static /su-exec-0.2/su-exec.c -o /usr/local/bin/su-exec \
&& chmod ug+s /usr/local/bin/su-exec

View File

@ -0,0 +1,9 @@
#!/bin/sh
for i in "$@"; do
shift
case $i in (-*) continue; esac
set -- "$@" "$i"
done
mksock "$@"

View File

@ -0,0 +1,33 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define max_size 256
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen("/etc/invokesh.conf", "r")) == NULL) {
return EXIT_FAILURE;
}
char bin[max_size];
if ( fgets(bin, max_size, fp) == NULL ) {
fclose(fp);
return EXIT_FAILURE;
}
char *newline;
if ((newline = strchr(bin, '\n')) != NULL) {
*newline = '\0';
}
fclose(fp);
argv[0] = bin;
execvp(bin, argv);
fprintf(stderr, "invokesh: %s: %s\n", bin, strerror(errno));
return errno;
}

View File

@ -0,0 +1,32 @@
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define UNIX_PATH_MAX 108
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_un addr;
if (argc < 2) {
return 1;
}
if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
return 1;
}
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, argv[1], UNIX_PATH_MAX);
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
return 1;
}
close(sock);
return 0;
}

View File

@ -0,0 +1,35 @@
#!/bin/sh
# Environment for installer test
# This script is for development purposes.
# It provide as is, do not any support.
# It may change without notice.
set -eu
[ $# -eq 0 ] && set -- general
case ${1:-} in ( general | make | bpkg | basher | brew) ;; (*)
cat <<'USAGE'
Usage: installer_test.sh [ general | make | bpkg | basher | brew]
USAGE
exit 0
esac
iid='' iidfile=$(mktemp -t shellspec.XXXXXXXX)
dockerfile="dockerfiles/.installer-test"
cleanup() {
[ -f "$iidfile" ] && rm "$iidfile"
[ "$iid" ] && docker rmi "$iid" > /dev/null
}
trap 'exit 1' INT
trap 'cleanup' EXIT
docker build --target="${1}_installer" -t "shellspec:${1}_installer" - < "$dockerfile"
sleep 5
docker build --target=test --build-arg "TYPE=$1" --iidfile "$iidfile" . -f "$dockerfile"
iid=$(cat "$iidfile")
shift
docker run -it --rm "$iid" "$@"

View File

@ -0,0 +1,25 @@
#!/bin/sh
version() {
./shellspec --version
}
files() {
echo "["
files="$(find bin lib libexec \( -type f -o -type l \) -exec echo " \"{}\"," \; | sort)"
echo "${files%,}"
echo " ]"
}
cat<<JSON
{
"name": "ShellSpec",
"version": "$(version)",
"description": "BDD style unit testing framework for POSIX compliant shell script",
"homepage": "https://shellspec.info",
"scripts": ["shellspec"],
"license": "MIT",
"files": $(files),
"install": "make install"
}
JSON

View File

@ -0,0 +1,17 @@
#!/bin/sh
# Measure metrics for shell scripts
# This script is for development purposes.
# It provide as is, do not any support.
# It may change without notice.
set -eu
sources() {
echo shellspec
echo install.sh
find lib libexec helper -name '*.sh'
}
shellmetrics $(sources)

View File

@ -0,0 +1,15 @@
#!/bin/bash
# Shell script beautify tool
# This script is for development purposes.
# It provide as is, do not any support.
# It may change without notice.
# Example of use
# cat example.sh | contrib/pretty.sh
eval "__dummy__() {
$(cat -)
}"
typeset -f __dummy__ | sed -E 's/( *)function /\1/; s/;$//'

View File

@ -0,0 +1,30 @@
#!/bin/sh -eu
run() {
echo "$@"
"$@"
}
confirm() {
printf "%s [y/N] " "$1"
read -r ans
case $ans in ([yY] | [yY][eE][sS]) return; esac
return 1
}
is_prerelease() {
case $1 in (*-*) return; esac
return 1
}
version=$(./shellspec --version)
confirm "Release $version?" || exit 0
run git tag -s -a "$version" -m "$version"
run git push origin "$version"
is_prerelease "$version" && exit 0
confirm "Update $version to latest?" || exit 0
run git tag -f latest
run git push -f origin latest

View File

@ -0,0 +1,24 @@
#!/bin/sh -eu
: "${SHELLSPEC_DOCKER:=shellspec/shellspec}"
UID=$(id -u) GID=$(id -g)
case $(uname -r) in (*Microsoft*)
PWD=$(wslpath -wa .)
if [ "${PWD##[a-zA-Z]:[\\/]*}" ]; then
echo "'$PWD' is not a valid Windows path" >&2
exit 1
fi
esac
docker_run() {
docker run -it --rm --entrypoint=/shellspec-docker "$@"
}
# You can override docker_run() with initrc to changes options,
# pass environment variables, etc.
if [ -e .shellspec-docker/initrc ]; then
. .shellspec-docker/initrc
fi
docker_run -u "$UID:$GID" -v "$PWD:/src" "$SHELLSPEC_DOCKER" "$@"

View File

@ -0,0 +1,152 @@
#!/bin/sh
# Run tests in docker
# This script is for development purposes.
# It provide as is, do not any support.
# It may change without notice.
set -eu
if [ $# -eq 0 ]; then
cat <<'USAGE'
Usage: test_in_docker.sh [Dockerfile..] [-- COMMAND]
Run tests in docker
Examples
contrib/test_in_docker.sh dockerfiles/*
contrib/test_in_docker.sh dockerfiles/debian-9-*
contrib/test_in_docker.sh dockerfiles/*-o # ends with -o is supported os
contrib/test_in_docker.sh dockerfiles/*bash* -- contrib/bugs.sh
To delete all shellspec images
docker rmi $(docker images shellspec -q)
USAGE
exit 0
fi
LF="
"
failures='' count=0 failures_count=0 total_count=0
main() {
options="" pull='' shift_count=0
for arg in "$@"; do
shift_count=$((shift_count + 1))
case $arg in
--) break ;;
-*)
[ "$arg" = "--pull" ] && pull=1
options="${options}${arg} "
;;
*) total_count=$((total_count + 1))
esac
done
cd contrib/helpers
docker build ${pull:+--pull} -t shellspec:helpers . | grayout
cd "$OLDPWD"
for arg in "$@"; do
shift "$shift_count"
shift_count=0
case $arg in
--) break ;;
-*) continue ;;
esac
run "$arg" "$@"
done
}
finished() {
if [ -f "$iidfile" ]; then
rm "$iidfile" ||:
fi
if [ "$failures" ]; then
echo >&2
echo "Failures:" >&2
echo "$failures" >&2
fi
}
info() {
printf '\033[1;35m%s\033[0m\n' "$*" >&2
}
grayout() {
while IFS= read -r line; do
printf '\033[1;2;37m\033[90m%s\033[0m\n' "$line" >&2
done
}
iidfile=$(mktemp -t shellspec.XXXXXXXX)
trap 'finished; exit 1' INT
trap 'finished' EXIT
run() {
dockerfile=$1
shift
info "======================================================================"
info "$dockerfile:" "$@"
count=$((count + 1))
os="${dockerfile##*/}"
os="${os#.}"
os="${os%-!}"
image="shellspec:$os"
old_image=$(docker images -q --no-trunc "$image")
# shellcheck disable=SC2086
docker build --iidfile "$iidfile" $options - < "$dockerfile" | grayout
base_image=$(cat "$iidfile")
info "Create image from base image: $base_image"
docker build --iidfile "$iidfile" -t "$image" --build-arg "IMAGE=$base_image" . -f "dockerfiles/.shellspec" | grayout
new_image=$(cat "$iidfile")
if [ "$old_image" ] && [ "$old_image" != "$new_image" ]; then
info "Delete old image $old_image"
docker rmi "$old_image" >/dev/null ||:
fi
info
info "Starting $dockerfile:" "$@"
info
docker run -it --rm "$image" "$@" &&:
xs=$?
info
if [ "$xs" -ne 0 ]; then
failures="${failures}- ${os} [$xs]${LF}"
failures_count=$((failures_count + 1))
fi
summary
info
}
summary() {
[ "$xs" -eq 0 ] && color="\033[32m" || color="\033[31m"
[ "$failures_count" -eq 0 ] && fcolor="\033[32m" || fcolor="\033[31m"
set -- "$count" "$total_count" "$failures_count"
printf "${color}##############################\n\033[m"
printf "${color}# exit status: %3d #\n\033[m" "$xs"
printf "${color}# ${fcolor}%3d / %3d (failures: %3d)${color} #\n\033[m" "$@"
printf "${color}##############################\n\033[m"
if [ "$failures" ]; then
echo "$failures"
fi
} >&2
start=$(date) start_sec=$(date +%s)
main "$@"
end=$(date) end_sec=$(date +%s)
sec=$((end_sec - start_sec))
echo "$start" >&2
echo "$end" >&2
echo "Done. $count tests, $sec sec ($((sec / 60)) min)" >&2
if [ "$failures" ]; then
exit 1
fi

View File

@ -0,0 +1,30 @@
#!/bin/sh
set -eu
: "${SH:=sh}"
# Workaround for GitHub Actions (SIGPIPE)
# In GitHub Actions, SIGPIPE seems to be set to SIG_IGN and if the process exits
# before receiving all STDIN data, it will result in Broken PIPE or I/O will be output
head() {
command head "$@"
cat >/dev/null
}
shellspec() {
set -- $SH shellspec --shell "$SH" "$@"
echo '$' "$@" >&2
"$@"
}
shellspec --banner --output progress --output documentation --output tap --output junit --output failures
shellspec --no-banner --skip-message quiet -j 3
shellspec --no-banner --skip-message quiet $(shellspec --list specfiles | head -n 5)
shellspec --no-banner --skip-message quiet $(shellspec --list examples:lineno | head -n 5)
shellspec --no-banner --skip-message quiet spec/general_spec.sh:40:60:80:100
shellspec --no-banner --skip-message quiet spec/libexec --profile
shellspec --syntax-check
shellspec --count
shellspec --task
shellspec --task hello:shellspec

View File

@ -0,0 +1,4 @@
FROM debian/eol:woody-slim
RUN groupadd user && useradd -m user -g user \
&& apt-get update && apt-get -y install ash
ENV SH=/bin/ash

View File

@ -0,0 +1,5 @@
FROM debian/eol:woody-slim
RUN groupadd user && useradd -m user -g user \
&& apt-get update && apt-get -y install busybox \
&& ln -s /bin/busybox /bin/ash
ENV SH=/bin/ash

View File

@ -0,0 +1,5 @@
FROM debian/eol:sarge-slim
RUN groupadd user && useradd -m user -g user \
&& apt-get update && apt-get -y install busybox-static \
&& ln -s /bin/busybox /bin/ash
ENV SH=/bin/ash

View File

@ -0,0 +1,5 @@
FROM debian/eol:sarge-slim
ENV DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes
RUN groupadd user && useradd -m user -g user \
&& apt-get update && apt-get -y install ksh
ENV SH=/bin/ksh

View File

@ -0,0 +1,7 @@
FROM debian/eol:etch-slim
ENV DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes
RUN useradd -m user \
&& sed '/updates/s/^/# /' -i /etc/apt/sources.list \
&& apt-get update && apt-get -y install busybox \
&& /bin/busybox --install -s
ENV SH=/bin/ash

View File

@ -0,0 +1,6 @@
FROM debian/eol:etch-slim
ENV DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes
RUN useradd -m user \
&& sed '/updates/s/^/# /' -i /etc/apt/sources.list \
&& apt-get update && apt-get -y install dash
ENV SH=/bin/dash

View File

@ -0,0 +1,6 @@
FROM debian/eol:etch-slim
ENV DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes
RUN useradd -m user \
&& sed '/updates/s/^/# /' -i /etc/apt/sources.list \
&& apt-get update && apt-get -y install ksh
ENV SH=/bin/ksh

View File

@ -0,0 +1,6 @@
FROM debian/eol:lenny-slim
ENV DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes
RUN useradd -m user \
&& apt-get update && apt-get -y --force-yes install busybox \
&& /bin/busybox --install -s
ENV SH=/bin/ash

View File

@ -0,0 +1,61 @@
ARG TYPE="general"
FROM alpine as general_installer
RUN apk --no-cache add git curl
RUN echo 'cd $(dirname $(which _$1)); mv _$1 $1' > /usr/local/bin/enable \
&& echo 'cd $(dirname $(which $1)); mv $1 _$1' > /usr/local/bin/disable \
&& chmod +x /usr/local/bin/*
RUN echo '#!/bin/sh' > /entrypoint.sh \
&& echo 'echo You can use enable/disable [COMMAND]' >> /entrypoint.sh \
&& echo 'exec "$@"' >> /entrypoint.sh \
&& chmod +x /entrypoint.sh
ENV PATH $PATH:/root/bin
WORKDIR /root
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "/bin/sh" ]
FROM alpine as make_installer
RUN apk --no-cache add make
WORKDIR /root/shellspec
FROM alpine as bpkg_installer
RUN apk --no-cache add bash git curl make coreutils
RUN curl -sLo- http://get.bpkg.sh | PREFIX=/usr/local bash
RUN echo '#!/bin/sh' > /entrypoint.sh \
&& echo 'echo Usage: bpkg install shellspec/shellspec' >> /entrypoint.sh \
&& echo 'echo Usage: bpkg install shellspec/shellspec@0.19.0' >> /entrypoint.sh \
&& echo 'echo Usage: bpkg install shellspec/shellspec -g' >> /entrypoint.sh \
&& echo 'exec "$@"' >> /entrypoint.sh \
&& chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
WORKDIR /root/project
CMD [ "/bin/bash", "-l" ]
FROM alpine as basher_installer
RUN apk --no-cache add bash git
RUN git clone https://github.com/basherpm/basher.git ~/.basher
RUN echo 'export PATH="$HOME/.basher/bin:$PATH"' >> ~/.bash_profile
RUN echo 'eval "$(basher init -)"' >> ~/.bash_profile
RUN echo '#!/bin/sh' > /entrypoint.sh \
&& echo 'echo Usage: basher install shellspec/shellspec' >> /entrypoint.sh \
&& echo 'echo Usage: basher install shellspec/shellspec@0.19.0' >> /entrypoint.sh \
&& echo 'echo Usage: basher link ./shellspec shellspec/shellspec' >> /entrypoint.sh \
&& echo 'exec "$@"' >> /entrypoint.sh \
&& chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
WORKDIR /root
CMD [ "/bin/bash", "-l" ]
FROM linuxbrew/brew as brew_installer
RUN echo '#!/bin/sh' > /entrypoint.sh \
&& echo 'echo "Usage: brew tap shellspec/shellspec"' >> /entrypoint.sh \
&& echo 'echo " brew install shellspec"' >> /entrypoint.sh \
&& echo 'echo " brew test shellspec"' >> /entrypoint.sh \
&& echo 'exec "$@"' >> /entrypoint.sh \
&& chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "/bin/bash", "-l" ]
FROM shellspec:${TYPE}_installer as test
COPY ./install.sh /root/
COPY ./ /root/shellspec

View File

@ -0,0 +1,3 @@
ARG VERSION=latest
FROM koalaman/shellcheck-alpine:$VERSION
COPY ./ ./

View File

@ -0,0 +1,21 @@
ARG IMAGE
FROM shellspec:helpers as helpers
FROM $IMAGE
COPY --from=helpers /usr/local/bin/* /usr/local/bin/
WORKDIR /shellspec
RUN chmod ug+s /usr/local/bin/su-exec \
&& echo "--no-banner" > /home/user/.shellspec-options \
&& if [ "$KCOV" ]; then echo "--kcov" >> /home/user/.shellspec-options; fi \
&& ln -s /shellspec/shellspec /usr/local/bin/shellspec \
&& echo "$SH" > /etc/invokesh.conf \
&& [ "$SH" = "/bin/sh" ] || ln -snf /usr/local/bin/invokesh /bin/sh
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "shellspec" ]
COPY ./dockerfiles/.shellspec-entrypoint.sh /entrypoint.sh
COPY --chown=user:user ./ /shellspec
RUN chmod 777 /shellspec
USER user

View File

@ -0,0 +1,10 @@
#!/bin/sh
if [ "$1" = "shellspec" ]; then
(
export SUDO_GID=$(id -g) SUDO_UID=$(id -u)
su-exec root shellspec --task fixture:stat:prepare
)
fi
exec "$@"

View File

@ -0,0 +1,3 @@
FROM alpine:3.12.3
RUN adduser -D user && apk add --no-cache bash
ENV SH=/bin/bash

View File

@ -0,0 +1,3 @@
FROM alpine:3.12.3
RUN adduser -D user && apk add --no-cache loksh
ENV SH=/bin/ksh

View File

@ -0,0 +1,3 @@
FROM alpine:3.12.3
RUN adduser -D user
ENV SH=/bin/ash

View File

@ -0,0 +1,3 @@
FROM alpine:edge
RUN adduser -D user && apk add --no-cache loksh
ENV SH=/bin/ksh

View File

@ -0,0 +1,4 @@
FROM alpine:edge
RUN echo "@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
RUN adduser -D user && apk add --no-cache oksh@testing
ENV SH=/bin/oksh

View File

@ -0,0 +1,3 @@
FROM busybox:1.32.0
RUN adduser -D user
ENV SH=/bin/ash

Some files were not shown because too many files have changed in this diff Show More