After the runner-CA fix (#11), the iac workflow runs far enough to reach terraform apply (run #27), which surfaced two provider-level failures. This PR fixes both. It complements #11 (merge both); no overlapping lines.
1. Cloudflare provider drift
cloudflare/cloudflare was constrained to ~> 5 with no committed .terraform.lock.hcl (the old .gitignore rule .terraform.* was hiding it), so every CI init pulled the latest — now v5.21.1 — where cloudflare_account_token.policies[].resources is a JSON string, not an HCL map:
Error: Incorrect attribute value type ... attribute "resources": string required
Fixes:
Pin~> 5.21 and commit a multi-platform lock file (linux_arm64 for the runner + darwin_arm64 for local) — stops the drift.
jsonencode(...) the module's policy resources (correct shape for ≥5.20; verified against the provider schema).
Bind the cloudflare_token module to cloudflare/cloudflare via its own required_providers — it was defaulting to hashicorp/cloudflare, pulling a redundant second provider.
.gitignore: .terraform / .terraform.* → .terraform/ + *.tfstate*, so the lock file is tracked.
2. Gitea provider TLS (x509)
Get "https://gitea.arcodange.lab/api/v1/version": x509: certificate signed by unknown authority
The gitea provider runs inside the dflook/terraform-applycontainer, which doesn't trust the homelab CA (only the ubuntu-latest-ca runner does; the vault provider survives because it honors VAULT_CACERT). Fix: feed the gitea provider the CA via its cacert_file argument, sourced from TF_VAR_gitea_cacert_file → the homelab.pem the workflow already writes to the workspace.
Validation
Done locally, no prod calls:
tofu init resolves to v5.21.1 + generates the lock file with both platforms.
tofu validate → Success.
tofu providers schema confirmed policies[].resources is string and the gitea provider exposes cacert_file.
Lock file no longer gitignored; hashicorp/cloudflare removed.
I could not exercise a real plan/apply (needs Vault JWT + prod credentials + the OAuth handoff).
Out of scope (need a live run / operator)
OVH consumer-key scope — the regenerated key authenticates but lacks access rules (403 "This call has not been granted"); recreate at auth.eu.ovhcloud.com/api/createToken with GET/POST/PUT/DELETE on /me/* and /iam/*.
R2 bucket "not found" on refresh — the arcodange-tf bucket exists (EU jurisdiction, confirmed via API) but v5.21.1 reports it missing on refresh. This is a state/read reconcile (likely a one-time tofu import), not fixable offline — verify on the next live run and don't let auto_approve recreate it.
postgres/iac uses exact version pins (low drift risk); a lock file there is a possible follow-up.
## Context
After the runner-CA fix (#11), the `iac` workflow runs far enough to reach `terraform apply` ([run #27](https://gitea.arcodange.lab/arcodange-org/factory/actions/runs/27)), which surfaced two provider-level failures. This PR fixes both. It **complements #11** (merge both); no overlapping lines.
## 1. Cloudflare provider drift
`cloudflare/cloudflare` was constrained to `~> 5` with **no committed `.terraform.lock.hcl`** (the old `.gitignore` rule `.terraform.*` was hiding it), so every CI `init` pulled the latest — now **v5.21.1** — where `cloudflare_account_token.policies[].resources` is a **JSON string**, not an HCL map:
```
Error: Incorrect attribute value type ... attribute "resources": string required
```
Fixes:
- **Pin** `~> 5.21` and **commit a multi-platform lock file** (`linux_arm64` for the runner + `darwin_arm64` for local) — stops the drift.
- **`jsonencode(...)`** the module's policy `resources` (correct shape for ≥5.20; verified against the provider schema).
- **Bind the `cloudflare_token` module to `cloudflare/cloudflare`** via its own `required_providers` — it was defaulting to `hashicorp/cloudflare`, pulling a redundant second provider.
- **`.gitignore`**: `.terraform` / `.terraform.*` → `.terraform/` + `*.tfstate*`, so the lock file is tracked.
## 2. Gitea provider TLS (x509)
```
Get "https://gitea.arcodange.lab/api/v1/version": x509: certificate signed by unknown authority
```
The gitea provider runs inside the `dflook/terraform-apply` **container**, which doesn't trust the homelab CA (only the `ubuntu-latest-ca` runner does; the vault provider survives because it honors `VAULT_CACERT`). Fix: feed the gitea provider the CA via its `cacert_file` argument, sourced from `TF_VAR_gitea_cacert_file` → the `homelab.pem` the workflow already writes to the workspace.
## Validation
Done **locally, no prod calls**:
- `tofu init` resolves to v5.21.1 + generates the lock file with both platforms.
- `tofu validate` → **Success**.
- `tofu providers schema` confirmed `policies[].resources` is `string` and the gitea provider exposes `cacert_file`.
- Lock file no longer gitignored; `hashicorp/cloudflare` removed.
I could not exercise a real `plan`/`apply` (needs Vault JWT + prod credentials + the OAuth handoff).
## Out of scope (need a live run / operator)
- **OVH consumer-key scope** — the regenerated key authenticates but lacks access rules (`403 "This call has not been granted"`); recreate at `auth.eu.ovhcloud.com/api/createToken` with `GET/POST/PUT/DELETE` on `/me/*` and `/iam/*`.
- **R2 bucket "not found" on refresh** — the `arcodange-tf` bucket exists (EU jurisdiction, confirmed via API) but v5.21.1 reports it missing on refresh. This is a state/read reconcile (likely a one-time `tofu import`), not fixable offline — verify on the next live run and **don't let `auto_approve` recreate it**.
- `postgres/iac` uses exact version pins (low drift risk); a lock file there is a possible follow-up.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
With the runner CA fix (#11) the iac workflow now runs far enough to apply,
which exposed two provider problems:
cloudflare drift — `cloudflare/cloudflare` floated on `~> 5` with no committed
lock file, so CI pulled v5.21.1 where `cloudflare_account_token.policies[].resources`
is a JSON string, not a map ("Incorrect attribute value type"). Fix:
- pin to `~> 5.21` and commit a multi-platform `.terraform.lock.hcl`
(linux_arm64 for the runner + darwin_arm64 for local);
- `jsonencode(...)` the module's policy resources;
- bind the cloudflare_token module to `cloudflare/cloudflare` explicitly (it was
defaulting to `hashicorp/cloudflare`, pulling a redundant provider);
- stop `.gitignore` from hiding the lock file (the old `.terraform.*` rule did).
gitea provider TLS — it runs inside the dflook/terraform-apply container, which
doesn't trust the homelab CA (only the ubuntu-latest-ca runner does), so it
failed `x509: certificate signed by unknown authority` reaching
gitea.arcodange.lab. Fix: feed it the homelab CA via the provider's `cacert_file`
(TF_VAR_gitea_cacert_file -> the homelab.pem the workflow already materializes).
Validated locally with `tofu validate` + provider-schema inspection (no prod
calls). Complements #11. Out of scope (need a live run / operator): the OVH
consumer-key scope, and the R2 bucket "not found" on refresh (a state reconcile).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context
After the runner-CA fix (#11), the
iacworkflow runs far enough to reachterraform apply(run #27), which surfaced two provider-level failures. This PR fixes both. It complements #11 (merge both); no overlapping lines.1. Cloudflare provider drift
cloudflare/cloudflarewas constrained to~> 5with no committed.terraform.lock.hcl(the old.gitignorerule.terraform.*was hiding it), so every CIinitpulled the latest — now v5.21.1 — wherecloudflare_account_token.policies[].resourcesis a JSON string, not an HCL map:Fixes:
~> 5.21and commit a multi-platform lock file (linux_arm64for the runner +darwin_arm64for local) — stops the drift.jsonencode(...)the module's policyresources(correct shape for ≥5.20; verified against the provider schema).cloudflare_tokenmodule tocloudflare/cloudflarevia its ownrequired_providers— it was defaulting tohashicorp/cloudflare, pulling a redundant second provider..gitignore:.terraform/.terraform.*→.terraform/+*.tfstate*, so the lock file is tracked.2. Gitea provider TLS (x509)
The gitea provider runs inside the
dflook/terraform-applycontainer, which doesn't trust the homelab CA (only theubuntu-latest-carunner does; the vault provider survives because it honorsVAULT_CACERT). Fix: feed the gitea provider the CA via itscacert_fileargument, sourced fromTF_VAR_gitea_cacert_file→ thehomelab.pemthe workflow already writes to the workspace.Validation
Done locally, no prod calls:
tofu initresolves to v5.21.1 + generates the lock file with both platforms.tofu validate→ Success.tofu providers schemaconfirmedpolicies[].resourcesisstringand the gitea provider exposescacert_file.hashicorp/cloudflareremoved.I could not exercise a real
plan/apply(needs Vault JWT + prod credentials + the OAuth handoff).Out of scope (need a live run / operator)
403 "This call has not been granted"); recreate atauth.eu.ovhcloud.com/api/createTokenwithGET/POST/PUT/DELETEon/me/*and/iam/*.arcodange-tfbucket exists (EU jurisdiction, confirmed via API) but v5.21.1 reports it missing on refresh. This is a state/read reconcile (likely a one-timetofu import), not fixable offline — verify on the next live run and don't letauto_approverecreate it.postgres/iacuses exact version pins (low drift risk); a lock file there is a possible follow-up.🤖 Generated with Claude Code