Add platform infrastructure configuration
This commit is contained in:
Executable
+243
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
readonly EXPECTED_HELM_VERSION="v3.19.4"
|
||||
readonly EXPECTED_KUSTOMIZE_VERSION="v5.8.1"
|
||||
readonly KUBE_VERSION="1.36.2"
|
||||
readonly PGADMIN_SHA="0f409becdc6a811e852f384cb53b3f11cfb652a19d77cb952a8a176071694f4c"
|
||||
readonly AISTOR_SHA="50ffa6a4e014cdc48566b237593baab41d039f103cdc50c1b1ac173b8d8bf71e"
|
||||
readonly IMAGE_SHA="sha256:66a300a7ecdcc1f325af0c430315329bca46cd4a7067227d6899802238167c6e"
|
||||
readonly ROOT="$(cd -- "$(dirname -- "$BASH_SOURCE")/../.." && pwd -P)"
|
||||
readonly PG_CACHE="$ROOT/services/pgadmin/.helm/charts/pgadmin4-helm-9.16.0"
|
||||
readonly AI_CACHE="$ROOT/services/minio-aistor/base/deployment/.helm/charts/aistor-objectstore-1.0.16"
|
||||
readonly NAMES="admin-namespace pgadmin-local-pv coredns-custom aistor-admin-oidc pgadmin"
|
||||
|
||||
fail() {
|
||||
printf 'ERROR: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
사용법:
|
||||
PLATFORM_HELM_BIN=/home/donghyeon/.local/bin/helm \
|
||||
bash scripts/validate/render-admin-services.sh
|
||||
|
||||
적용 스크립트 내부 handoff:
|
||||
bash scripts/validate/render-admin-services.sh \
|
||||
--verified-output-dir /tmp/platform-admin-apply.XXXXXX
|
||||
|
||||
고정한 공식 차트의 SHA-256과 렌더 결과를 검사합니다. 변경은 수행하지 않습니다.
|
||||
USAGE
|
||||
}
|
||||
|
||||
out=""
|
||||
if (( $# == 0 )); then
|
||||
:
|
||||
elif (( $# == 2 )) && [[ "$1" == "--verified-output-dir" ]]; then
|
||||
out="$2"
|
||||
else
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for cmd in awk find helm install kubectl rg sed sha256sum stat tar wc tr; do
|
||||
if [[ "$cmd" == helm ]] && [[ -v PLATFORM_HELM_BIN ]]; then
|
||||
continue
|
||||
fi
|
||||
command -v "$cmd" >/dev/null 2>&1 || fail "$cmd 명령이 필요합니다"
|
||||
done
|
||||
|
||||
if [[ -v PLATFORM_HELM_BIN ]]; then
|
||||
[[ "$PLATFORM_HELM_BIN" == /* && -x "$PLATFORM_HELM_BIN" ]] || \
|
||||
fail "PLATFORM_HELM_BIN은 실행 가능한 절대 경로여야 합니다"
|
||||
readonly HELM="$PLATFORM_HELM_BIN"
|
||||
else
|
||||
readonly HELM="$(command -v helm)"
|
||||
fi
|
||||
[[ "$("$HELM" version --template '{{.Version}}')" == "$EXPECTED_HELM_VERSION" ]] || \
|
||||
fail "Helm은 정확히 $EXPECTED_HELM_VERSION이어야 합니다"
|
||||
kv="$(kubectl version --client --output=yaml | sed -n 's/^kustomizeVersion: //p')"
|
||||
[[ "$kv" == "$EXPECTED_KUSTOMIZE_VERSION" ]] || \
|
||||
fail "Kustomize는 정확히 $EXPECTED_KUSTOMIZE_VERSION이어야 합니다"
|
||||
|
||||
if [[ -n "$out" ]]; then
|
||||
[[ "$out" == /tmp/platform-admin-apply.* ]] || fail "handoff 경로 형식이 잘못됐습니다"
|
||||
[[ -d "$out" && ! -L "$out" && -O "$out" ]] || fail "handoff 경로가 안전하지 않습니다"
|
||||
[[ "$(stat --format='%a' "$out")" == 700 ]] || fail "handoff 권한은 0700이어야 합니다"
|
||||
[[ -z "$(find "$out" -mindepth 1 -maxdepth 1 -print -quit)" ]] || fail "handoff 경로가 비어 있지 않습니다"
|
||||
fi
|
||||
|
||||
umask 077
|
||||
work="$(mktemp -d /tmp/platform-admin-render.XXXXXX)"
|
||||
pg_created=false
|
||||
ai_created=false
|
||||
|
||||
cleanup() {
|
||||
rc=$?
|
||||
set +e
|
||||
[[ "$pg_created" == true ]] && rm -rf -- "$PG_CACHE"
|
||||
[[ "$ai_created" == true ]] && rm -rf -- "$AI_CACHE"
|
||||
rmdir -- "$ROOT/services/pgadmin/.helm/charts" "$ROOT/services/pgadmin/.helm" 2>/dev/null || true
|
||||
rmdir -- "$ROOT/services/minio-aistor/base/deployment/.helm/charts" \
|
||||
"$ROOT/services/minio-aistor/base/deployment/.helm" 2>/dev/null || true
|
||||
case "$work" in
|
||||
/tmp/platform-admin-render.*) rm -rf -- "$work" ;;
|
||||
esac
|
||||
exit "$rc"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
prepare() {
|
||||
label="$1"
|
||||
ref="$2"
|
||||
name="$3"
|
||||
version="$4"
|
||||
expected="$5"
|
||||
cache="$6"
|
||||
archive="$work/$name-$version.tgz"
|
||||
|
||||
[[ ! -e "$cache" && ! -L "$cache" ]] || fail "생성 캐시가 이미 있습니다: $cache"
|
||||
if [[ "$ref" == oci://* ]]; then
|
||||
"$HELM" pull "$ref" --version "$version" --destination "$work"
|
||||
else
|
||||
"$HELM" pull "$name" --repo "$ref" --version "$version" --destination "$work"
|
||||
fi
|
||||
[[ -f "$archive" ]] || fail "$label archive가 없습니다"
|
||||
actual="$(sha256sum "$archive" | awk '{print $1}')"
|
||||
[[ "$actual" == "$expected" ]] || fail "$label SHA-256 불일치: $actual"
|
||||
mkdir -p -- "$cache"
|
||||
[[ "$cache" == "$PG_CACHE" ]] && pg_created=true
|
||||
[[ "$cache" == "$AI_CACHE" ]] && ai_created=true
|
||||
tar -xzf "$archive" -C "$cache"
|
||||
[[ -f "$cache/$name/Chart.yaml" ]] || fail "$label Chart.yaml이 없습니다"
|
||||
printf '검증 %-18s SHA-256 %s\n' "$label" "$actual"
|
||||
}
|
||||
|
||||
plain() {
|
||||
kubectl kustomize "$ROOT/$2" >"$work/$1.yaml"
|
||||
[[ -s "$work/$1.yaml" ]] || fail "$1 렌더 결과가 비었습니다"
|
||||
}
|
||||
|
||||
with_helm() {
|
||||
kubectl kustomize --enable-helm --helm-command "$HELM" \
|
||||
--helm-kube-version "$KUBE_VERSION" "$ROOT/$2" >"$work/$1.yaml"
|
||||
[[ -s "$work/$1.yaml" ]] || fail "$1 렌더 결과가 비었습니다"
|
||||
}
|
||||
|
||||
cd -- "$ROOT"
|
||||
if rg -n --glob '*.yaml' --glob '*.yml' --glob '!**/.helm/**' \
|
||||
'^[[:space:]]*kind:[[:space:]]*Secret[[:space:]]*$' infrastructure services bootstrap; then
|
||||
fail "Git 관리 대상에서 Secret manifest를 발견했습니다"
|
||||
fi
|
||||
if rg -n '^[[:space:]]*hostAliases:' services/gitea/profiles/oidc; then
|
||||
fail "Gitea hostAliases가 남아 있습니다"
|
||||
fi
|
||||
|
||||
prepare pgadmin oci://docker.io/dpage/pgadmin4-helm pgadmin4-helm 9.16.0 "$PGADMIN_SHA" "$PG_CACHE"
|
||||
prepare aistor-objectstore https://helm.min.io/ aistor-objectstore 1.0.16 "$AISTOR_SHA" "$AI_CACHE"
|
||||
|
||||
plain admin-namespace infrastructure/namespaces/admin-tools
|
||||
plain pgadmin-local-pv infrastructure/storage/pgadmin-local-pv
|
||||
plain coredns-custom infrastructure/networking/private-dns/kubernetes
|
||||
with_helm aistor-admin-oidc services/minio-aistor/profiles/admin-oidc
|
||||
with_helm pgadmin services/pgadmin
|
||||
|
||||
if rg -n '^[[:space:]]*kind:[[:space:]]*Secret[[:space:]]*$' "$work"/*.yaml; then
|
||||
fail "렌더 결과에 Secret 리소스가 있습니다"
|
||||
fi
|
||||
[[ "$(rg -c -F "image: docker.io/dpage/pgadmin4@$IMAGE_SHA" "$work/pgadmin.yaml")" == 3 ]] || \
|
||||
fail "pgAdmin main/init 이미지 세 개의 digest 고정이 깨졌습니다"
|
||||
rg -q -F 'type: Recreate' "$work/pgadmin.yaml" || fail "pgAdmin Recreate 전략이 없습니다"
|
||||
rg -q -F 'storageClassName: ssd-local-pgadmin-retain' "$work/pgadmin.yaml" || fail "pgAdmin StorageClass가 다릅니다"
|
||||
[[ "$(rg -c -F 'value: db-admin.learn.hyeonworks.com' "$work/pgadmin.yaml")" == 3 ]] || \
|
||||
fail "pgAdmin 세 probe의 Host header가 고정되지 않았습니다"
|
||||
rg -q -F 'host: db-admin.learn.hyeonworks.com' "$work/pgadmin.yaml" || fail "pgAdmin Ingress가 없습니다"
|
||||
[[ "$(rg -c -F 'ingressClassName: traefik' "$work/pgadmin.yaml")" == 1 ]] || \
|
||||
fail "pgAdmin IngressClass가 traefik으로 고정되지 않았습니다"
|
||||
if rg -q -F 'kubernetes.io/ingress.class' "$work/pgadmin.yaml"; then
|
||||
fail "pgAdmin Ingress에 레거시 ingress.class annotation이 남아 있습니다"
|
||||
fi
|
||||
rg -q -F 'namespace: platform-data' "$work/pgadmin.yaml" || fail "PostgreSQL 정책 namespace가 다릅니다"
|
||||
bad_pgadmin_namespaces="$(
|
||||
awk '
|
||||
function verify() {
|
||||
if (pgadmin_instance && namespace != "platform-admin") {
|
||||
printf "%s/%s=%s\n", kind, name, namespace == "" ? "missing" : namespace
|
||||
}
|
||||
}
|
||||
function reset() {
|
||||
kind=""
|
||||
name=""
|
||||
namespace=""
|
||||
in_metadata=0
|
||||
in_labels=0
|
||||
pgadmin_instance=0
|
||||
}
|
||||
/^---$/ {
|
||||
verify()
|
||||
reset()
|
||||
next
|
||||
}
|
||||
/^kind:/ {
|
||||
kind=$2
|
||||
next
|
||||
}
|
||||
/^metadata:$/ {
|
||||
in_metadata=1
|
||||
in_labels=0
|
||||
next
|
||||
}
|
||||
in_metadata && /^ name:/ && name == "" {
|
||||
name=$2
|
||||
next
|
||||
}
|
||||
in_metadata && /^ namespace:/ && namespace == "" {
|
||||
namespace=$2
|
||||
next
|
||||
}
|
||||
in_metadata && /^ labels:$/ {
|
||||
in_labels=1
|
||||
next
|
||||
}
|
||||
in_labels && /^ app.kubernetes.io\/instance: pgadmin$/ {
|
||||
pgadmin_instance=1
|
||||
next
|
||||
}
|
||||
in_metadata && /^[^ ]/ {
|
||||
in_metadata=0
|
||||
in_labels=0
|
||||
}
|
||||
END {
|
||||
verify()
|
||||
}
|
||||
' "$work/pgadmin.yaml"
|
||||
)"
|
||||
[[ -z "$bad_pgadmin_namespaces" ]] || \
|
||||
fail "pgAdmin instance 리소스 namespace 오류: $bad_pgadmin_namespaces"
|
||||
rg -q -F 'host: storage-admin.learn.hyeonworks.com' "$work/aistor-admin-oidc.yaml" || fail "AIStor Console Ingress가 없습니다"
|
||||
rg -q -F 'name: MINIO_IDENTITY_OPENID_CLIENT_SECRET' "$work/aistor-admin-oidc.yaml" || fail "AIStor OIDC Secret 참조가 없습니다"
|
||||
if rg -q -F 'name: MINIO_IDENTITY_OPENID_VENDOR' "$work/aistor-admin-oidc.yaml"; then
|
||||
fail "claim-only OIDC profile에 Keycloak Admin API vendor 설정이 있습니다"
|
||||
fi
|
||||
if rg -q -F 'host: s3.' "$work/aistor-admin-oidc.yaml"; then
|
||||
fail "S3 API Ingress가 생성됐습니다"
|
||||
fi
|
||||
|
||||
for name in $NAMES; do
|
||||
bytes="$(wc -c <"$work/$name.yaml" | tr -d '[:space:]')"
|
||||
sha="$(sha256sum "$work/$name.yaml" | awk '{print $1}')"
|
||||
printf '렌더 %-20s %8s bytes SHA-256 %s\n' "$name" "$bytes" "$sha"
|
||||
if [[ -n "$out" ]]; then
|
||||
install -m 0600 "$work/$name.yaml" "$out/$name.yaml"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$out" ]]; then
|
||||
count="$(find "$out" -mindepth 1 -maxdepth 1 -type f | wc -l | tr -d '[:space:]')"
|
||||
[[ "$count" == 5 ]] || fail "handoff 파일 수가 다릅니다"
|
||||
fi
|
||||
printf 'ADMIN SERVICES STATIC RENDER PASS\n'
|
||||
Reference in New Issue
Block a user