diff --git a/.github/workflows/generate-architecture-diagrams.yml b/.github/workflows/generate-architecture-diagrams.yml deleted file mode 100644 index b05c212..0000000 --- a/.github/workflows/generate-architecture-diagrams.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Generate Architecture Diagrams - -on: - workflow_dispatch: - pull_request: - paths: - - "src/**" - - "modules/**" - - "docs/diagrams/assets/**" - - "docs/diagrams/scripts/generate_example_architecture.py" - - "docs/diagrams/README.md" - push: - branches: - - main - paths: - - "src/**" - - "modules/**" - - "docs/diagrams/assets/**" - - "docs/diagrams/scripts/generate_example_architecture.py" - - "docs/diagrams/README.md" - -permissions: - contents: write - -jobs: - generate-diagrams: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - - - name: Install Mermaid CLI - run: npm install -g @mermaid-js/mermaid-cli - - - name: Generate architecture diagrams - run: python3 docs/diagrams/scripts/generate_example_architecture.py --out-dir docs/diagrams - - - name: Render Mermaid SVG diagrams - run: | - for diagram in docs/diagrams/*.mmd; do - output="${diagram%.mmd}.svg" - mmdc -i "$diagram" -o "$output" -c .github/mermaid-config.json -p .github/mermaid-puppeteer-config.json - done - - - name: Verify generated diagrams are committed (PR) - if: github.event_name == 'pull_request' - run: | - if [[ -n "$(git status --porcelain -- docs/diagrams)" ]]; then - echo "::error::Generated diagrams are out of date. Run the generator locally and commit the changes." - git status --short -- docs/diagrams - exit 1 - fi - - - name: Commit updated diagrams (main) - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.actor != 'github-actions[bot]' - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "chore(diagrams): regenerate architecture diagrams" - file_pattern: docs/diagrams/*.mmd docs/diagrams/*.mmd.md docs/diagrams/*.svg diff --git a/.github/workflows/tflint.yml b/.github/workflows/tflint.yml index a8c673f..c1115c2 100644 --- a/.github/workflows/tflint.yml +++ b/.github/workflows/tflint.yml @@ -1,41 +1,118 @@ -name: tflint +name: Terraform Quality on: pull_request: + paths: + - "src/**" + - ".github/workflows/tflint.yml" push: branches: - main + paths: + - "src/**" + - ".github/workflows/tflint.yml" + +permissions: + contents: read + +concurrency: + group: terraform-quality-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - tflint: + format: + name: Format + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@v1 + with: + tofu_version: "1.12.5" + + - name: Check formatting + run: tofu -chdir=src fmt -check -diff -recursive + + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@v1 + with: + tofu_version: "1.12.5" + + - name: Initialize and validate Terraform configurations + shell: bash + run: | + set -euo pipefail + mapfile -t terraform_directories < <( + find src -type f -name '*.tf' -not -path '*/.terraform/*' -printf '%h\n' | sort -u + ) + + for directory in "${terraform_directories[@]}"; do + echo "Validating $directory" + tofu -chdir="$directory" init -backend=false -input=false -no-color + tofu -chdir="$directory" validate -no-color + done + + test: + name: Configuration Tests runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + timeout-minutes: 30 + env: + STACKIT_SERVICE_ACCOUNT_KEY: ${{ secrets.STACKIT_SERVICE_ACCOUNT_KEY }} steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@v1 with: - terraform_version: 1.9.8 + tofu_version: "1.12.5" + + - name: Configure STACKIT credentials + shell: bash + run: | + set -euo pipefail + test -n "$STACKIT_SERVICE_ACCOUNT_KEY" + install -d -m 700 "$HOME/.stackit" + printf '%s' "$STACKIT_SERVICE_ACCOUNT_KEY" > "$HOME/.stackit/credentials.json" + chmod 600 "$HOME/.stackit/credentials.json" + + - name: Initialize test configuration + run: tofu -chdir=src init -backend=false -input=false -no-color + + - name: Run configuration tests + run: tofu -chdir=src test -no-color + + tflint: + name: TFLint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 - name: Setup TFLint uses: terraform-linters/setup-tflint@v4 with: - tflint_version: latest + tflint_version: v0.60.0 - name: Show version run: tflint --version - name: Init TFLint - run: tflint --init + run: tflint --chdir=src --init - name: Run TFLint (blocking except unused declarations) - run: tflint --recursive --format compact --minimum-failure-severity=warning --disable-rule=terraform_unused_declarations --disable-rule=terraform_required_providers + run: tflint --chdir=src --recursive --format compact --minimum-failure-severity=warning --disable-rule=terraform_unused_declarations --disable-rule=terraform_required_providers - name: Run TFLint (unused declarations report only) continue-on-error: true - run: tflint --recursive --format compact --only=terraform_unused_declarations - - # runs 6h - # - name: Validate STACKIT flavors (live) - # run: python3 docs/diagrams/scripts/validate_stackit_flavors.py \ No newline at end of file + run: tflint --chdir=src --recursive --format compact --only=terraform_unused_declarations diff --git a/.github/workflows/validate-stackit-flavors.yml b/.github/workflows/validate-stackit-flavors.yml new file mode 100644 index 0000000..22c2a13 --- /dev/null +++ b/.github/workflows/validate-stackit-flavors.yml @@ -0,0 +1,21 @@ +name: Validate STACKIT Flavors + +on: + workflow_dispatch: + schedule: + - cron: "17 5 * * 1" + +permissions: + contents: read + +jobs: + validate: + name: Validate live flavors + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate configured flavors against STACKIT SKUs + run: python3 docs/diagrams/scripts/validate_stackit_flavors.py diff --git a/src/_firewall-bootstrap.tf b/src/_firewall-bootstrap.tf index b39625f..6c8976a 100644 --- a/src/_firewall-bootstrap.tf +++ b/src/_firewall-bootstrap.tf @@ -108,52 +108,52 @@ ephemeral "vault_kv_secret_v2" "firewall_api" { # # Injected last, so these three keys win over an entry of the same name in the .tfvars. locals { - firewall_ha_enabled = try(var.connectivity.firewall.ha, null) != null - - firewall_ha_aliases = local.firewall_ha_enabled ? { - fw_cluster = { - type = "host" - enabled = true - description = "LAN addresses of the firewall HA pair" - content = try(module.connectivity[0].firewall_cluster_lan_ips, []) - update_freq = null - stats = false - } - } : {} - - firewall_ha_rule_defaults = { - sequence = 100 - enabled = true - action = "pass" - direction = "in" - interfaces = ["lan"] - protocol = "any" - ip_protocol = "inet" - quick = true - source_net = "fw_cluster" - source_port = null - source_invert = false - destination_net = "fw_cluster" - destination_port = null - destination_invert = false - log = false - description = null + firewall_ha_enabled = try(var.connectivity.firewall.ha, null) != null + + firewall_ha_aliases = local.firewall_ha_enabled ? { + fw_cluster = { + type = "host" + enabled = true + description = "LAN addresses of the firewall HA pair" + content = try(module.connectivity[0].firewall_cluster_lan_ips, []) + update_freq = null + stats = false } + } : {} + + firewall_ha_rule_defaults = { + sequence = 100 + enabled = true + action = "pass" + direction = "in" + interfaces = ["lan"] + protocol = "any" + ip_protocol = "inet" + quick = true + source_net = "fw_cluster" + source_port = null + source_invert = false + destination_net = "fw_cluster" + destination_port = null + destination_invert = false + log = false + description = null + } - # 90/91 puts them ahead of every landing zone rule, which start at 100 — block-lz-to-lz - # in particular. They sit behind the two floating GUI rules at 10/20, which match TCP on - # port 443 only and can therefore never swallow CARP or pfsync. - firewall_ha_rules = local.firewall_ha_enabled ? { - allow-fw-carp = merge(local.firewall_ha_rule_defaults, { - sequence = 90 - protocol = "CARP" - description = "Unicast CARP advertisements between the HA pair" - }) - - allow-fw-pfsync = merge(local.firewall_ha_rule_defaults, { - sequence = 91 - protocol = "PFSYNC" - description = "pfsync state replication between the HA pair" - }) - } : {} + # 90/91 puts them ahead of every landing zone rule, which start at 100 — block-lz-to-lz + # in particular. They sit behind the two floating GUI rules at 10/20, which match TCP on + # port 443 only and can therefore never swallow CARP or pfsync. + firewall_ha_rules = local.firewall_ha_enabled ? { + allow-fw-carp = merge(local.firewall_ha_rule_defaults, { + sequence = 90 + protocol = "CARP" + description = "Unicast CARP advertisements between the HA pair" + }) + + allow-fw-pfsync = merge(local.firewall_ha_rule_defaults, { + sequence = 91 + protocol = "PFSYNC" + description = "pfsync state replication between the HA pair" + }) + } : {} } \ No newline at end of file diff --git a/src/modules/debug-bastion/terraform.tf b/src/modules/debug-bastion/terraform.tf index bb2ef41..3ebb3ce 100644 --- a/src/modules/debug-bastion/terraform.tf +++ b/src/modules/debug-bastion/terraform.tf @@ -1,4 +1,6 @@ terraform { + required_version = ">= 1.11" + required_providers { stackit = { source = "stackitcloud/stackit" diff --git a/src/modules/namespace-service-demo/main.tf b/src/modules/namespace-service-demo/main.tf index 9852b86..f69e0ff 100644 --- a/src/modules/namespace-service-demo/main.tf +++ b/src/modules/namespace-service-demo/main.tf @@ -1,4 +1,6 @@ terraform { + required_version = ">= 1.11" + required_providers { stackit = { source = "stackitcloud/stackit"