#!/usr/bin/env bash set -euo pipefail # Regression tests for k3s CNPG storage guardrails in etc/init_cloudnative_pg.sh # - must fail if protected mounts are root-backed # - must fail if manifest lacks explicit prole-iscsi + selector labels (prevents local-path fallback) # - must succeed (with mocks) when mounts + manifest + runtime objects are correct SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) REPO_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd) SCRIPT_UNDER_TEST="$REPO_ROOT/etc/init_cloudnative_pg.sh" run_case() { local name="$1"; shift echo "--- CASE: $name ---" # Ensure case-local env doesn't leak across runs. unset CNPG_MANIFEST_OVERRIDE || true unset PROLE_NAMESPACE PROLE_MODE DEPLOYMENT_MODE CLUSTER_ENV prole_mode \ LOCAL_REGISTRY LOCAL_REGISTRY_INTERNAL PROLE_K3S_SERVER K3S_SERVER_URL || true local TMP_DIR TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' RETURN local BIN_DIR="$TMP_DIR/bin" mkdir -p "$BIN_DIR" export PROLE_TEST_LOG_FILE="$TMP_DIR/mock_calls.log" : >"$PROLE_TEST_LOG_FILE" # findmnt mock (driven by env vars) cat >"$BIN_DIR/findmnt" <<'EOF' #!/usr/bin/env bash path="" field="" while [[ $# -gt 0 ]]; do case "$1" in -T) path="$2"; shift 2; continue ;; -o) field="$2"; shift 2; continue ;; esac shift done if [[ "$field" == *","* ]]; then field="${field%%,*}" fi if [[ "$path" == "/" && "$field" == "SOURCE" ]]; then printf '%s\n' "${PROLE_TEST_FINDMNT_ROOT_SRC:-/dev/root}" exit 0 fi if [[ "$field" == "TARGET" ]]; then # Return the mount target for the path (for root-backed checks) printf '%s\n' "${PROLE_TEST_FINDMNT_TARGET:-$path}" exit 0 fi case "$path" in "${PROLE_PROTECTED_DATA_PATH}") printf '%s\n' "${PROLE_TEST_FINDMNT_DATA_SRC:-/dev/iscsi-data}"; exit 0 ;; "${PROLE_PROTECTED_WAL_PATH}") printf '%s\n' "${PROLE_TEST_FINDMNT_WAL_SRC:-/dev/iscsi-wal}"; exit 0 ;; esac # Default: pretend it's root-backed printf '%s\n' "${PROLE_TEST_FINDMNT_ROOT_SRC:-/dev/root}" EOF chmod +x "$BIN_DIR/findmnt" # kubectl mock cat >"$BIN_DIR/kubectl" <<'EOF' #!/usr/bin/env bash _log_file="${PROLE_TEST_LOG_FILE:-}" if [[ -n "${_log_file}" ]]; then echo "Mocked kubectl called with $*" >>"${_log_file}" 2>/dev/null || true fi args="$*" if [[ "$args" == *"get --raw='/readyz'"* || "$args" == *"get --raw=/readyz"* ]]; then echo "ok" exit 0 fi if [[ "$args" == *"version --short"* ]]; then echo "Client Version: v0.0.0" echo "Server Version: v0.0.0" exit 0 fi # CNPG webhook wait: return an endpoint IP so waits pass quickly. if [[ "$args" == *"get endpoints"*"cnpg-webhook-service"* ]]; then echo "10.42.0.10" exit 0 fi # Barman TLS readiness probes: return non-empty jsonpath so waits pass. if [[ "$args" == *"-n cnpg-system get secret"*"barman-cloud-"*"-o jsonpath="* ]]; then echo "dummy" exit 0 fi # Barman plugin service registration readiness (strict gating) if [[ "$args" == *"-n cnpg-system get svc"*"-l cnpg.io/pluginName=barman-cloud.cloudnative-pg.io"*"-o jsonpath="*"metadata.name"* ]]; then echo "barman-cloud" exit 0 fi if [[ "$args" == *"-n cnpg-system get svc barman-cloud"*"-o jsonpath="*"metadata.name"* ]]; then echo "barman-cloud" exit 0 fi if [[ "$args" == *"-n cnpg-system get svc barman-cloud"*"pluginClientSecret"* ]]; then echo "barman-cloud-client-tls" exit 0 fi if [[ "$args" == *"-n cnpg-system get svc barman-cloud"*"pluginServerSecret"* ]]; then echo "barman-cloud-server-tls" exit 0 fi if [[ "$args" == *"-n cnpg-system get svc barman-cloud"*"pluginPort"* ]]; then echo "9090" exit 0 fi if [[ "$args" == *"-n cnpg-system get endpoints barman-cloud"* ]]; then echo "10.42.0.11" exit 0 fi # DB node availability checks (bootstrap safety) if [[ "$args" == *"get nodes"*"--no-headers"* ]]; then # Name Status Roles Age Version echo "mock-node-1 Ready 1d v0.0.0" exit 0 fi # Runtime validation calls if [[ "$args" == *" get cluster prole-db -o json"* ]]; then cat <"$BIN_DIR/$t" <>"$TMP_DIR/mock_calls.log" exit 0 EOF chmod +x "$BIN_DIR/$t" done export PATH="$BIN_DIR:$PATH" # Minimal conf layout (service env) so prole_cfg.sh loads k3s mode. local CONF_DIR="$TMP_DIR/conf" mkdir -p "$CONF_DIR/service" cat >"$CONF_DIR/service/prole.cfg" <"$TMP_DIR/stdout" 2>"$TMP_DIR/stderr" rc=$? set -e echo "[DEBUG_LOG] rc=$rc" sed -n '1,120p' "$TMP_DIR/stderr" || true sed -n '1,120p' "$TMP_DIR/stdout" || true return $rc } case_root_backed() { local manifest_dir="$1" # Any manifest is fine; we should fail before apply. cat >"$manifest_dir/prole-db.yaml" <<'EOF' apiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: prole-db spec: storage: pvcTemplate: storageClassName: prole-iscsi selector: matchLabels: prole.storage/role: data walStorage: pvcTemplate: storageClassName: prole-iscsi selector: matchLabels: prole.storage/role: wal EOF export PROLE_TEST_FINDMNT_DATA_SRC="/dev/root" export PROLE_TEST_FINDMNT_WAL_SRC="/dev/root" } case_invalid_manifest_missing_sc() { local manifest_dir="$1" local tmp_dir="$2" cat >"$manifest_dir/prole-db.yaml" <<'EOF' apiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: prole-db spec: storage: pvcTemplate: resources: requests: storage: 29Gi walStorage: pvcTemplate: resources: requests: storage: 29Gi EOF # Use override so deploy_cluster applies our invalid manifest even if sync modifies the default. export CNPG_MANIFEST_OVERRIDE="$manifest_dir/prole-db.yaml" } case_success() { local manifest_dir="$1" cat >"$manifest_dir/prole-db.yaml" <<'EOF' apiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: prole-db spec: instances: 1 storage: pvcTemplate: storageClassName: prole-iscsi selector: matchLabels: prole.storage/role: data resources: requests: storage: 29Gi walStorage: pvcTemplate: storageClassName: prole-iscsi selector: matchLabels: prole.storage/role: wal resources: requests: storage: 29Gi EOF } # 1) Root-backed mounts must hard-fail. if run_case "root-backed mounts" case_root_backed; then echo "FAILURE: expected root-backed mounts to fail" >&2 exit 1 fi # 2) Missing explicit storageClassName/selector must hard-fail. if run_case "invalid manifest (missing storageClassName/selector)" case_invalid_manifest_missing_sc; then echo "FAILURE: expected invalid manifest to fail" >&2 exit 1 fi # 3) Happy path should succeed with mocks. run_case "success" case_success echo "SUCCESS"