Files
project-infra/docs/superpowers/plans/2026-08-02-project-infra-phase-1-dev-gitops.md
T

582 lines
22 KiB
Markdown

# Project Infra Phase 1 dev GitOps Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to execute this plan task by task. Use `superpowers:test-driven-development` for behavior changes and `superpowers:verification-before-completion` before reporting success.
**Goal:** Rename the active rollout from `lab` to `dev`, make operator desired state render from Git, and correct the ForwardAuth/Keycloak ownership defects without changing a live cluster.
**Architecture:** The catalog remains ownership-first and environment-neutral in `base`, while `overlays/dev` contains dev-only values. `gitops/clusters/dev/main` is the only current deployable Kubernetes root. A shared render function always enables Kustomize Helm support. Operator charts live in independent platform units and are selected by a dedicated ordered stage. This plan only changes Git-owned source and local tests; it must not invoke `kubectl apply`, `kubectl delete`, or Helm mutation commands.
**Tech Stack:** Bash, Kustomize 5.8.1, Helm 3.19.4, Kubernetes/K3s manifests, repository shell test harness, `rg`.
## Global constraints
- Read `/AGENTS.md`, the nearest nested `AGENTS.md`, `docs/standards/infra/STYLE.md`, and the directly relevant standards before each manifest task.
- Preserve `mnt`, `minio-operator`, and `vault-secrets-operator-system` namespace names in Phase 1.
- Preserve `.local.test` hostnames as dev-only values.
- Never apply `gitops/clusters/dev/main/all`; it is render/audit-only.
- Do not run a live-cluster mutation command while executing this plan.
- Do not edit CI workflow definitions or the sibling `cicd-platform` repository.
- Commit only the files listed by the current task and inspect `git status --short` before each commit.
## Dependency and interface contract
This plan is executed before the automation-safety and validation-structure plans.
- `tests/run.sh [name-fragment]` discovers executable `tests/contracts/*-test.sh` files and optionally filters by basename.
- `tests/lib/assert.sh` provides `fail`, `assert_eq`, `assert_file_exists`, `assert_file_absent`, `assert_contains`, and `assert_not_contains`.
- `scripts/lib/kustomize.sh` exports `kustomize_render ENTRYPOINT`; rendered YAML is written only to stdout and diagnostics only to stderr.
- Every later render path must call `kustomize_render`; no later script may reimplement `kustomize build` or `kubectl kustomize`.
- The ordered dev stages are `00-platform`, `05-operators`, `10-vault`, `20-secrets`, `30-data`, `35-registry`, `40-operations`, and `50-apps`.
---
## Task 1: Add the contract-test harness and rename the active environment
**Files:**
- Create: `tests/lib/assert.sh`
- Create: `tests/run.sh`
- Create: `tests/contracts/dev-layout-test.sh`
- Move: `gitops/clusters/lab/main``gitops/clusters/dev/main`
- Move: `gitops/apps/auth-migration/overlays/lab``gitops/apps/auth-migration/overlays/dev`
- Move: `gitops/apps/auth-server/overlays/lab``gitops/apps/auth-server/overlays/dev`
- Move: `gitops/apps/identity-postgres/overlays/lab``gitops/apps/identity-postgres/overlays/dev`
- Move: `gitops/apps/keycloak-realm-import/overlays/lab``gitops/apps/keycloak-realm-import/overlays/dev`
- Move: `gitops/platform/cert-manager/overlays/lab``gitops/platform/cert-manager/overlays/dev`
- Move: `gitops/platform/forward-auth/overlays/lab``gitops/platform/forward-auth/overlays/dev`
- Move: `gitops/platform/keycloak-operator/overlays/lab``gitops/platform/keycloak-operator/overlays/dev`
- Move: `gitops/platform/keycloak/overlays/lab``gitops/platform/keycloak/overlays/dev`
- Move: `gitops/platform/minio/overlays/lab``gitops/platform/minio/overlays/dev`
- Move: `gitops/platform/registry/overlays/lab``gitops/platform/registry/overlays/dev`
- Move: `gitops/platform/secret-delivery/overlays/lab``gitops/platform/secret-delivery/overlays/dev`
- Move: `gitops/platform/traefik/overlays/lab``gitops/platform/traefik/overlays/dev`
- Move: `gitops/platform/vault/overlays/lab``gitops/platform/vault/overlays/dev`
- Move: `gitops/policies/baseline/overlays/lab``gitops/policies/baseline/overlays/dev`
- Modify: moved Kustomization files, `tests/kustomize-entrypoints.txt`
- Modify: `AGENTS.md`, `README.md`, `guide.md`, `gitops/AGENTS.md`, `gitops/PROJECT.md`, `gitops/clusters/AGENTS.md`, `gitops/clusters/README.md`, `gitops/clusters/dev/main/README.md`, `docs/architecture.md`, `docs/architecture/repository-structure.md`, `docs/guides/getting-started.md`, `docs/ingress-traefik.md`, `docs/operations.md`, `docs/security-hardening.md`, `docs/troubleshooting.md`, `docs/vault-vso.md`, `scripts/bin/bootstrap.sh`, `scripts/bin/teardown.sh`, `scripts/tasks/minio-provision-registry.sh`, `scripts/ci/validate-docs.sh`, `scripts/README.md`, `tests/README.md`
**Step 1: Write the failing layout contract**
Use strict mode in every new script. The runner must execute each selected test in a fresh Bash process and return non-zero if any test fails:
```bash
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
filter="${1:-}"
failed=0
while IFS= read -r test_file; do
[[ -z "$filter" || "$(basename "$test_file")" == *"$filter"* ]] || continue
bash "$test_file" || failed=$((failed + 1))
done < <(find "$ROOT_DIR/tests/contracts" -type f -name '*-test.sh' -print | sort)
((failed == 0))
```
`dev-layout-test.sh` must assert all of these conditions:
```text
gitops/clusters/dev/main exists
gitops/clusters/lab does not exist
every selected unit has overlays/dev and no overlays/lab
tests/kustomize-entrypoints.txt contains gitops/clusters/dev/main
tests/kustomize-entrypoints.txt does not contain gitops/clusters/lab
no deployable kustomization contains example.com/environment: lab
the active docs listed above do not instruct users to deploy lab
```
**Step 2: Run the contract and verify that it fails for the current tree**
Run:
```bash
bash tests/run.sh dev-layout
```
Expected: non-zero, with the first failure reporting the missing `gitops/clusters/dev/main` path.
**Step 3: Move the directories with Git-aware renames**
Run one `git mv` per source directory. Do not use recursive copy/delete. After the moves, replace only active environment references:
```bash
rg -l 'clusters/lab|overlays/lab|environment: lab|\blab\b' \
AGENTS.md README.md guide.md gitops scripts docs tests \
--glob '!docs/standards/**' \
--glob '!docs/examples/**' \
--glob '!docs/superpowers/**'
```
Review every hit and change current deployment semantics from `lab` to `dev`, including bootstrap/teardown accepted environment names and MinIO's dev-only HTTP default. Preserve historical text in approved design/spec documents and normative examples that intentionally compare environments. Change `gitops/AGENTS.md` base reuse wording to name `dev`, `staging`, and `prod`; it may still describe `lab` only as an optional disposable environment.
**Step 4: Update the moved cluster root**
All resource paths under `gitops/clusters/dev/main/stages/*/kustomization.yaml` must select `overlays/dev`. Every stage label must be:
```yaml
labels:
- pairs:
example.com/environment: dev
example.com/owner-team: platform
includeSelectors: false
includeTemplates: true
```
Remove `../../../../../tenants/mnt/base` from `stages/00-platform`; namespace ownership belongs only to the separate `namespaces` entrypoint.
Update `tests/kustomize-entrypoints.txt` to contain only dev cluster entrypoints. Keep `all/` in the render inventory, but mark it audit-only in the cluster README and tests README.
**Step 5: Run the test and documentation gate**
Run:
```bash
bash tests/run.sh dev-layout
bash scripts/ci/validate-docs.sh
git diff --check
```
Expected: all commands exit 0.
**Step 6: Commit**
```bash
git add tests gitops AGENTS.md README.md guide.md docs scripts/bin/bootstrap.sh scripts/bin/teardown.sh scripts/tasks/minio-provision-registry.sh scripts/ci/validate-docs.sh scripts/README.md
git commit -m "refactor: dev 환경 경로로 전환"
```
---
## Task 2: Introduce one Helm-aware Kustomize render interface
**Files:**
- Create: `scripts/lib/kustomize.sh`
- Create: `tests/contracts/kustomize-render-test.sh`
- Modify: `scripts/ci/validate.sh`
- Modify: `scripts/lib/common.sh` only if it is needed to share `REPO_ROOT` without changing cluster behavior
**Step 1: Write the failing fake-command test**
The test creates a temporary `PATH` with fake `kustomize` and `kubectl` executables. It must prove:
1. when `kustomize` exists, the helper calls exactly `kustomize build --enable-helm ENTRYPOINT`;
2. when only `kubectl` exists, it calls exactly `kubectl kustomize ENTRYPOINT --enable-helm`;
3. when neither exists, it exits non-zero;
4. rendered YAML remains on stdout and the command trace remains outside stdout.
Run:
```bash
bash tests/run.sh kustomize-render
```
Expected: non-zero because `scripts/lib/kustomize.sh` does not yet exist.
**Step 2: Implement the shared helper**
Use this public interface:
```bash
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
kustomize_render() {
local entrypoint="${1:?entrypoint is required}"
if command -v kustomize >/dev/null 2>&1; then
kustomize build --enable-helm "$entrypoint"
elif command -v kubectl >/dev/null 2>&1; then
kubectl kustomize "$entrypoint" --enable-helm
else
printf 'ERROR: kustomize 또는 kubectl이 필요합니다.\n' >&2
return 1
fi
}
```
Source this file from `scripts/ci/validate.sh` and delete its local `render_kustomization` implementation. Change the validation call to:
```bash
kustomize_render "$entrypoint" >"$rendered"
```
**Step 3: Verify the helper and current dev renders**
Run:
```bash
bash tests/run.sh kustomize-render
VALIDATION_PROFILE=local bash scripts/ci/validate.sh
git diff --check
```
Expected: all commands exit 0; render log lines identify dev entrypoints.
**Step 4: Commit**
```bash
git add scripts/lib/kustomize.sh scripts/ci/validate.sh tests/contracts/kustomize-render-test.sh
git commit -m "refactor: Helm 지원 렌더 경로 통합"
```
---
## Task 3: Add declarative operator namespaces and catalog units
**Files:**
- Create: `gitops/tenants/minio-operator/base/kustomization.yaml`
- Create: `gitops/tenants/minio-operator/base/namespace.yaml`
- Create: `gitops/tenants/vault-secrets-operator-system/base/kustomization.yaml`
- Create: `gitops/tenants/vault-secrets-operator-system/base/namespace.yaml`
- Create: `gitops/platform/minio-operator/base/kustomization.yaml`
- Create: `gitops/platform/minio-operator/overlays/dev/kustomization.yaml`
- Create: `gitops/platform/minio-operator/overlays/dev/values.yaml`
- Create: `gitops/platform/vault-secrets-operator/base/kustomization.yaml`
- Create: `gitops/platform/vault-secrets-operator/overlays/dev/kustomization.yaml`
- Move: `gitops/platform/secret-delivery/base/helm/values.yaml``gitops/platform/vault-secrets-operator/overlays/dev/values.yaml`
- Create: `gitops/clusters/dev/main/stages/05-operators/kustomization.yaml`
- Modify: `gitops/clusters/dev/main/namespaces/kustomization.yaml`
- Modify: `gitops/clusters/dev/main/all/kustomization.yaml`
- Modify: `tests/kustomize-entrypoints.txt`
- Create: `tests/contracts/operator-render-test.sh`
- Modify: `gitops/platform/README.md`, `gitops/tenants/README.md`, `gitops/clusters/dev/main/README.md`
**Step 1: Write the failing operator render contract**
Render `gitops/clusters/dev/main/stages/05-operators` through `kustomize_render`. Assert:
```text
the render exits 0
at least two Deployment documents exist
tenants.minio.min.io exists as a CustomResourceDefinition name
vaultstaticsecrets.secrets.hashicorp.com exists as a CustomResourceDefinition name
the namespaces entrypoint renders Namespace/minio-operator and Namespace/vault-secrets-operator-system
every namespaced resource in the operator stage targets minio-operator or vault-secrets-operator-system
no HelmRelease custom resource is present
chart versions 7.0.0 and 0.9.0 are pinned in source
includeCRDs: true is present in both dev overlay kustomizations
```
Run:
```bash
bash tests/run.sh operator-render
```
Expected: non-zero because the operator stage does not exist.
**Step 2: Add namespace ownership units**
Each Namespace must use the standard labels from `gitops/tenants/mnt/base/namespace.yaml`, with `app.kubernetes.io/name` and `app.kubernetes.io/instance` set to its namespace. Apply the `restricted` Pod Security labels. Each base Kustomization contains only its `namespace.yaml`.
Add both bases to `gitops/clusters/dev/main/namespaces/kustomization.yaml` after `mnt`.
**Step 3: Add the MinIO Operator unit**
The dev overlay Kustomization must be exactly shaped as follows:
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
helmCharts:
- name: operator
repo: https://operator.min.io
version: 7.0.0
releaseName: minio-operator
namespace: minio-operator
includeCRDs: true
valuesFile: values.yaml
```
Use only chart-supported keys from the pinned MinIO Operator 7.0.0 values schema. `values.yaml` must be:
```yaml
operator:
replicaCount: 1
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containerSecurityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
resources:
requests:
cpu: 200m
memory: 256Mi
ephemeral-storage: 500Mi
limits:
memory: 384Mi
ephemeral-storage: 1Gi
```
The single replica is a dev overlay decision. A later staging/prod overlay must reassess replicas, PDB, and topology from measured capacity.
**Step 4: Add the Vault Secrets Operator unit**
The dev overlay Kustomization must pin:
```yaml
helmCharts:
- name: vault-secrets-operator
repo: https://helm.releases.hashicorp.com
version: 0.9.0
releaseName: vault-secrets-operator
namespace: vault-secrets-operator-system
includeCRDs: true
valuesFile: values.yaml
```
Move the existing hardened VSO values file unchanged first. Remove the now-empty `gitops/platform/secret-delivery/base/helm` directory from Git. Keep secret-delivery CRs in their existing unit; only the controller chart moves.
**Step 5: Add the ordered stage and inventory**
`gitops/clusters/dev/main/stages/05-operators/kustomization.yaml` contains exactly the two dev overlays and the normal dev/platform labels. Insert it between `00-platform` and `10-vault` in `all/kustomization.yaml` and in `tests/kustomize-entrypoints.txt`.
Do not add these operator overlays to `00-platform`.
**Step 6: Verify render, schema, and policy**
Run:
```bash
bash tests/run.sh operator-render
VALIDATION_PROFILE=full bash scripts/ci/validate.sh
git diff --check
```
Expected: operator test exits 0; full validation reports no invalid schema or policy result. If chart download is unavailable, stop and report the external availability failure rather than committing an unrendered chart definition.
**Step 7: Commit**
```bash
git add gitops/tenants gitops/platform/minio-operator gitops/platform/vault-secrets-operator gitops/platform/secret-delivery gitops/clusters/dev/main tests
git commit -m "feat: operator desired state를 GitOps로 선언"
```
---
## Task 4: Put ForwardAuth resources and patches under their real owners
**Files:**
- Move: `gitops/platform/forward-auth/component/oauth2-proxy-config.yaml``gitops/platform/forward-auth/overlays/dev/oauth2-proxy-config.yaml`
- Move: `gitops/platform/forward-auth/component/oauth2-proxy-ingress.yaml``gitops/platform/forward-auth/overlays/dev/oauth2-proxy-ingress.yaml`
- Move: `gitops/platform/forward-auth/component/oauth2-proxy-middleware.yaml``gitops/platform/forward-auth/overlays/dev/oauth2-proxy-middleware.yaml`
- Move: `gitops/platform/forward-auth/component/oauth2-proxy-networkpolicy.yaml``gitops/platform/forward-auth/overlays/dev/oauth2-proxy-networkpolicy.yaml`
- Delete: `gitops/platform/forward-auth/component/kustomization.yaml`
- Modify: `gitops/platform/forward-auth/overlays/dev/kustomization.yaml`
- Modify: `gitops/apps/auth-server/base/configmap.yaml`
- Modify: `gitops/apps/auth-server/overlays/dev/kustomization.yaml`
- Modify: `gitops/platform/keycloak/overlays/dev/kustomization.yaml`
- Delete: `gitops/platform/keycloak/overlays/dev/ingress-admin.yaml`
- Create: `tests/contracts/dev-ingress-render-test.sh`
**Step 1: Write the failing rendered-output assertions**
Render `gitops/clusters/dev/main/stages/50-apps` and `00-platform`. Extract resources by `kind` and `metadata.name` without assuming document order. Assert:
```text
Ingress/auth-server annotation is exactly:
mnt-oauth2-proxy-errors@kubernetescrd,mnt-oauth2-proxy-auth@kubernetescrd,kube-system-security-headers@kubernetescrd
Ingress/keycloak exists
Ingress/keycloak-admin does not exist
ConfigMap/auth-server-config in the app stage contains SPRING_PROFILES_ACTIVE=dev
ConfigMap/auth-server-config rendered from gitops/apps/auth-server/base does not contain SPRING_PROFILES_ACTIVE
the 00-platform render contains oauth2-proxy ConfigMap, Ingress, Middleware resources, and NetworkPolicy
```
Run:
```bash
bash tests/run.sh dev-ingress-render
```
Expected: non-zero because the current auth-server annotation has only the security middleware.
**Step 2: Refactor the ForwardAuth overlay**
Change the dev overlay from a Component reference to normal ownership:
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mnt
resources:
- ../../base
- oauth2-proxy-config.yaml
- oauth2-proxy-ingress.yaml
- oauth2-proxy-middleware.yaml
- oauth2-proxy-networkpolicy.yaml
```
Delete the component directory after all resources are moved. The platform stage continues to select only `forward-auth/overlays/dev`.
**Step 3: Move the auth-server patch to the auth-server owner**
Add this patch to `gitops/apps/auth-server/overlays/dev/kustomization.yaml`:
```yaml
- target:
kind: Ingress
name: auth-server
patch: |-
- op: replace
path: /metadata/annotations/traefik.ingress.kubernetes.io~1router.middlewares
value: mnt-oauth2-proxy-errors@kubernetescrd,mnt-oauth2-proxy-auth@kubernetescrd,kube-system-security-headers@kubernetescrd
```
Remove `SPRING_PROFILES_ACTIVE` from the base ConfigMap and add it through the existing dev ConfigMap patch.
**Step 4: Remove the dev admin Ingress from desired state**
Remove `ingress-admin.yaml` from the Keycloak dev Kustomization and delete the file. Do not run `kubectl delete`; live cleanup belongs to Phase 2.
**Step 5: Verify exact rendered behavior**
Run:
```bash
bash tests/run.sh dev-ingress-render
VALIDATION_PROFILE=full bash scripts/ci/validate.sh
git diff --check
```
Expected: all commands exit 0.
**Step 6: Commit**
```bash
git add gitops/platform/forward-auth gitops/apps/auth-server gitops/platform/keycloak tests/contracts/dev-ingress-render-test.sh
git commit -m "fix: dev ForwardAuth 소유 경계 수정"
```
---
## Task 5: Record the environment and CI ownership decisions
**Files:**
- Create: `docs/adr/0002-dev-environment-and-external-ci-ownership.md`
- Modify: `docs/architecture/repository-structure.md`
- Modify: `README.md`
- Modify: `docs/superpowers/specs/2026-08-02-project-infra-dev-structure-refactor-design.md`
- Modify: `scripts/ci/validate-docs.sh`
**Step 1: Extend the docs contract so it fails first**
Add assertions to `scripts/ci/validate-docs.sh` requiring the ADR to contain all of these literals:
```text
Status: Accepted
gitops/clusters/dev/main
cicd-platform
delivery-platform.yaml
all/ is render/audit-only
```
Run:
```bash
bash scripts/ci/validate-docs.sh
```
Expected: non-zero because ADR 0002 does not exist.
**Step 2: Write the ADR**
Use sections `Context`, `Decision`, `Consequences`, and `Phase 2 follow-up`. Record:
- current rollout environment is `dev`, not `lab`;
- dev is controller-less and uses ordered bootstrap stages;
- `all/ is render/audit-only` and is never an apply target;
- project-infra owns local validation contracts;
- `cicd-platform` owns workflow execution, authoritative tools, evidence, and the future `delivery-platform.yaml` consumer onboarding;
- existing workflows remain transitional and are not changed in Phase 1.
Verify that the approved design document remains marked `Status: approved`.
**Step 3: Verify documentation**
Run:
```bash
bash scripts/ci/validate-docs.sh
git diff --check
```
Expected: both commands exit 0.
**Step 4: Commit**
```bash
git add docs/adr docs/architecture/repository-structure.md README.md docs/superpowers/specs scripts/ci/validate-docs.sh
git commit -m "docs: dev 및 CI 소유권 결정 기록"
```
---
## Task 6: Plan-level verification checkpoint
**Files:** No source changes expected.
**Step 1: Prove no live mutation was introduced by this plan**
Run:
```bash
rg -n 'kubectl[[:space:]]+(apply|delete|replace|patch)|helm[[:space:]]+(install|upgrade|uninstall)' tests scripts/ci scripts/lib
```
Expected: no new invocation in files created or modified by this plan. Existing task/bootstrap matches are handled by the next plan.
**Step 2: Run the complete plan contract**
Run:
```bash
bash tests/run.sh
VALIDATION_PROFILE=full bash scripts/ci/validate.sh
bash scripts/ci/validate-docs.sh
git diff --check
git status --short
```
Expected:
- tests and validation exit 0;
- every inventory entrypoint renders with Helm enabled;
- no active `lab` cluster or overlay path remains;
- `git diff --check` emits nothing;
- status contains no unrelated files.
**Step 3: Inspect commits**
```bash
git log --oneline --decorate -7
```
Confirm that each task is independently reviewable. Do not squash before the user reviews the phase.