From af3815ab6a86b563bd7a02f75dc28eacc72791b1 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Sun, 21 Jun 2026 15:25:20 +0200 Subject: [PATCH] chore(infra): migrate Terraform state GitLab -> Scaleway Object Storage (Phase 2A) All 4 modules (kapsule/public-gateway/dns/block-storage) migrated to s3 backend (bucket foxhunt-tfstate, fr-par, use_lockfile=true). plan=No changes verified per module + final re-plan; all 4 state objects present in bucket; lock smoke test passed (refresh-only apply, no orphan lockfile). GitLab http state left intact as fallback (removed in Phase 2C). Also fixed root.hcl path in spec/plan docs. Co-Authored-By: Claude Opus 4.8 --- .../plans/2026-06-21-tfstate-to-scaleway.md | 18 ++++++------ .../2026-06-21-tfstate-to-scaleway-design.md | 4 +-- infra/live/production/root.hcl | 28 +++++++++++++------ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/docs/superpowers/plans/2026-06-21-tfstate-to-scaleway.md b/docs/superpowers/plans/2026-06-21-tfstate-to-scaleway.md index 3921724bd..498ce550d 100644 --- a/docs/superpowers/plans/2026-06-21-tfstate-to-scaleway.md +++ b/docs/superpowers/plans/2026-06-21-tfstate-to-scaleway.md @@ -8,7 +8,7 @@ external Scaleway Object Storage (bucket `foxhunt-tfstate`), with native S3 lockfile locking. **Architecture:** Back up all 4 states from GitLab first; flip the single `remote_state` block in -`infra/live/root.hcl` from `http`→`s3`; migrate each module in-place with `terragrunt init +`infra/live/production/root.hcl` from `http`→`s3`; migrate each module in-place with `terragrunt init -migrate-state`; gate each on `terragrunt plan = No changes`. GitLab state is left intact as a fallback (deleted later in 2C). @@ -85,11 +85,11 @@ Expected: a serial number per module. Keep this output — the migrated state sh ## Task 2: Switch the backend in root.hcl (http → s3) -**Files:** Modify `infra/live/root.hcl` +**Files:** Modify `infra/live/production/root.hcl` - [ ] **Step 1: Replace the `remote_state` block** -In `infra/live/root.hcl`, replace the entire existing `remote_state { ... }` block (the `backend = "http"` +In `infra/live/production/root.hcl`, replace the entire existing `remote_state { ... }` block (the `backend = "http"` GitLab block) with: ```hcl # Remote state in Scaleway Object Storage (bucket foxhunt-tfstate, fr-par) — migrated off GitLab 2026-06-21. @@ -125,7 +125,7 @@ Leave the `generate "provider"` block and `inputs`/`locals` untouched. - [ ] **Step 2: Sanity-check the file parses** ```bash cd /home/jgrusewski/Work/foxhunt -grep -A2 'backend = "s3"' infra/live/root.hcl && grep -c 'backend = "http"' infra/live/root.hcl +grep -A2 'backend = "s3"' infra/live/production/root.hcl && grep -c 'backend = "http"' infra/live/production/root.hcl ``` Expected: shows the `s3` backend line; the `backend = "http"` count is `0`. @@ -142,7 +142,7 @@ terragrunt init -migrate-state -force-copy -no-color 2>&1 | sed 's/.*tofu: //' | ``` Expected: a line like `Terraform has been successfully migrated to the "s3" backend!` (or `Successfully configured the backend "s3"`). **If it errors on checksum** (Scaleway quirk), add `skip_s3_checksum = true` -to the `config` block in `infra/live/root.hcl` and re-run this step. +to the `config` block in `infra/live/production/root.hcl` and re-run this step. - [ ] **Step 2: GATE — plan must show No changes** ```bash @@ -205,7 +205,7 @@ Expected: migration success line, then `No changes`. **STOP on any drift.** ## Task 5: Final verification + commit -**Files:** Modify (commit) `infra/live/root.hcl`; also commit generated `backend.tf` only if not gitignored. +**Files:** Modify (commit) `infra/live/production/root.hcl`; also commit generated `backend.tf` only if not gitignored. - [ ] **Step 1: Re-plan all 4 modules from a clean shell** (proves the s3 backend is authoritative) ```bash @@ -233,7 +233,7 @@ echo "GitLab TF state is intentionally left in place as a 2A fallback; deleted i - [ ] **Step 4: Commit** ```bash cd /home/jgrusewski/Work/foxhunt -git add infra/live/root.hcl +git add infra/live/production/root.hcl # backend.tf is terragrunt-generated; add only if tracked (usually gitignored) git status --porcelain infra/live/production/*/backend.tf 2>/dev/null git commit -m "chore(infra): migrate Terraform state GitLab -> Scaleway Object Storage (Phase 2A) @@ -248,7 +248,7 @@ Co-Authored-By: Claude Opus 4.8 " --- ## Rollback (if any module's post-migration plan shows drift) -1. Restore the `http` backend block in `infra/live/root.hcl` (git checkout the file). +1. Restore the `http` backend block in `infra/live/production/root.hcl` (git checkout the file). 2. Re-attach GitLab state: in each affected module, `terragrunt init -migrate-state -force-copy` (copies back s3→http) OR `terragrunt init -reconfigure` (the GitLab state was never deleted). 3. If state is corrupted, restore from `/tmp/tfstate-backup-.tfstate` via `terragrunt state push`. @@ -257,7 +257,7 @@ Co-Authored-By: Claude Opus 4.8 " --- ## Acceptance criteria (from spec) -- `infra/live/root.hcl` uses the Scaleway s3 backend; committed. ✅ Task 2 + Task 5.4 +- `infra/live/production/root.hcl` uses the Scaleway s3 backend; committed. ✅ Task 2 + Task 5.4 - All 4 modules: `terragrunt plan = No changes` on the new backend. ✅ Task 3.2, Task 4, Task 5.1 - State objects at `foxhunt-tfstate//terraform.tfstate`. ✅ Task 3.3, Task 5.2 - Lock smoke test passes. ✅ Task 3.4 diff --git a/docs/superpowers/specs/2026-06-21-tfstate-to-scaleway-design.md b/docs/superpowers/specs/2026-06-21-tfstate-to-scaleway-design.md index 28fc54e35..28e391306 100644 --- a/docs/superpowers/specs/2026-06-21-tfstate-to-scaleway-design.md +++ b/docs/superpowers/specs/2026-06-21-tfstate-to-scaleway-design.md @@ -24,7 +24,7 @@ OpenTofu v1.11.5 present) — no DynamoDB. ## Scope — what changes -1. **`infra/live/root.hcl`** — replace the single `remote_state` block: `backend = "http"` (GitLab) → +1. **`infra/live/production/root.hcl`** — replace the single `remote_state` block: `backend = "http"` (GitLab) → `backend = "s3"` (Scaleway). New config: ```hcl remote_state { @@ -87,7 +87,7 @@ backend changes. ## Acceptance criteria -- `infra/live/root.hcl` uses the Scaleway s3 backend; committed. +- `infra/live/production/root.hcl` uses the Scaleway s3 backend; committed. - All 4 modules migrated: each shows `terragrunt plan = No changes` against the new backend. - State objects present in `foxhunt-tfstate` at `/terraform.tfstate` (verified via `scw`/`mc`). - Lock smoke test passes (lock acquired + released, no orphan lockfile). diff --git a/infra/live/production/root.hcl b/infra/live/production/root.hcl index 190667547..e78d6ccb2 100644 --- a/infra/live/production/root.hcl +++ b/infra/live/production/root.hcl @@ -7,21 +7,31 @@ locals { project_id = get_env("SCW_DEFAULT_PROJECT_ID") } -# Remote state in GitLab Terraform state backend (project root/foxhunt, ID=1) -# Token stored in k8s secret gitlab-terraform-state-token -# Set TF_HTTP_USERNAME and TF_HTTP_PASSWORD env vars before running terragrunt. +# Remote state in Scaleway Object Storage (bucket foxhunt-tfstate, fr-par) — migrated off GitLab 2026-06-21. +# The s3 backend reads AWS_* env vars (set them to the Scaleway access/secret keys); SCW_* drives the provider. remote_state { - backend = "http" + backend = "s3" generate = { path = "backend.tf" if_exists = "overwrite" } config = { - address = "${get_env("GITLAB_TF_STATE_URL", "https://tf.fxhnt.ai")}/api/v4/projects/1/terraform/state/${path_relative_to_include()}" - lock_address = "${get_env("GITLAB_TF_STATE_URL", "https://tf.fxhnt.ai")}/api/v4/projects/1/terraform/state/${path_relative_to_include()}/lock" - unlock_address = "${get_env("GITLAB_TF_STATE_URL", "https://tf.fxhnt.ai")}/api/v4/projects/1/terraform/state/${path_relative_to_include()}/lock" - lock_method = "POST" - unlock_method = "DELETE" + bucket = "foxhunt-tfstate" + key = "${path_relative_to_include()}/terraform.tfstate" + region = "fr-par" + + endpoints = { + s3 = "https://s3.fr-par.scw.cloud" + } + + # Scaleway S3-compat: skip AWS-specific preflight calls + skip_credentials_validation = true + skip_region_validation = true + skip_requesting_account_id = true + skip_metadata_api_check = true + + # OpenTofu-native lock (no DynamoDB) + use_lockfile = true } }