infra-as-code · git:20261031.ad68ebd · 2026-10-31 · sha256 23351f70f93b8c82

infra-as-code git:20261031.ad68ebdA

Immutable. This exact content never changes and is served at /api/v1/blob/23351f70f93b8c82.

---
name: infra-as-code
description: "Manage OpenTofu/Terraform modules, providers, state, and plans."
license: MIT
metadata:
  kind: task
  author: Médéric HURIER (Fmind)
  source: github.com/fmind/dot/tree/main/skills/infra-as-code
  created: "2026-09-16"
  updated: "2026-10-31"
---

# Infrastructure as Code

Canonical infrastructure as code with OpenTofu (the open-source Terraform fork; the binary is `tofu`). A repository that needs a BSL-licensed feature or employer-mandated HashiCorp Terraform pins `terraform` in its own mise configuration and documents the deviation.

## Defaults

- **Default to OpenTofu via project mise pins**: `opentofu` tool, `tofu` binary; verify provider, backend, state, and language-feature compatibility before a Terraform migration. The workstation installs `tofu` and `tflint` globally only for the editor; declare `opentofu`, `tflint`, and `terraform-docs` in the project's `mise.toml` with `mise use --pin <tool>`, as the [template](templates/mise.toml) does.
- **Use the template tasks and hooks**: [mise.toml](templates/mise.toml) exposes the canonical vocabulary per [mise](../mise/SKILL.md) — `check` fans out to format, validate, lint (tflint), scan (trivy), and leaks; [lefthook.yml](templates/lefthook.yml) wires the hooks per [lefthook](../github-actions/references/lefthook.md).
- **Generate docs with terraform-docs**: `mise run build` injects the inputs/outputs table between the `TF_DOCS` markers in `README.md` (add them once at scaffold time) per [terraform-docs.yml](templates/terraform-docs.yml), without cloud access.
- **Authenticate without service-account keys**: Application Default Credentials locally; Workload Identity Federation in CI per [cloud-run bootstrap](../cloud-run/references/bootstrap.md).

## Workflow

1. **Resolve the target**: identify the root module, backend, workspace (`tofu workspace show`), cloud project, credentials, and the authorized change scope. A fresh clone runs `mise run install` first: validation, linting, and tests need `tofu init` providers and `tflint --init` rulesets.
1. **Edit minimally**: change the smallest root module that owns the resource; keep one state per concern (one GCS `prefix` per small root module: smaller blast radius, faster plans).
1. **Check before planning**: run `mise run check` and `mise run test`; tests stay offline only while every provider is mocked (the starter's `mock_provider`), otherwise plan-mode tests can authenticate, refresh state, and read data sources. Native tests over `tests/*.tftest.hcl` use `command = plan` ([main.tftest.hcl](templates/main.tftest.hcl)); encode "must never happen" rules (public buckets, missing labels) as `check:scan` trivy findings or plan-test assertions.
1. **Plan explicitly**: `mise run plan` writes `tmp/plan.tfplan` for the selected backend, workspace, and cloud target; feed secrets per State and secrets below. A plan can access APIs, state, and data sources, so keep plan and apply out of automatic gates.
1. **Review the plan**: read `tofu show tmp/plan.tfplan` and flag every replacement, destroy, IAM change, and cost-relevant resource before asking for apply authority.
1. **Apply the reviewed plan when authorized**: `tofu apply tmp/plan.tfplan` against the same target, then delete the saved plan.
1. **Verify**: re-read the changed resources and run the same target's plan with the same secret inputs, `tofu plan -input=false -detailed-exitcode`; exit 0 means no remaining drift, 2 means pending changes, 1 is an error.

## State and secrets

- **State is secret**: state can store sensitive resource attributes in plaintext — never in git, always in the versioned GCS bucket, ideally wrapped by OpenTofu's `encryption` block. Read [state-encryption](references/state-encryption.md) before enabling it or migrating existing state.
- **Keep secrets out of tfvars and plans**: `*.tfvars` is gitignored; commit only `*.example.tfvars`. Feed secrets as `TF_VAR_<name>` environment variables that the project's secret store or launcher injects into the `tofu` process only, never exported in the shell or passed as arguments. A saved plan stores ordinary variable values, sensitive ones included; declare secret inputs `ephemeral = true`, pass them only to write-only arguments (for example `secret_data_wo` with an incremented `secret_data_wo_version`), and supply them again at `tofu apply tmp/plan.tfplan`.

## Gotchas

- **Apply-mode tests create resources**: `command = apply` creates real (then destroyed) resources; reserve it for behavior a plan cannot prove, with approved project access and cost.
- **tflint rulesets are downloaded**: `tflint --init` (in `install:lint`) fetches the plugins pinned in [tflint.hcl](templates/tflint.hcl); export `GITHUB_TOKEN` in CI to avoid API rate limits.
- **Initialize before the CI gate**: the shared [ci.yml](../github-actions/references/ci-cd/templates/ci.yml) runs only `mise run all`, so add a step with `tofu init -backend=false` and `tflint --init` before it; validation, linting, and `tofu test` need the providers and rulesets.
- **Provider majors move fast**: [versions.tf](templates/versions.tf) pins the google provider to one major with `~>`; bump majors deliberately with [upgrade-tools](../upgrade-tools/SKILL.md) and read the upgrade guide, because resources rename across majors.

## Task guides

<!-- guides:start -->

- [scaffold](references/scaffold.md): Create a new OpenTofu root module from the templates, validate it offline, lock providers, and promote its GCS backend.
- [state-encryption](references/state-encryption.md): Enable OpenTofu client-side state and plan encryption with GCP KMS, including plaintext state migration.

<!-- guides:end -->

## Documentation

- [OpenTofu](https://opentofu.org/docs/) · [tflint](https://github.com/terraform-linters/tflint) · [trivy config](https://trivy.dev/docs/latest/scanner/misconfiguration/) · [terraform-docs](https://terraform-docs.io) · [State encryption](https://opentofu.org/docs/language/state/encryption/)
- Releases: [OpenTofu](https://github.com/opentofu/opentofu/releases)
- Upstream: `hashicorp/agent-skills` per the [vendor-skill policy](../agent-project/references/vendor-skills.md); select only the Terraform or provider guidance the stack needs.
- Companion skills: [mise](../mise/SKILL.md) (task vocabulary and project pins), [github-actions](../github-actions/references/ci-cd/GUIDE.md) (CI), [code-security](../code-security/references/code-review/GUIDE.md) (full-repo scans), [Google catalog](../google-developer/SKILL.md) (product skills).