#!/usr/bin/env bash # Render every declared entrypoint, then run available schema/policy/shell gates. # CI sets VALIDATION_PROFILE=full so a missing validator is a hard failure. set -Eeuo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=../lib/common.sh . "$SCRIPT_DIR/../lib/common.sh" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" ENTRYPOINTS_FILE="$REPO_ROOT/tests/kustomize-entrypoints.txt" KUBE_LINTER_CFG="$REPO_ROOT/.kube-linter.yaml" VALIDATION_PROFILE="${VALIDATION_PROFILE:-local}" CRD_CATALOG='https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' WORK_DIR="$(mktemp -d -t project-infra-validate.XXXXXX)" trap_cleanup_path "$WORK_DIR" tool_available() { command -v "$1" >/dev/null 2>&1 } require_full_tool() { local tool="$1" if tool_available "$tool"; then return 0 fi [[ "$VALIDATION_PROFILE" != "full" ]] \ || die "VALIDATION_PROFILE=full: 필수 도구가 없습니다: $tool" warn "$tool 없음: 해당 검증을 skip합니다. 'mise install' 또는 CI를 사용하세요." return 1 } render_kustomization() { local entrypoint="$1" local output="$2" if tool_available kustomize; then kustomize build "$entrypoint" >"$output" elif tool_available kubectl; then kubectl kustomize "$entrypoint" >"$output" else die "kustomize 또는 kubectl 중 하나가 필요합니다." fi } load_entrypoints() { [[ -f "$ENTRYPOINTS_FILE" ]] || die "entrypoint inventory 없음: $ENTRYPOINTS_FILE" mapfile -t ENTRYPOINTS < <( sed -e 's/#.*$//' -e '/^[[:space:]]*$/d' "$ENTRYPOINTS_FILE" ) ((${#ENTRYPOINTS[@]} > 0)) || die "검증할 entrypoint가 없습니다." } validate_manifests() { local rel entrypoint rendered failed=0 local have_kubeconform=false have_kube_linter=false require_full_tool kubeconform && have_kubeconform=true require_full_tool kube-linter && have_kube_linter=true for rel in "${ENTRYPOINTS[@]}"; do entrypoint="$REPO_ROOT/$rel" [[ -f "$entrypoint/kustomization.yaml" ]] || { err "$rel: kustomization.yaml 없음" failed=$((failed + 1)) continue } rendered="$WORK_DIR/$(printf '%s' "$rel" | tr '/' '_').yaml" if ! render_kustomization "$entrypoint" "$rendered" 2>"$rendered.err"; then err "$rel: render 실패" sed 's/^/ /' "$rendered.err" >&2 failed=$((failed + 1)) continue fi log "$rel: render=ok ($(grep -c '^kind:' "$rendered") resources)" if grep -Eq 'replace-in-overlay|__REPLACE_ME_[A-Z0-9_]+' "$rendered"; then err "$rel: unresolved placeholder가 render 결과에 남아 있습니다." grep -En 'replace-in-overlay|__REPLACE_ME_[A-Z0-9_]+' "$rendered" \ | sed 's/^/ /' >&2 failed=$((failed + 1)) fi if [[ "$have_kubeconform" == "true" ]]; then if ! kubeconform \ -strict \ -ignore-missing-schemas \ -schema-location default \ -schema-location "$CRD_CATALOG" \ -summary \ "$rendered" >"$rendered.kubeconform" 2>&1; then err "$rel: kubeconform 실패" sed 's/^/ /' "$rendered.kubeconform" >&2 failed=$((failed + 1)) fi fi if [[ "$have_kube_linter" == "true" ]]; then if ! kube-linter lint \ --config "$KUBE_LINTER_CFG" \ "$rendered" >"$rendered.kube-linter" 2>&1; then err "$rel: kube-linter 실패" sed 's/^/ /' "$rendered.kube-linter" >&2 failed=$((failed + 1)) fi fi done ((failed == 0)) || return 1 } validate_shell() { local failed=0 mapfile -t shell_files < <( find "$REPO_ROOT/scripts" -type f -name '*.sh' -print | sort ) if require_full_tool shellcheck; then shellcheck -S style -x -P SCRIPTDIR "${shell_files[@]}" \ || failed=$((failed + 1)) fi if require_full_tool shfmt; then shfmt -i 2 -bn -ci -d "${shell_files[@]}" || failed=$((failed + 1)) fi ((failed == 0)) || return 1 } validate_secrets() { if require_full_tool gitleaks; then local scan_dir="$WORK_DIR/gitleaks-source" mkdir -p "$scan_dir" ( cd "$REPO_ROOT" git ls-files -z --cached --others --exclude-standard \ | while IFS= read -r -d '' source_file; do if [[ -f "$source_file" ]]; then printf '%s\0' "$source_file" fi done \ | tar --null --files-from=- --create --file=- ) | tar --extract --file=- --directory="$scan_dir" gitleaks dir "$scan_dir" \ --no-banner \ --redact \ --config "$REPO_ROOT/.gitleaks.toml" fi } main() { load_entrypoints local failed=0 validate_manifests || failed=$((failed + 1)) validate_shell || failed=$((failed + 1)) validate_secrets || failed=$((failed + 1)) bash "$SCRIPT_DIR/validate-docs.sh" || failed=$((failed + 1)) ((failed == 0)) || die "$failed validation group(s) 실패" log "모든 validation gate 통과 (profile=$VALIDATION_PROFILE)" } main "$@"