Commit 1fc707c8 authored by xuwang's avatar xuwang
Browse files

feat(skills/otica-migrate): tools.txt, cert-manager gate, encryption_key, SQL_HOST



- otica-migrate: add post-migration tools.txt audit section (find loop)
- otica-migrate-gke: flag cert-manager.mk as conditional in §3; add
  §7 cert-manager gate (kubectl/grep check, two add-on composite
  variants for cert-manager vs Google-managed certs); add tools.txt step
  (GKE tool list: tfenv + kubectl + helm)
- otica-migrate-tf: add GCS backend encryption_key removal note (vault
  404 when path not provisioned); add SQL_HOST runtime-CI-var gotcha
  with manual gcloud workaround; add tools.txt step (TF-only list,
  flat vs nested distinction)
- otica-migrate-kube: add universal .gitignore section (add .envvars
  even without envvars.sec; bash audit loop); add tools.txt step (no tfenv)
- otica-migrate-docker: add tools.txt step (docker list, flat vs nested)

Lessons from som-department-medicine migration (98 repos, 2026-07-07).

Co-Authored-By: default avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 8d8ef1e3
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -230,6 +230,30 @@ AR_IMAGE_FULLNAME=${DOCKER_REGISTRY}/${DOCKER_REPO}/${DOCKER_IMAGE}

and use `AR_IMAGE_FULLNAME` (not `DOCKER_IMAGE:DOCKER_TAG`) for `tags list`. Symptom of getting it wrong: `ERROR: (gcloud.artifacts.docker.tags.list) NOT_FOUND`.

### Add `tools.txt`

OTICA's `make help` warns when `tools.txt` is missing. Create it at the project root (docker flavor):

```
git
make
jq
envsubst
vault
gomplate
gcloud
docker
```

For nested repos (common/ structure), wire it as the first line of `common/env.mk`:

```diff
+TOOLS_FILE=../tools.txt
 DOCKER_IMAGE=...
```

For flat repos, place `tools.txt` at the project root — no `TOOLS_FILE` override needed.

### Validate

```bash
+65 −8
Original line number Diff line number Diff line
@@ -129,7 +129,7 @@ include ${OTICA_DIR}/makefiles/hc-vault.mk
include ${OTICA_DIR}/makefiles/terraform.mk
include ${OTICA_DIR}/makefiles/gke.mk
include ${OTICA_DIR}/makefiles/kube.mk
include ${OTICA_DIR}/makefiles/cert-manager.mk
include ${OTICA_DIR}/makefiles/cert-manager.mk   # see §7 — drop if using Google-managed certs
include ${OTICA_DIR}/makefiles/external-dns.mk
include ${OTICA_DIR}/makefiles/stakater-reloader.mk

@@ -283,20 +283,53 @@ destroy-cluster:

Note: a sibling `prod/Makefile` may override `tf-destroy` with a guardrail — preserve it.

### cert-manager: include only if the cluster actually uses it

`cert-manager.mk` requires `ACME_EMAIL` at parse time — `make help` hard-fails with a missing-var error if the cluster doesn't use cert-manager.

**Before adding the include (§3), check whether cert-manager is actually deployed:**

```bash
# Is cert-manager running on the cluster?
kubectl get namespace cert-manager 2>/dev/null || echo "not deployed"
# Is ACME_EMAIL defined anywhere in env files?
grep -rn 'ACME_EMAIL' --include='*.mk' .
```

**If the cluster uses Google-managed certificates** (no cert-manager namespace, no `ACME_EMAIL`):
- Remove `include ${OTICA_DIR}/makefiles/cert-manager.mk` from §3
- Remove all `deploy-cert-manager`, `deploy-cert-issuer`, `upgrade-cert-manager`, `upgrade-cert-issuer`, `destroy-cert-issuer`, `destroy-cert-manager` from add-on composites

**If cert-manager is deployed**, keep the include and set `ACME_EMAIL` in `common/env.mk`.

### Add-on composites

Switch bodies to OTICA prereqs / target names:
Switch bodies to OTICA prereqs / target names. Adjust for the cert-manager decision above.

**With cert-manager:**

```makefile
deploy-add-ons: vault-login kc-config
	make deploy-cert-manager
	make deploy-cert-issuer
	make deploy-external-dns
	make deploy-storage-class
	${MAKE} deploy-cert-manager
	${MAKE} deploy-cert-issuer
	${MAKE} deploy-external-dns
	${MAKE} deploy-sc

update-add-ons: vault-login kc-config
	make upgrade-cert-manager
	make upgrade-cert-issuer
	${MAKE} upgrade-cert-manager
	${MAKE} upgrade-cert-issuer
```

**Without cert-manager (Google-managed certs):**

```makefile
deploy-add-ons: vault-login kc-config ## deploy external-dns, RBAC, storageclass (Google-managed certs)
	${MAKE} deploy-sc
	${MAKE} deploy-external-dns
	${MAKE} deploy-rbac

update-add-ons: vault-login kc-config ## upgrade external-dns (Google-managed certs)
	${MAKE} upgrade-external-dns
```

### Drop targets superseded by OTICA `gke.mk`
@@ -313,6 +346,30 @@ update-add-ons: vault-login kc-config

Required by OTICA's `terraform.mk`. Without it, `make tf-plan` aborts with `tf-check-git-ignore`.

### Add `tools.txt`

OTICA's `make help` warns when `tools.txt` is missing. Create it at the project root (full GKE stack):

```
git
make
jq
envsubst
tfenv
vault
gomplate
gcloud
kubectl
helm
```

Wire it as the first line of `common/env.mk`:

```diff
+TOOLS_FILE=../tools.txt
 GKE_CLUSTER_NAME=...
```

### Validate

```bash
+43 −0
Original line number Diff line number Diff line
@@ -240,6 +240,49 @@ awk '/kc-config:[ \t]*gke-login/{kc=NR} kc&&/^\t.*(export|:=|=)/{print FILENAME"

No output = correctly placed.

### `.gitignore`: add OTICA artifact dirs

OTICA renders vault-sourced values into local build directories. All of these must be in `.gitignore` before any render runs:

```
.deploy_sec
.deploy
.envvars
```

Add `.envvars` even if the repo has no `envvars.sec` today — a future addition of `envvars.sec` would write vault-sourced values to `.envvars` at render time, and without the gitignore entry those secrets could be committed.

Audit all kube repos at once:

```bash
for dir in sub-projects/kube-*/; do
  grep -qL '.envvars' "${dir}.gitignore" 2>/dev/null && echo "$dir MISSING .envvars"
done
```

### Add `tools.txt`

OTICA's `make help` warns when `tools.txt` is missing. Create it at the project root (kube flavor — no tfenv):

```
git
make
jq
envsubst
vault
gomplate
gcloud
kubectl
helm
```

Wire it as the first line of `common/env.mk`:

```diff
+TOOLS_FILE=../tools.txt
 GKE_CLUSTER_NAME=...
```

### `envvars.sec` — strongly prefer dropping it

**If the only consumer is a Secret manifest**, drop the middleman entirely and put the vault lookup directly in the Secret template. One source of truth, no `kc-render: envvars` ordering dependency.
+54 −0
Original line number Diff line number Diff line
@@ -221,6 +221,60 @@ Required by OTICA's `terraform.mk`. First `make tf-plan` after migration errors

This is a secret-leak guardrail — `make tf-render` writes vault-sourced values into `.tf_build/`. Add `.tf_build` to `.gitignore` FIRST, then run `make tf-render`.

### GCS backend `encryption_key` — remove if not provisioned

Legacy `provider.tf` may contain an optional CMEK line:

```hcl
backend "gcs" {
  bucket         = "{{ .Env.GCP_INFRASTRUCTURE_BUCKET }}"
  prefix         = "{{ .Env.TF_BACKEND_PREFIX }}"
  encryption_key = "{{ tmpl.Exec \"vault-kv\" .Env.TF_KEY_VAULT_PATH }}"
}
```

If the vault path was never provisioned or has been retired, this causes a vault 404 on every `tf-render`. Remove the `encryption_key` line entirely — GCS bucket-level encryption is sufficient. Also remove the dead `TF_KEY_VAULT_PATH` variable from `common/env.mk`.

```bash
grep -rn 'TF_KEY_VAULT_PATH\|encryption_key' common/terraform/ common/env.mk
```

### `SQL_HOST` — runtime CI var for database repos

For repos that manage databases or users on an existing Cloud SQL instance, `SQL_HOST` is the instance IP. It comes from terraform outputs of the companion instances repo and is **not** a static env var — `make tf-plan` will fail locally with `missing env var: SQL_HOST`. This is expected. Set it manually if running locally:

```bash
export SQL_HOST=$(gcloud sql instances describe ${CLOUD_SQL_INSTANCE} \
  --project=${GCP_PROJECT_ID} --format='value(ipAddresses[0].ipAddress)')
make tf-plan
```

In CI, `SQL_HOST` is injected as a pipeline variable from the instances repo's output job. Document it as an expected local failure — it is not a migration bug.

### Add `tools.txt`

OTICA's `make help` warns when `tools.txt` is missing. Create it at the project root listing required tools (TF-only flavor):

```
git
make
jq
envsubst
tfenv
vault
gomplate
gcloud
```

For nested repos (common/ structure), wire it as the first line of `common/env.mk`:

```diff
+TOOLS_FILE=../tools.txt
 CLOUD_SQL_INSTANCE=...
```

For flat repos (no `common/` — e.g. `gitlab-tf`), place `tools.txt` at the project root with no `TOOLS_FILE` override needed.

### Validate

```bash
+12 −0
Original line number Diff line number Diff line
@@ -73,6 +73,18 @@ Whenever `repos.txt` changes (add/remove/rename a sub-project), update the root
`README.md` to match — the repo list (e.g. the "Dockerization" / sub-projects
sections and their link references) is kept in sync with `repos.txt` by hand.

## Post-migration: `tools.txt` required by every repo

Each flavor skill contains a **`tools.txt`** step. Do not skip it — OTICA's `make help` prints a warning on every invocation if `tools.txt` is missing or `TOOLS_FILE` isn't wired.

Quick check across all sub-projects after migration:

```bash
# Find repos missing tools.txt
find sub-projects -maxdepth 2 -name Makefile -printf '%h\n' \
  | while read d; do [ -f "$d/tools.txt" ] || echo "MISSING: $d/tools.txt"; done
```

## Cost note

Use **Sonnet** (`/model claude-sonnet-4-6`) for migration sessions — the flavor skills are fully prescriptive and don't require Opus-level reasoning.