#!/usr/bin/env bash # Remove an environment in reverse dependency order. # # Default scope removes only application and one-shot operation resources. # Stateful data, Vault, namespaces, CRDs, and shared operators require explicit # opt-in flags. 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)" GITOPS_ROOT="$REPO_ROOT/gitops" NAMESPACE="mnt" VSO_NAMESPACE="${VSO_NAMESPACE:-vault-secrets-operator-system}" VSO_RELEASE="${VSO_RELEASE:-vault-secrets-operator}" MINIO_OPERATOR_NAMESPACE="${MINIO_OPERATOR_NAMESPACE:-minio-operator}" MINIO_OPERATOR_RELEASE="${MINIO_OPERATOR_RELEASE:-minio-operator}" NAMESPACE_DELETE_INCOMPLETE=0 usage() { cat >&2 <<'EOF' Usage: scripts/bin/teardown.sh Default: Remove stages 50-apps and 40-operations only. Options: DELETE_DATA=yes Also remove data, VSO CRs, Vault, and app namespace TEARDOWN_PLATFORM=yes Also remove shared Helm operators and stage 00 (requires DELETE_DATA=yes) FORCE_FINALIZERS=yes Last-resort namespace finalizer removal (requires DELETE_DATA=yes) ALLOW_PROD_DESTRUCTIVE=yes Required for prod CONFIRM=yes Non-interactive confirmation EOF exit 1 } delete_entrypoint() { local label="$1" local entrypoint="$2" [[ -f "$entrypoint/kustomization.yaml" ]] || { log "[$label] entrypoint 없음: skip" return 0 } log "[$label] delete" kubectl delete -k "$entrypoint" \ --ignore-not-found \ --wait=true \ --timeout=300s } delete_application_scope() { delete_entrypoint "50-apps" "$CLUSTER_ROOT/stages/50-apps" delete_entrypoint "40-operations" "$CLUSTER_ROOT/stages/40-operations" } delete_data_scope() { [[ "${DELETE_DATA:-}" == "yes" ]] || { log "DELETE_DATA 미지정: data / secrets / Vault / namespace 보존" return 0 } delete_entrypoint "35-registry" "$CLUSTER_ROOT/stages/35-registry" delete_entrypoint "30-data" "$CLUSTER_ROOT/stages/30-data" delete_entrypoint "20-secrets" "$CLUSTER_ROOT/stages/20-secrets" delete_entrypoint "10-vault" "$CLUSTER_ROOT/stages/10-vault" log "namespace/$NAMESPACE 삭제" if ! kubectl delete namespace "$NAMESPACE" \ --ignore-not-found \ --wait=true \ --timeout=300s; then warn "namespace 삭제가 제한 시간 안에 완료되지 않았습니다." NAMESPACE_DELETE_INCOMPLETE=1 fi } force_finalize_if_requested() { [[ "${FORCE_FINALIZERS:-}" == "yes" ]] || return 0 [[ "${DELETE_DATA:-}" == "yes" ]] \ || die "FORCE_FINALIZERS=yes 는 DELETE_DATA=yes 와 함께만 사용할 수 있습니다." ns_exists "$NAMESPACE" || return 0 warn "FORCE_FINALIZERS=yes: orphaned volume/controller state가 생길 수 있습니다." confirm "namespace/$NAMESPACE finalizer를 강제로 제거합니까?" || die "사용자 취소" strip_finalizers_all_ns_resources "$NAMESPACE" force_finalize_namespace "$NAMESPACE" wait_namespace_gone "$NAMESPACE" 120 \ || die "namespace/$NAMESPACE 강제 finalize 후에도 남아 있습니다." NAMESPACE_DELETE_INCOMPLETE=0 } delete_platform_scope() { [[ "${TEARDOWN_PLATFORM:-}" == "yes" ]] || { log "TEARDOWN_PLATFORM 미지정: 공유 operator / CRD / stage 00 보존" return 0 } [[ "${DELETE_DATA:-}" == "yes" ]] \ || die "TEARDOWN_PLATFORM=yes 는 DELETE_DATA=yes 와 함께만 사용할 수 있습니다." warn "공유 controller와 cluster-scoped 리소스를 제거합니다." confirm "이 클러스터가 다른 환경과 공유되지 않음을 확인했습니까?" || die "사용자 취소" helm -n "$VSO_NAMESPACE" uninstall "$VSO_RELEASE" --wait --timeout=5m \ 2>/dev/null || warn "VSO Helm release가 없거나 uninstall에 실패했습니다." helm -n "$MINIO_OPERATOR_NAMESPACE" uninstall "$MINIO_OPERATOR_RELEASE" --wait --timeout=5m \ 2>/dev/null || warn "MinIO Operator Helm release가 없거나 uninstall에 실패했습니다." delete_entrypoint "00-platform" "$CLUSTER_ROOT/stages/00-platform" kubectl delete namespace "$VSO_NAMESPACE" "$MINIO_OPERATOR_NAMESPACE" \ --ignore-not-found \ --wait=true \ --timeout=300s \ || warn "operator namespace 삭제가 완료되지 않았습니다." } main() { ENV_NAME="${1:-}" case "$ENV_NAME" in lab | staging | prod) ;; *) usage ;; esac CLUSTER_ROOT="$GITOPS_ROOT/clusters/$ENV_NAME/main" [[ -f "$CLUSTER_ROOT/all/kustomization.yaml" ]] \ || die "클러스터 entrypoint가 없습니다: $CLUSTER_ROOT" require_cmd kubectl helm jq require_kube_context "$ENV_NAME" require_production_gate "$ENV_NAME" "$NAMESPACE" local scope="apps + one-shot operations" [[ "${DELETE_DATA:-}" == "yes" ]] && scope="$scope + data + Vault + namespace" [[ "${TEARDOWN_PLATFORM:-}" == "yes" ]] && scope="$scope + shared platform" log "Project-Infra teardown: env=$ENV_NAME context=$KUBE_CONTEXT_LOCKED" confirm "삭제 범위: $scope. 계속합니까?" || die "사용자 취소" delete_application_scope delete_data_scope force_finalize_if_requested delete_platform_scope if ((NAMESPACE_DELETE_INCOMPLETE == 1)) && ns_exists "$NAMESPACE"; then die "$ENV_NAME teardown 미완료: namespace/$NAMESPACE 가 남아 있습니다." fi log "$ENV_NAME teardown 완료: $scope" } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi