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 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-21 15:25:20 +02:00
parent 3a18a348ac
commit af3815ab6a
3 changed files with 30 additions and 20 deletions

View File

@@ -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 <noreply@anthropic.com>"
---
## 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-<module>.tfstate` via `terragrunt state push`.
@@ -257,7 +257,7 @@ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
---
## 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/<module>/terraform.tfstate`. ✅ Task 3.3, Task 5.2
- Lock smoke test passes. ✅ Task 3.4

View File

@@ -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 `<module>/terraform.tfstate` (verified via `scw`/`mc`).
- Lock smoke test passes (lock acquired + released, no orphan lockfile).

View File

@@ -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
}
}