#!/usr/bin/env bash set -Eeuo pipefail readonly EXPECTED_KUSTOMIZE_VERSION="v5.8.1" readonly EXPECTED_HELM_VERSION="v3.19.4" readonly TARGET_KUBERNETES_VERSION="1.36.2" readonly CNPG_CHART_NAME="cloudnative-pg" readonly CNPG_CHART_VERSION="0.29.0" readonly CNPG_CHART_REPOSITORY="https://cloudnative-pg.github.io/charts" readonly EXPECTED_CNPG_CHART_SHA256="668e065ff53508d58238788fd35b355a925060843629a951df0e6a9362e6d32f" readonly GITEA_CHART_NAME="gitea" readonly GITEA_CHART_VERSION="12.7.0" readonly GITEA_CHART_REPOSITORY="https://dl.gitea.com/charts/" readonly EXPECTED_GITEA_CHART_SHA256="5881ef9c59400bee2d5547e77c4cd0efb925143c2f5d93fb4f38446db76b0167" readonly REPOSITORY_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)" readonly -a RENDERED_MANIFEST_NAMES=( namespaces ssd-local-pv cnpg-operator platform-postgres gitea gitea-oidc ) fail() { printf 'ERROR: %s\n' "$*" >&2 exit 1 } assert_exact_source_text() { local file="$1" local expected_text="$2" local description="$3" local count count="$(rg --count-matches --fixed-strings -- "$expected_text" "$file" || true)" [[ "$count" == "1" ]] || \ fail "${description} must appear exactly once in ${file#${REPOSITORY_ROOT}/}" } usage() { cat <<'USAGE' Usage: bash scripts/validate/render-phase1.sh bash scripts/validate/render-phase1.sh \ --verified-output-dir /tmp/platform-phase1-apply.XXXXXX The output option is an internal handoff used only by the Phase 1 apply script. The destination must be an existing, empty, non-symlink directory created directly below /tmp with the platform-phase1-apply.* prefix. USAGE } verified_output_dir="" case "$#" in 0) ;; 2) [[ "$1" == "--verified-output-dir" ]] || { usage >&2 exit 2 } verified_output_dir="$2" ;; *) usage >&2 exit 2 ;; esac if [[ -n "$verified_output_dir" ]]; then [[ "$verified_output_dir" == /tmp/platform-phase1-apply.* ]] || \ fail "verified output directory must match /tmp/platform-phase1-apply.*" [[ -d "$verified_output_dir" && ! -L "$verified_output_dir" ]] || \ fail "verified output directory must be an existing non-symlink directory" [[ "$(cd -- "$verified_output_dir" && pwd -P)" == "$verified_output_dir" ]] || \ fail "verified output directory must be an absolute canonical path" [[ -O "$verified_output_dir" ]] || \ fail "verified output directory must be owned by the current user" [[ "$(stat --format='%a' -- "$verified_output_dir")" == "700" ]] || \ fail "verified output directory must have mode 0700" [[ -z "$(find "$verified_output_dir" -mindepth 1 -maxdepth 1 -print -quit)" ]] || \ fail "verified output directory must be empty" fi command -v kubectl >/dev/null 2>&1 || fail "kubectl is required" command -v jq >/dev/null 2>&1 || fail "jq is required" command -v rg >/dev/null 2>&1 || fail "ripgrep (rg) is required" command -v sha256sum >/dev/null 2>&1 || fail "sha256sum is required" command -v tar >/dev/null 2>&1 || fail "tar is required" command -v cmp >/dev/null 2>&1 || fail "cmp is required" command -v find >/dev/null 2>&1 || fail "find is required" command -v install >/dev/null 2>&1 || fail "install is required" command -v stat >/dev/null 2>&1 || fail "stat is required" if [[ -n "${PLATFORM_HELM_BIN:-}" ]]; then [[ "$PLATFORM_HELM_BIN" == /* ]] || \ fail "PLATFORM_HELM_BIN must be an absolute path" [[ -f "$PLATFORM_HELM_BIN" && -x "$PLATFORM_HELM_BIN" ]] || \ fail "PLATFORM_HELM_BIN is not an executable file: ${PLATFORM_HELM_BIN}" readonly HELM_BIN="$PLATFORM_HELM_BIN" else HELM_BIN="$(command -v helm 2>/dev/null)" || \ fail "Helm ${EXPECTED_HELM_VERSION} is required" readonly HELM_BIN fi kustomize_version="$(kubectl version --client --output=yaml | sed -n 's/^kustomizeVersion: //p')" helm_version="$("$HELM_BIN" version --template '{{.Version}}')" [[ "$kustomize_version" == "$EXPECTED_KUSTOMIZE_VERSION" ]] || \ fail "expected Kustomize ${EXPECTED_KUSTOMIZE_VERSION}, found ${kustomize_version:-unknown}" [[ "$helm_version" == "$EXPECTED_HELM_VERSION" ]] || \ fail "expected Helm ${EXPECTED_HELM_VERSION}, found ${helm_version:-unknown}" render_dir="$(mktemp -d "${TMPDIR:-/tmp}/platform-phase1-render.XXXXXX")" declare -a generated_chart_cache_dirs=() declare -a generated_chart_cache_parent_dirs=() cleanup() { local cache_dir local parent_dir for cache_dir in "${generated_chart_cache_dirs[@]}"; do case "$cache_dir" in "${REPOSITORY_ROOT}/infrastructure/controllers/cloudnative-pg/.helm/charts/cloudnative-pg-0.29.0"|\ "${REPOSITORY_ROOT}/services/gitea/profiles/oidc/.helm/charts/gitea-12.7.0") rm -rf -- "$cache_dir" ;; *) printf 'WARNING: refusing to remove unexpected chart cache: %s\n' \ "$cache_dir" >&2 ;; esac done for parent_dir in "${generated_chart_cache_parent_dirs[@]}"; do case "$parent_dir" in "${REPOSITORY_ROOT}/infrastructure/controllers/cloudnative-pg/.helm/charts"|\ "${REPOSITORY_ROOT}/infrastructure/controllers/cloudnative-pg/.helm"|\ "${REPOSITORY_ROOT}/services/gitea/profiles/oidc/.helm/charts"|\ "${REPOSITORY_ROOT}/services/gitea/profiles/oidc/.helm") rmdir -- "$parent_dir" 2>/dev/null || true ;; *) printf 'WARNING: refusing to remove unexpected chart cache parent: %s\n' \ "$parent_dir" >&2 ;; esac done case "$render_dir" in /tmp/platform-phase1-render.*|"${TMPDIR:-/tmp}"/platform-phase1-render.*) rm -rf -- "$render_dir" ;; *) printf 'WARNING: refusing to remove unexpected render directory: %s\n' "$render_dir" >&2 ;; esac } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM render_plain() { local label="$1" local relative_path="$2" local output="${render_dir}/${label}.yaml" kubectl kustomize "${REPOSITORY_ROOT}/${relative_path}" >"$output" [[ -s "$output" ]] || fail "${label} rendered an empty manifest" printf 'Rendered %-20s %8s bytes\n' "$label" "$(wc -c <"$output" | tr -d '[:space:]')" } render_helm() { local label="$1" local relative_path="$2" local output="${render_dir}/${label}.yaml" kubectl kustomize \ --enable-helm \ --helm-command "$HELM_BIN" \ --helm-kube-version "$TARGET_KUBERNETES_VERSION" \ "${REPOSITORY_ROOT}/${relative_path}" >"$output" [[ -s "$output" ]] || fail "${label} rendered an empty manifest" printf 'Rendered %-20s %8s bytes\n' "$label" "$(wc -c <"$output" | tr -d '[:space:]')" } extract_rendered_document() { local manifest="$1" local wanted_kind="$2" local wanted_name="$3" local output="$4" awk \ -v wanted_kind="$wanted_kind" \ -v wanted_name="$wanted_name" \ ' function reset_document() { document = "" document_kind = "" document_name = "" in_metadata = 0 } function flush_document() { if (document_kind == wanted_kind && document_name == wanted_name) { matches++ printf "%s", document } } BEGIN { reset_document() } /^---[[:space:]]*$/ { flush_document() reset_document() next } { document = document $0 ORS if ($0 ~ /^kind:[[:space:]]*/) { document_kind = $0 sub(/^kind:[[:space:]]*/, "", document_kind) sub(/[[:space:]]*$/, "", document_kind) } if ($0 == "metadata:") { in_metadata = 1 next } if (in_metadata && $0 ~ /^ name:[[:space:]]*/) { document_name = $0 sub(/^ name:[[:space:]]*/, "", document_name) sub(/[[:space:]]*$/, "", document_name) in_metadata = 0 } else if (in_metadata && $0 ~ /^[^[:space:]]/) { in_metadata = 0 } } END { flush_document() if (matches != 1) { exit 42 } } ' \ "$manifest" >"$output" } prepare_verified_chart_cache() { local label="$1" local chart_name="$2" local chart_repository="$3" local chart_version="$4" local build_root_relative_path="$5" local expected_digest="$6" local package_path="${render_dir}/${chart_name}-${chart_version}.tgz" local cache_version_dir="${REPOSITORY_ROOT}/${build_root_relative_path}/.helm/charts/${chart_name}-${chart_version}" local checksum_output local actual_digest local chart_cache_dir="${cache_version_dir%/*}" local helm_cache_dir="${chart_cache_dir%/*}" local cache_parent if [[ ! -f "$package_path" ]]; then "$HELM_BIN" pull "$chart_name" \ --repo "$chart_repository" \ --version "$chart_version" \ --destination "$render_dir" fi [[ -f "$package_path" ]] || fail "${label} chart package was not downloaded: ${package_path}" checksum_output="$(sha256sum -- "$package_path")" actual_digest="${checksum_output%% *}" [[ "$actual_digest" == "$expected_digest" ]] || \ fail "${label} chart package SHA-256 mismatch: expected ${expected_digest}, found ${actual_digest}" case "$cache_version_dir" in "${REPOSITORY_ROOT}/infrastructure/controllers/cloudnative-pg/.helm/charts/cloudnative-pg-0.29.0"|\ "${REPOSITORY_ROOT}/services/gitea/profiles/oidc/.helm/charts/gitea-12.7.0") ;; *) fail "refusing to create unexpected chart cache directory: ${cache_version_dir}" ;; esac for cache_parent in "$helm_cache_dir" "$chart_cache_dir"; do [[ ! -L "$cache_parent" ]] || \ fail "refusing symlinked chart cache parent: ${cache_parent}" [[ ! -e "$cache_parent" || -d "$cache_parent" ]] || \ fail "chart cache parent is not a directory: ${cache_parent}" done [[ ! -e "$cache_version_dir" && ! -L "$cache_version_dir" ]] || \ fail "generated chart cache already exists; remove it only after confirming it is disposable: ${cache_version_dir}" [[ -e "$chart_cache_dir" ]] || generated_chart_cache_parent_dirs+=("$chart_cache_dir") [[ -e "$helm_cache_dir" ]] || generated_chart_cache_parent_dirs+=("$helm_cache_dir") mkdir -p -- "$cache_version_dir" generated_chart_cache_dirs+=("$cache_version_dir") tar -xzf "$package_path" -C "$cache_version_dir" [[ -f "${cache_version_dir}/${chart_name}/Chart.yaml" ]] || \ fail "${label} extracted chart is missing Chart.yaml" printf 'Verified %-20s SHA-256 %s\n' "$label" "$actual_digest" } cd -- "$REPOSITORY_ROOT" if rg --line-number --glob '*.yaml' --glob '*.yml' \ --glob '!**/.helm/**' --glob '!**/charts/**' \ '^[[:space:]]*kind:[[:space:]]*Secret[[:space:]]*$' \ infrastructure services bootstrap clusters components; then fail "a source-controlled Kubernetes Secret manifest was found" fi if rg --line-number --glob 'kustomization.yaml' 'LoadRestrictionsNone|load-restrictor' .; then fail "the repository must keep Kustomize LoadRestrictionsRootOnly" fi readonly GITEA_BASELINE_KUSTOMIZATION="${REPOSITORY_ROOT}/services/gitea/kustomization.yaml" readonly GITEA_OIDC_KUSTOMIZATION="${REPOSITORY_ROOT}/services/gitea/profiles/oidc/kustomization.yaml" for gitea_kustomization in \ "$GITEA_BASELINE_KUSTOMIZATION" \ "$GITEA_OIDC_KUSTOMIZATION"; do assert_exact_source_text "$gitea_kustomization" \ 'repo: https://dl.gitea.com/charts/' \ "the pinned Gitea Chart repository" assert_exact_source_text "$gitea_kustomization" \ 'version: 12.7.0' \ "the pinned Gitea Chart version" assert_exact_source_text "$gitea_kustomization" \ 'includeCRDs: false' \ "the Gitea includeCRDs policy" assert_exact_source_text "$gitea_kustomization" \ 'skipTests: true' \ "the Gitea Helm test policy" done assert_exact_source_text "$GITEA_BASELINE_KUSTOMIZATION" \ 'chartHome: profiles/oidc/.helm/charts' \ "the baseline Gitea verified Chart cache" assert_exact_source_text "$GITEA_BASELINE_KUSTOMIZATION" \ 'valuesFile: profiles/oidc/values/baseline.yaml' \ "the baseline Gitea values path" assert_exact_source_text "$GITEA_OIDC_KUSTOMIZATION" \ 'chartHome: .helm/charts' \ "the OIDC Gitea verified Chart cache" assert_exact_source_text "$GITEA_OIDC_KUSTOMIZATION" \ 'valuesFile: values/baseline.yaml' \ "the OIDC Gitea baseline values path" assert_exact_source_text "$GITEA_OIDC_KUSTOMIZATION" \ 'additionalValuesFiles:' \ "the OIDC Gitea values merge" assert_exact_source_text "$GITEA_OIDC_KUSTOMIZATION" \ 'values/oidc.yaml' \ "the OIDC Gitea override path" render_plain namespaces infrastructure/namespaces/overlays/home render_plain ssd-local-pv infrastructure/storage/ssd-local-pv prepare_verified_chart_cache \ cloudnative-pg-chart \ "$CNPG_CHART_NAME" \ "$CNPG_CHART_REPOSITORY" \ "$CNPG_CHART_VERSION" \ infrastructure/controllers/cloudnative-pg \ "$EXPECTED_CNPG_CHART_SHA256" render_helm cnpg-operator infrastructure/controllers/cloudnative-pg render_plain platform-postgres services/platform-postgres prepare_verified_chart_cache \ gitea-chart \ "$GITEA_CHART_NAME" \ "$GITEA_CHART_REPOSITORY" \ "$GITEA_CHART_VERSION" \ services/gitea/profiles/oidc \ "$EXPECTED_GITEA_CHART_SHA256" render_helm gitea services/gitea render_helm gitea-oidc services/gitea/profiles/oidc [[ "$(awk '$0 == "kind: Cluster" { count++ } END { print count + 0 }' "${render_dir}/platform-postgres.yaml")" == "1" ]] || \ fail "the Phase 1 PostgreSQL root must contain exactly one Cluster" [[ "$(awk '$0 == "kind: DatabaseRole" { count++ } END { print count + 0 }' "${render_dir}/platform-postgres.yaml")" == "1" ]] || \ fail "the Phase 1 PostgreSQL root must contain exactly one Gitea DatabaseRole" [[ "$(awk '$0 == "kind: Database" { count++ } END { print count + 0 }' "${render_dir}/platform-postgres.yaml")" == "1" ]] || \ fail "the Phase 1 PostgreSQL root must contain exactly one Gitea Database" [[ "$(awk '$0 == "kind: NetworkPolicy" { count++ } END { print count + 0 }' "${render_dir}/platform-postgres.yaml")" == "1" ]] || \ fail "the Phase 1 PostgreSQL root must contain exactly one base NetworkPolicy" [[ "$(rg --count -- '^[[:space:]]*name:[[:space:]]platform-postgres-gitea[[:space:]]*$' "${render_dir}/platform-postgres.yaml" || true)" == "2" ]] || \ fail "the Phase 1 PostgreSQL root must contain the Gitea DatabaseRole and Database" if rg --quiet '^[[:space:]]*name:[[:space:]]platform-postgres-keycloak[[:space:]]*$' "${render_dir}/platform-postgres.yaml"; then fail "the Phase 1 PostgreSQL root must not contain a Keycloak DatabaseRole or Database" fi [[ "$(rg --count -- '^[[:space:]]*-[[:space:]]host gitea gitea all scram-sha-256[[:space:]]*$' "${render_dir}/platform-postgres.yaml" || true)" == "1" ]] || \ fail "the Gitea role must authenticate only to the Gitea database" [[ "$(rg --count -- '^[[:space:]]*-[[:space:]]host all gitea all reject[[:space:]]*$' "${render_dir}/platform-postgres.yaml" || true)" == "1" ]] || \ fail "the Gitea role must be rejected from every other database" [[ "$(rg --count -- '^[[:space:]]*-[[:space:]]host keycloak keycloak all scram-sha-256[[:space:]]*$' "${render_dir}/platform-postgres.yaml" || true)" == "1" ]] || \ fail "the Keycloak role must authenticate only to the Keycloak database" [[ "$(rg --count -- '^[[:space:]]*-[[:space:]]host all keycloak all reject[[:space:]]*$' "${render_dir}/platform-postgres.yaml" || true)" == "1" ]] || \ fail "the Keycloak role must be rejected from every other database" rg --quiet '^kind: CustomResourceDefinition$' "${render_dir}/cnpg-operator.yaml" || \ fail "CloudNativePG CRDs are missing from the operator render" rg --quiet '^kind: Cluster$' "${render_dir}/platform-postgres.yaml" || \ fail "the platform PostgreSQL Cluster is missing" rg --quiet '^kind: DatabaseRole$' "${render_dir}/platform-postgres.yaml" || \ fail "the Gitea DatabaseRole is missing" rg --quiet '^kind: Database$' "${render_dir}/platform-postgres.yaml" || \ fail "the Gitea Database is missing" for gitea_profile in gitea gitea-oidc; do gitea_profile_manifest="${render_dir}/${gitea_profile}.yaml" gitea_profile_deployment_document="${render_dir}/${gitea_profile}-deployment.yaml" gitea_profile_ingress_document="${render_dir}/${gitea_profile}-ingress.yaml" gitea_profile_servicemonitor_document="${render_dir}/${gitea_profile}-servicemonitor.yaml" rg --quiet '^kind: PersistentVolumeClaim$' "$gitea_profile_manifest" || \ fail "${gitea_profile} is missing the Gitea PVC" rg --quiet '^kind: Ingress$' "$gitea_profile_manifest" || \ fail "${gitea_profile} is missing the Gitea Ingress" rg --quiet \ '^[[:space:]]*-[[:space:]]*host:[[:space:]]*git\.learn\.hyeonworks\.com[[:space:]]*$' \ "$gitea_profile_manifest" || \ fail "${gitea_profile} Ingress host is not git.learn.hyeonworks.com" [[ "$(rg --count-matches \ '^[[:space:]]*ROOT_URL=https://git\.learn\.hyeonworks\.com/[[:space:]]*$' \ "$gitea_profile_manifest" || true)" == "1" ]] || \ fail "${gitea_profile} must render the external HTTPS ROOT_URL exactly once" extract_rendered_document \ "$gitea_profile_manifest" \ Deployment \ gitea \ "$gitea_profile_deployment_document" || \ fail "${gitea_profile} must contain exactly one gitea Deployment" extract_rendered_document \ "$gitea_profile_manifest" \ Ingress \ gitea-http \ "$gitea_profile_ingress_document" || \ fail "${gitea_profile} must contain exactly one gitea-http Ingress" extract_rendered_document \ "$gitea_profile_manifest" \ ServiceMonitor \ gitea \ "$gitea_profile_servicemonitor_document" || \ fail "${gitea_profile} must contain exactly one gitea ServiceMonitor" if rg --quiet '^[[:space:]]{2}tls:[[:space:]]*' "$gitea_profile_ingress_document"; then fail "${gitea_profile} must not render an in-cluster TLS section" fi if ! kubectl create --dry-run=client \ -f "$gitea_profile_servicemonitor_document" \ -o json | jq -e ' .apiVersion == "monitoring.coreos.com/v1" and .kind == "ServiceMonitor" and .metadata.name == "gitea" and .metadata.namespace == "gitea" and .metadata.labels["observability.hyeonworks.com/instance"] == "home" and .spec.jobLabel == "app.kubernetes.io/name" and .spec.selector.matchLabels == { "app.kubernetes.io/instance": "gitea", "app.kubernetes.io/name": "gitea" } and .spec.endpoints == [{ "interval": "30s", "port": "http", "scrapeTimeout": "10s" }] ' >/dev/null; then fail "${gitea_profile} ServiceMonitor contract is not exact" fi for restricted_setting in \ 'allowPrivilegeEscalation: false' \ 'runAsNonRoot: true' \ 'type: RuntimeDefault' \ '- ALL'; do [[ "$(rg --count-matches --fixed-strings -- "$restricted_setting" \ "$gitea_profile_deployment_document" || true)" == "4" ]] || \ fail "${gitea_profile} must apply ${restricted_setting} to all four containers" done done for forbidden_baseline_marker in \ 'gitea-keycloak-oidc' \ 'id.learn.hyeonworks.com' \ 'gitea-allow-host-nginx-keycloak' \ 'gitea-branding-assets' \ 'gitea-branding-templates' \ 'ALLOW_ONLY_EXTERNAL_REGISTRATION=true'; do if rg --quiet --fixed-strings -- "$forbidden_baseline_marker" "${render_dir}/gitea.yaml"; then fail "the baseline Gitea render contains OIDC-only marker ${forbidden_baseline_marker}" fi done for baseline_setting in \ 'DISABLE_REGISTRATION=true' \ 'ALLOW_ONLY_EXTERNAL_REGISTRATION=false' \ 'SHOW_REGISTRATION_BUTTON=false' \ 'ENABLE_PASSWORD_SIGNIN_FORM=true'; do [[ "$(rg --count-matches --fixed-strings -- "$baseline_setting" \ "${render_dir}/gitea.yaml" || true)" == "1" ]] || \ fail "the baseline Gitea render must contain exactly one ${baseline_setting} setting" done gitea_deployment_document="${render_dir}/gitea-oidc-deployment.yaml" gitea_keycloak_policy_document="${render_dir}/gitea-keycloak-egress-policy.yaml" gitea_branding_assets_document="${render_dir}/gitea-branding-assets-configmap.yaml" gitea_branding_templates_document="${render_dir}/gitea-branding-templates-configmap.yaml" extract_rendered_document \ "${render_dir}/gitea-oidc.yaml" \ Deployment \ gitea \ "$gitea_deployment_document" || \ fail "the Gitea render must contain exactly one gitea Deployment" extract_rendered_document \ "${render_dir}/gitea-oidc.yaml" \ NetworkPolicy \ gitea-allow-host-nginx-keycloak \ "$gitea_keycloak_policy_document" || \ fail "the Gitea render must contain exactly one dedicated Keycloak egress NetworkPolicy" # Assert only references to the externally-created OIDC credential Secret. # No credential payload is rendered, decoded, read, or printed by these checks. rg --quiet --multiline \ '(?s)- name: GITEA_OAUTH_KEY_0\n[[:space:]]+valueFrom:\n[[:space:]]+secretKeyRef:\n[[:space:]]+key: key\n[[:space:]]+name: gitea-keycloak-oidc' \ "$gitea_deployment_document" || \ fail "the Gitea Deployment does not reference gitea-keycloak-oidc key=key" rg --quiet --multiline \ '(?s)- name: GITEA_OAUTH_SECRET_0\n[[:space:]]+valueFrom:\n[[:space:]]+secretKeyRef:\n[[:space:]]+key: secret\n[[:space:]]+name: gitea-keycloak-oidc' \ "$gitea_deployment_document" || \ fail "the Gitea Deployment does not reference gitea-keycloak-oidc key=secret" [[ "$(rg --count-matches --fixed-strings \ 'https://id.learn.hyeonworks.com/realms/hyeonworks/.well-known/openid-configuration' \ "${render_dir}/gitea-oidc.yaml" || true)" == "2" ]] || \ fail "the Gitea OAuth add/update script must use the exact Keycloak discovery URL" # These are non-sensitive app.ini policy values rendered by the pinned Chart. for expected_setting in \ 'ALLOW_ONLY_EXTERNAL_REGISTRATION=true' \ 'DISABLE_REGISTRATION=false' \ 'SHOW_REGISTRATION_BUTTON=false' \ 'ENABLE_PASSWORD_SIGNIN_FORM=true' \ 'ENABLE_AUTO_REGISTRATION=true' \ 'USERNAME=preferred_username' \ 'ACCOUNT_LINKING=login' \ 'OPENID_CONNECT_SCOPES=profile email'; do [[ "$(rg --count-matches --fixed-strings "$expected_setting" \ "${render_dir}/gitea-oidc.yaml" || true)" == "1" ]] || \ fail "the Gitea render must contain exactly one ${expected_setting} setting" done if rg --quiet '^[[:space:]]*hostAliases:' "$gitea_deployment_document"; then fail "the Gitea Deployment must rely on CoreDNS and must not contain hostAliases" fi [[ "$(rg --count-matches \ '^[[:space:]]*cidr:[[:space:]]*192\.168\.0\.107/32[[:space:]]*$' \ "$gitea_keycloak_policy_document" || true)" == "1" ]] || \ fail "the dedicated Keycloak egress policy must allow exactly 192.168.0.107/32" [[ "$(rg --count-matches \ '^[[:space:]]*-[[:space:]]*port:[[:space:]]*443[[:space:]]*$' \ "$gitea_keycloak_policy_document" || true)" == "1" ]] || \ fail "the dedicated Keycloak egress policy must allow exactly TCP port 443" [[ "$(rg --count-matches \ '^[[:space:]]*protocol:[[:space:]]*TCP[[:space:]]*$' \ "$gitea_keycloak_policy_document" || true)" == "1" ]] || \ fail "the dedicated Keycloak egress policy must use TCP" rg --quiet \ '^[[:space:]]*-[[:space:]]*Egress[[:space:]]*$' \ "$gitea_keycloak_policy_document" || \ fail "the dedicated Keycloak NetworkPolicy must select egress traffic" gitea_branding_assets_configmap_name="$( (rg --only-matching --no-filename \ 'gitea-branding-assets-[a-z0-9]+' \ "${render_dir}/gitea-oidc.yaml" || true) | LC_ALL=C sort --unique )" gitea_branding_templates_configmap_name="$( (rg --only-matching --no-filename \ 'gitea-branding-templates-[a-z0-9]+' \ "${render_dir}/gitea-oidc.yaml" || true) | LC_ALL=C sort --unique )" [[ "$gitea_branding_assets_configmap_name" =~ ^gitea-branding-assets-[a-z0-9]{10}$ ]] || \ fail "the Gitea branding assets ConfigMap must have one Kustomize content hash" [[ "$gitea_branding_templates_configmap_name" =~ ^gitea-branding-templates-[a-z0-9]{10}$ ]] || \ fail "the Gitea branding templates ConfigMap must have one Kustomize content hash" extract_rendered_document \ "${render_dir}/gitea-oidc.yaml" \ ConfigMap \ "$gitea_branding_assets_configmap_name" \ "$gitea_branding_assets_document" || \ fail "the Gitea render must contain exactly one branding assets ConfigMap" extract_rendered_document \ "${render_dir}/gitea-oidc.yaml" \ ConfigMap \ "$gitea_branding_templates_configmap_name" \ "$gitea_branding_templates_document" || \ fail "the Gitea render must contain exactly one branding templates ConfigMap" for asset_key in hyeonworks.css logo.svg favicon.svg; do [[ "$(rg --count-matches \ "^[[:space:]]{2}${asset_key//./\\.}:[[:space:]]*\\|[+-]?[[:space:]]*$" \ "$gitea_branding_assets_document" || true)" == "1" ]] || \ fail "the branding assets ConfigMap must contain exactly one ${asset_key}" done for template_key in header.tmpl extra_links.tmpl; do [[ "$(rg --count-matches \ "^[[:space:]]{2}${template_key//./\\.}:[[:space:]]*\\|[+-]?[[:space:]]*$" \ "$gitea_branding_templates_document" || true)" == "1" ]] || \ fail "the branding templates ConfigMap must contain exactly one ${template_key}" done [[ "$(rg --count-matches --fixed-strings \ "$gitea_branding_assets_configmap_name" \ "$gitea_deployment_document" || true)" == "1" ]] || \ fail "the Gitea Deployment must reference the hashed branding assets ConfigMap once" [[ "$(rg --count-matches --fixed-strings \ "$gitea_branding_templates_configmap_name" \ "$gitea_deployment_document" || true)" == "1" ]] || \ fail "the Gitea Deployment must reference the hashed branding templates ConfigMap once" rg --quiet --multiline \ '(?s)- mountPath: /data/gitea/public/assets\n[[:space:]]+name: branding-assets\n[[:space:]]+readOnly: true' \ "$gitea_deployment_document" || \ fail "the Gitea branding assets must be mounted read-only at the official custom path" rg --quiet --multiline \ '(?s)- mountPath: /data/gitea/templates/custom\n[[:space:]]+name: branding-templates\n[[:space:]]+readOnly: true' \ "$gitea_deployment_document" || \ fail "the Gitea branding templates must be mounted read-only at the official custom path" if rg --quiet '^[[:space:]]*type:[[:space:]]*(NodePort|LoadBalancer)[[:space:]]*$' \ "${render_dir}/platform-postgres.yaml" \ "${render_dir}/gitea.yaml" \ "${render_dir}/gitea-oidc.yaml"; then fail "an application service is exposed as NodePort or LoadBalancer" fi if rg --quiet '^[[:space:]]*name:[[:space:]]*gitea-ssh[[:space:]]*$' \ "${render_dir}/gitea.yaml" "${render_dir}/gitea-oidc.yaml"; then fail "the disabled Gitea SSH Service is still rendered" fi if [[ -n "$verified_output_dir" ]]; then for manifest_name in "${RENDERED_MANIFEST_NAMES[@]}"; do source_manifest="${render_dir}/${manifest_name}.yaml" output_manifest="${verified_output_dir}/${manifest_name}.yaml" [[ -f "$source_manifest" && ! -L "$source_manifest" && -s "$source_manifest" ]] || \ fail "validated manifest is missing or unsafe: ${source_manifest}" install -m 0600 -- "$source_manifest" "$output_manifest" [[ -f "$output_manifest" && ! -L "$output_manifest" && -s "$output_manifest" ]] || \ fail "verified manifest handoff failed: ${output_manifest}" cmp --silent -- "$source_manifest" "$output_manifest" || \ fail "verified manifest changed during handoff: ${manifest_name}.yaml" done verified_entry_count="$(find "$verified_output_dir" -mindepth 1 -maxdepth 1 | wc -l | tr -d '[:space:]')" [[ "$verified_entry_count" == "${#RENDERED_MANIFEST_NAMES[@]}" ]] || \ fail "verified output directory does not contain exactly six manifest files" printf 'Preserved six verified manifests for the apply handoff.\n' fi printf 'Phase 1 baseline and Gitea OIDC desired rendering invariants passed.\n' printf 'Temporary rendered manifests and generated chart caches will be removed on exit.\n'