name: infra/hetzner — OpenTofu validate + test

# Module-local guardrail for the Catalyst Hetzner Phase-0 OpenTofu module
# at infra/hetzner/. Every PR touching the module re-runs `tofu validate`,
# `tofu fmt -check`, and the module's own `.tftest.hcl` test suite so the
# multi-region wiring (slice G1, EPIC-0 #1095) stays green and the legacy
# singular-region apply path keeps its plan-clean shape.
#
# Per CLAUDE.md "every workflow MUST be event-driven, NEVER scheduled":
# this workflow is push-on-merge + pull-request-on-touch. There is no
# `schedule:` trigger; ad-hoc reruns go through workflow_dispatch.

on:
  push:
    branches: [main]
    paths:
      - 'infra/hetzner/**'
      - '.github/workflows/infra-hetzner-tofu.yaml'
  pull_request:
    paths:
      - 'infra/hetzner/**'
      - '.github/workflows/infra-hetzner-tofu.yaml'
  workflow_dispatch:

jobs:
  validate-and-test:
    name: validate + fmt + test
    runs-on: ubuntu-latest
    timeout-minutes: 10
    defaults:
      run:
        working-directory: infra/hetzner
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install OpenTofu
        uses: opentofu/setup-opentofu@v1
        with:
          # Pinned to match the version `infra/hetzner/versions.tf` declares
          # (`required_version = ">= 1.6.0"`) and the version
          # `tests/e2e/hetzner-provisioning` already uses (1.8.5). Bump in
          # lockstep with that workflow to keep CI behaviour deterministic.
          tofu_version: 1.8.5

      - name: tofu init (backend=false — no real state for module-local checks)
        run: tofu init -backend=false

      - name: tofu fmt -check
        run: tofu fmt -check -recursive

      - name: tofu validate
        run: tofu validate

      # Fix #111 (2026-05-10) — guard against the PR #1311 regression class.
      #
      # tftpl files (cloudinit-control-plane.tftpl, cloudinit-worker.tftpl,
      # any future *.tftpl) are consumed by tofu's `templatefile()` function
      # which parses ALL `${...}` interpolation sequences regardless of
      # YAML/HCL/shell context — including ones that appear inside YAML
      # comments. When a comment references a downstream shell-envsubst
      # variable like `${QA_FIXTURES_ENABLED:-false}`, tofu sees the colon
      # inside the interpolation and dies with:
      #
      #     Error: Extra characters after interpolation expression;
      #     Template interpolation doesn't expect a colon at this location.
      #
      # PR #1311 (Fix #73) shipped exactly this bug, broke `tofu plan`
      # immediately, and prov #9 (4204f0b0c5e37a80) wasted ~30 min before
      # PR #1328 caught and escaped the one offender. Without a CI guard,
      # the next operator who adds a similar comment will repeat the
      # incident.
      #
      # The fix is to use HCL's literal-dollar escape: `$$` → emits one
      # literal `$` from templatefile(), so `$${VAR:-default}` survives
      # tofu and reaches the cloud-init shell as `${VAR:-default}`.
      #
      # This grep scans every *.tftpl in infra/hetzner/ for any line that:
      #   - starts with `#` (a comment) — leading whitespace optional
      #   - contains a single-`$` `${UPPERCASE_VAR:-...}` interpolation —
      #     the colon-in-interpolation shape that breaks tofu
      # and fails the build with an actionable error message. The regex
      # uses PCRE's negative lookbehind `(?<!\$)` so correctly-escaped
      # `$${VAR:-default}` (which expands to literal `${VAR:-default}` after
      # templatefile()) does NOT trip the guard. Code lines (non-comment)
      # that reference shell vars are caught at `tofu validate` time; this
      # guard plugs the comment-line gap that validate misses because
      # templatefile() doesn't actually run during `validate`.
      #
      # Ubuntu-latest runners ship GNU grep with PCRE (`-P`) enabled.
      - name: tftpl shell-expansion escape guard (Fix #111)
        run: |
          set -euo pipefail
          violations=$(grep -rEnP '^\s*#.*(?<!\$)\$\{[A-Z_]+:-' *.tftpl || true)
          if [ -n "$violations" ]; then
            echo "::error title=Unescaped tftpl shell expansion::Use \$\${VAR:-default} (double-dollar) in tftpl YAML comments — bare \${VAR:-default} is consumed by tofu's templatefile() and breaks 'tofu plan' with 'Template interpolation doesn't expect a colon at this location' (see PR #1311 / PR #1328 / Fix #111)."
            echo "Offending lines:"
            echo "$violations"
            exit 1
          fi

      - name: tofu test (offline — mock_provider + override_resource)
        # The module's tests/multi_region.tftest.hcl exercises the
        # multi-region wiring shape WITHOUT touching real Hetzner.
        # `mock_provider "hcloud"` short-circuits API calls and
        # `override_resource minio_s3_bucket.main` bypasses the minio
        # provider's required-attribute schema check. Real-cloud E2E
        # provisioning lives in `.github/workflows/test-hetzner-e2e.yaml`
        # (gated on the `test/hetzner-e2e` PR label).
        run: tofu test
