infra/terragrunt.hcl

130 lines
3.2 KiB
HCL
Raw Normal View History

# Root Terragrunt configuration
# Provides DRY provider, backend, and variable loading for all stacks.
# Two-tier state backend:
# Tier 0 (bootstrap): local state, SOPS-encrypted in git — must exist before PG is reachable.
# Tier 1 (everything else): PG backend on CNPG cluster, native pg_advisory_lock.
locals {
tier0_stacks = ["infra", "platform", "cnpg", "vault", "dbaas", "external-secrets"]
stack_name = replace(path_relative_to_include(), "stacks/", "")
is_tier0 = contains(local.tier0_stacks, local.stack_name)
}
remote_state {
backend = local.is_tier0 ? "local" : "pg"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = local.is_tier0 ? {
path = "${get_repo_root()}/state/${path_relative_to_include()}/terraform.tfstate"
} : {
conn_str = get_env("PG_CONN_STR", "")
schema_name = local.stack_name
}
}
# Load config.tfvars (plaintext). Secrets come from Vault KV — authenticate via `vault login -method=oidc`.
terraform {
extra_arguments "common_vars" {
commands = get_terraform_commands_that_need_vars()
required_var_files = [
"${get_repo_root()}/config.tfvars"
]
}
extra_arguments "no_backup" {
commands = ["apply", "plan", "destroy", "import"]
arguments = ["-backup=-"]
}
extra_arguments "kube_config" {
commands = get_terraform_commands_that_need_vars()
arguments = [
"-var", "kube_config_path=${get_repo_root()}/config"
]
}
}
[infra] Auto-create Cloudflare DNS records from ingress_factory ## Context Deploying new services required manually adding hostnames to cloudflare_proxied_names/cloudflare_non_proxied_names in config.tfvars — a separate file from the service stack. This was frequently forgotten, leaving services unreachable externally. ## This change: - Add `dns_type` parameter to `ingress_factory` and `reverse_proxy/factory` modules. Setting `dns_type = "proxied"` or `"non-proxied"` auto-creates the Cloudflare DNS record (CNAME to tunnel or A/AAAA to public IP). - Simplify cloudflared tunnel from 100 per-hostname rules to wildcard `*.viktorbarzin.me → Traefik`. Traefik still handles host-based routing. - Add global Cloudflare provider via terragrunt.hcl (separate cloudflare_provider.tf with Vault-sourced API key). - Migrate 118 hostnames from centralized config.tfvars to per-service dns_type. 17 hostnames remain centrally managed (Helm ingresses, special cases). - Update docs, AGENTS.md, CLAUDE.md, dns.md runbook. ``` BEFORE AFTER config.tfvars (manual list) stacks/<svc>/main.tf | module "ingress" { v dns_type = "proxied" stacks/cloudflared/ } for_each = list | cloudflare_record auto-creates tunnel per-hostname cloudflare_record + annotation ``` ## What is NOT in this change: - Uptime Kuma monitor migration (still reads from config.tfvars) - 17 remaining centrally-managed hostnames (Helm, special cases) - Removal of allow_overwrite (keep until migration confirmed stable) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:45:04 +00:00
# Generate kubernetes + helm + cloudflare providers for all stacks.
# The infra stack overrides this to add the proxmox provider.
generate "k8s_providers" {
path = "providers.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
terraform {
required_providers {
vault = {
source = "hashicorp/vault"
version = "~> 4.0"
}
[infra] Auto-create Cloudflare DNS records from ingress_factory ## Context Deploying new services required manually adding hostnames to cloudflare_proxied_names/cloudflare_non_proxied_names in config.tfvars — a separate file from the service stack. This was frequently forgotten, leaving services unreachable externally. ## This change: - Add `dns_type` parameter to `ingress_factory` and `reverse_proxy/factory` modules. Setting `dns_type = "proxied"` or `"non-proxied"` auto-creates the Cloudflare DNS record (CNAME to tunnel or A/AAAA to public IP). - Simplify cloudflared tunnel from 100 per-hostname rules to wildcard `*.viktorbarzin.me → Traefik`. Traefik still handles host-based routing. - Add global Cloudflare provider via terragrunt.hcl (separate cloudflare_provider.tf with Vault-sourced API key). - Migrate 118 hostnames from centralized config.tfvars to per-service dns_type. 17 hostnames remain centrally managed (Helm ingresses, special cases). - Update docs, AGENTS.md, CLAUDE.md, dns.md runbook. ``` BEFORE AFTER config.tfvars (manual list) stacks/<svc>/main.tf | module "ingress" { v dns_type = "proxied" stacks/cloudflared/ } for_each = list | cloudflare_record auto-creates tunnel per-hostname cloudflare_record + annotation ``` ## What is NOT in this change: - Uptime Kuma monitor migration (still reads from config.tfvars) - 17 remaining centrally-managed hostnames (Helm, special cases) - Removal of allow_overwrite (keep until migration confirmed stable) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:45:04 +00:00
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4"
}
[infra] Adopt Authentik catch-all Proxy Provider + Application into TF (Wave 6a) ## Context Wave 6a of the state-drift consolidation plan. The Domain wide catch all Proxy Provider (pk=5) + its wrapping Application (slug=domain-wide-catch-all) + the embedded outpost (uuid 0eecac07-97c7-443c-8925-05f2f4fe3e47) have run for a year as pure UI-created state. When the 2026-04-18 outpost SEV2 hit, it was harder to reason about the config than it should have been — the only source of truth was the Authentik admin UI. Bringing the provider + application under Terraform means future changes are reviewable in PRs and recoverable from git if the admin UI misbehaves. ## This change Adds the `goauthentik/authentik` provider to the repo's central `terragrunt.hcl` `required_providers` (side-effect: every stack can now declare authentik resources; this stack is the only current consumer). Stack-local `stacks/authentik/authentik_provider.tf` holds the provider instance configuration + API token wiring + two resources + their flow data-source lookups. ### Auth - API token stored in Vault at `secret/authentik/tf_api_token`, identifier `terraform-infra-stack`, intent=API, user=akadmin, no expiry. Rotatable by rewriting the Vault KV + any running TF apply picks it up on next plan. ### Imports (both landed zero-diff) - `authentik_application.catchall` ← id `domain-wide-catch-all` - `authentik_provider_proxy.catchall` ← id `5` ### Flow references Authorization + invalidation flows are looked up via `data "authentik_flow"` by slug (`default-provider-authorization-implicit-consent` + `default-provider-invalidation-flow`). Keeping them as data sources rather than hardcoded UUIDs means a flow recreation (slug unchanged) doesn't require an HCL edit. ### `lifecycle { ignore_changes }` scope On `authentik_provider_proxy.catchall`: - `property_mappings` (5 UUIDs), `jwt_federation_sources` (1 UUID) — the live state references complex many-to-many relations that are easier to manage from the Authentik UI than to serialise in HCL. Drift suppressed. - `skip_path_regex`, `internal_host`, all `basic_auth_*`, `intercept_header_auth`, `access_token_validity` — either defaults or UI-only tuning knobs that aren't part of Terraform's concern for this catch-all provider. On `authentik_application.catchall`: - `meta_description`, `meta_launch_url`, `meta_icon`, `group`, `backchannel_providers`, `policy_engine_mode`, `open_in_new_tab` — cosmetic/non-functional attributes; the Authentik UI is the right place to edit these and drift on them isn't interesting. ## What is NOT in this change - Outpost-binding resource — the embedded outpost's provider list is a single-row many-to-many that the Authentik UI manages cleanly; adding TF there would fight the UI without reducing drift. - Property mappings and JWT federation source — managed via UI, drift suppressed. A future wave can bring them in when someone actually wants to edit them through code review. - Other Authentik entities (Flows, Stages, Groups, RBAC policies) — same rationale: UI is the natural editing surface. Adopt incrementally as they become interesting to code-review. ## Verification ``` $ cd stacks/authentik && ../../scripts/tg plan | grep Plan: Plan: 0 to add, 1 to change, 0 to destroy. # module.authentik.kubernetes_deployment.pgbouncer — pre-existing drift, # unrelated to this commit (image_pull_policy Always -> IfNotPresent) $ ../../scripts/tg state list | grep authentik_ authentik_application.catchall authentik_provider_proxy.catchall data.authentik_flow.default_authorization_implicit_consent data.authentik_flow.default_provider_invalidation ``` ## Reproduce locally 1. `git pull && cd stacks/authentik && ../../scripts/tg init` 2. Terraform pulls goauthentik/authentik provider (first time). 3. `tg plan` — expect only pgbouncer drift; authentik resources read-only. Refs: Wave 6a of the state-drift consolidation (code-hl1) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:48:26 +00:00
authentik = {
source = "goauthentik/authentik"
version = "~> 2024.10"
}
}
}
variable "kube_config_path" {
type = string
default = "~/.kube/config"
}
provider "kubernetes" {
config_path = var.kube_config_path
}
provider "helm" {
kubernetes = {
config_path = var.kube_config_path
}
}
provider "vault" {
address = "https://vault.viktorbarzin.me"
skip_child_token = true
}
EOF
}
2026-02-23 22:05:28 +00:00
[infra] Auto-create Cloudflare DNS records from ingress_factory ## Context Deploying new services required manually adding hostnames to cloudflare_proxied_names/cloudflare_non_proxied_names in config.tfvars — a separate file from the service stack. This was frequently forgotten, leaving services unreachable externally. ## This change: - Add `dns_type` parameter to `ingress_factory` and `reverse_proxy/factory` modules. Setting `dns_type = "proxied"` or `"non-proxied"` auto-creates the Cloudflare DNS record (CNAME to tunnel or A/AAAA to public IP). - Simplify cloudflared tunnel from 100 per-hostname rules to wildcard `*.viktorbarzin.me → Traefik`. Traefik still handles host-based routing. - Add global Cloudflare provider via terragrunt.hcl (separate cloudflare_provider.tf with Vault-sourced API key). - Migrate 118 hostnames from centralized config.tfvars to per-service dns_type. 17 hostnames remain centrally managed (Helm ingresses, special cases). - Update docs, AGENTS.md, CLAUDE.md, dns.md runbook. ``` BEFORE AFTER config.tfvars (manual list) stacks/<svc>/main.tf | module "ingress" { v dns_type = "proxied" stacks/cloudflared/ } for_each = list | cloudflare_record auto-creates tunnel per-hostname cloudflare_record + annotation ``` ## What is NOT in this change: - Uptime Kuma monitor migration (still reads from config.tfvars) - 17 remaining centrally-managed hostnames (Helm, special cases) - Removal of allow_overwrite (keep until migration confirmed stable) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:45:04 +00:00
# Generate Cloudflare provider config (separate file to avoid conflicts
# with stacks that override providers.tf, e.g. infra stack).
# DNS records are created per-service via ingress_factory's dns_type param.
generate "cloudflare_provider" {
path = "cloudflare_provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
data "vault_kv_secret_v2" "cf_platform" {
mount = "secret"
name = "platform"
}
provider "cloudflare" {
api_key = data.vault_kv_secret_v2.cf_platform.data["cloudflare_api_key"]
email = "vbarzin@gmail.com"
}
EOF
}
2026-02-23 22:05:28 +00:00
# Generate shared tiers locals for all stacks.
# Previously duplicated in 67+ stacks; now defined once here.
generate "tiers" {
path = "tiers.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
locals {
tiers = {
core = "0-core"
cluster = "1-cluster"
gpu = "2-gpu"
edge = "3-edge"
aux = "4-aux"
}
}
EOF
}