fix(lambda): prevent recurring deployment plans (#489) #216
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| # yamllint disable rule:comments | |
| # yamllint disable rule:truthy | |
| name: IaC Policy | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| - renovatebot | |
| paths: | |
| - modules/**/*.tf | |
| - modules/**/*.tftest.hcl | |
| - modules/**/*.tofutest.hcl | |
| - examples/**/*.tf | |
| - policy/** | |
| - tests/iac/** | |
| - tests/tofu/** | |
| - scripts/ci_summary.py | |
| - pyproject.toml | |
| - uv.lock | |
| - .github/workflows/iac-policy.yml | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - modules/**/*.tf | |
| - modules/**/*.tftest.hcl | |
| - modules/**/*.tofutest.hcl | |
| - examples/**/*.tf | |
| - policy/** | |
| - tests/iac/** | |
| - tests/tofu/** | |
| - scripts/ci_summary.py | |
| - pyproject.toml | |
| - uv.lock | |
| - .github/workflows/iac-policy.yml | |
| permissions: {} # Default deny; jobs declare only the permissions they need. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| offline-iac-contracts: | |
| name: Offline IaC contract tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read # Required to checkout repository content. | |
| issues: write # Required to create or update sticky PR summary comments. | |
| pull-requests: write # Required to publish summaries back to pull requests. | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout Repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: '3.12' | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 | |
| with: | |
| version: 0.11.29 | |
| - name: Run offline IaC contract tests | |
| working-directory: tests | |
| run: | | |
| uv run --project .. --locked --only-group lambda-tests \ | |
| pytest -q iac --junitxml=iac-results.xml | |
| - name: Write offline IaC contract summary | |
| if: always() | |
| working-directory: tests | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| python ../scripts/ci_summary.py \ | |
| --title "Offline IaC contract tests" \ | |
| --junit "Offline IaC contracts=iac-results.xml" \ | |
| --coverage coverage.xml \ | |
| --output iac-contract-summary.md \ | |
| --comment-marker forge-ci-summary:offline-iac-contracts | |
| opentofu-tests: | |
| name: OpenTofu module tests (${{ matrix.opentofu.name }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read # Required to checkout repository content. | |
| issues: write # Required to create or update sticky PR summary comments. | |
| pull-requests: write # Required to publish summaries back to pull requests. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| opentofu: | |
| - name: minimum-supported | |
| version: 1.11.0 | |
| - name: latest-stable | |
| version: latest | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install OpenTofu | |
| uses: opentofu/setup-opentofu@a1320f892987e89d278cc92dc5adc984fb93aca4 # v2.0.2 | |
| with: | |
| tofu_version: ${{ matrix.opentofu.version }} | |
| tofu_wrapper: false | |
| cache: true | |
| github_token: ${{ github.token }} | |
| - name: Record OpenTofu version | |
| id: tofu-version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version="$( | |
| tofu version -json \ | |
| | jq -r '.terraform_version // .tofu_version // .opentofu_version // empty' | |
| )" | |
| if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "Invalid OpenTofu version: ${version}" >&2 | |
| exit 1 | |
| fi | |
| echo "version=${version}" >> "${GITHUB_OUTPUT}" | |
| - name: Prepare OpenTofu plugin cache | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cache_dir="${RUNNER_TEMP}/tofu-plugin-cache" | |
| mkdir -p "${cache_dir}" | |
| echo "TF_PLUGIN_CACHE_DIR=${cache_dir}" >> "${GITHUB_ENV}" | |
| - name: Cache OpenTofu providers | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ${{ runner.temp }}/tofu-plugin-cache | |
| key: tofu-${{ steps.tofu-version.outputs.version }}-providers-${{ hashFiles('modules/**/*.tf') }} | |
| restore-keys: | | |
| tofu-${{ steps.tofu-version.outputs.version }}-providers- | |
| tofu-providers- | |
| - name: Install TFLint | |
| env: | |
| TFLINT_VERSION: 0.55.1 | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| install_dir="${HOME}/.local/bin" | |
| mkdir -p "${install_dir}" | |
| curl -fsSL -o /tmp/tflint.zip \ | |
| "https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/tflint_linux_amd64.zip" | |
| unzip -oq /tmp/tflint.zip -d "${install_dir}" | |
| chmod +x "${install_dir}/tflint" | |
| - name: Show IaC tool versions | |
| shell: bash | |
| run: | | |
| tofu version | |
| tflint --version | |
| - name: Discover OpenTofu test modules | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| find modules -path '*/tests/*.tftest.hcl' -exec dirname {} \; \ | |
| | sed 's#/tests$##' \ | |
| | sort -u > .opentofu-test-modules | |
| test -s .opentofu-test-modules | |
| wc -l .opentofu-test-modules | |
| - name: Check OpenTofu formatting | |
| shell: bash | |
| run: tofu fmt -check -recursive | |
| - name: Initialize and validate modules | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| while IFS= read -r module; do | |
| echo "::group::${module}" | |
| tofu -chdir="${module}" init -backend=false -input=false | |
| tofu -chdir="${module}" validate -no-color | |
| echo "::endgroup::" | |
| done < .opentofu-test-modules | |
| - name: Run TFLint | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| while IFS= read -r module; do | |
| echo "::group::${module}" | |
| # Static .tftest.hcl contracts already enforce required_providers. | |
| # TFLint 0.55 does not understand OpenTofu provider for_each | |
| # references used by modules/helpers/forge_subscription. | |
| tflint --chdir="${module}" --disable-rule=terraform_required_providers --no-color | |
| echo "::endgroup::" | |
| done < .opentofu-test-modules | |
| - name: Run native OpenTofu tests | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| OPENTOFU_NAME: ${{ matrix.opentofu.name }} | |
| OPENTOFU_VERSION: ${{ steps.tofu-version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| summary_table="$(mktemp)" | |
| failures=0 | |
| total=0 | |
| while IFS= read -r module; do | |
| echo "::group::${module}" | |
| total=$((total + 1)) | |
| log_file="${RUNNER_TEMP}/tofu-test-${total}.log" | |
| if tofu -chdir="${module}" test -no-color > "${log_file}" 2>&1; then | |
| result="passed" | |
| else | |
| result="failed" | |
| failures=$((failures + 1)) | |
| fi | |
| cat "${log_file}" | |
| printf '| `%s` | %s |\n' "${module}" "${result}" >> "${summary_table}" | |
| echo "::endgroup::" | |
| done < .opentofu-test-modules | |
| if (( failures == 0 )); then | |
| result="passed" | |
| else | |
| result="failed" | |
| fi | |
| summary_file="opentofu-${OPENTOFU_NAME}-summary.md" | |
| { | |
| printf '## OpenTofu module tests (%s)\n\n' "${OPENTOFU_NAME}" | |
| printf '**Result:** %s\n\n' "${result}" | |
| printf '**OpenTofu version:** `%s`\n\n' "${OPENTOFU_VERSION}" | |
| printf '| Metric | Count |\n' | |
| printf '| --- | ---: |\n' | |
| printf '| Passed modules | %s |\n' "$((total - failures))" | |
| printf '| Failed modules | %s |\n' "${failures}" | |
| printf '| Total modules | %s |\n\n' "${total}" | |
| printf '| Module | Result |\n' | |
| printf '| --- | --- |\n' | |
| cat "${summary_table}" | |
| printf '\n' | |
| } > "${summary_file}" | |
| python scripts/ci_summary.py \ | |
| --title "OpenTofu module tests (${OPENTOFU_NAME})" \ | |
| --input-markdown "${summary_file}" \ | |
| --output "${summary_file}" \ | |
| --comment-marker "forge-ci-summary:opentofu-${OPENTOFU_NAME}" | |
| (( failures == 0 )) | |
| opa-policy: | |
| name: OPA/conftest tenant-isolation gate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read # Required to checkout repository content. | |
| issues: write # Required to create or update sticky PR summary comments. | |
| pull-requests: write # Required to publish summaries back to pull requests. | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout Repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install conftest | |
| env: | |
| CONFTEST_VERSION: 0.56.0 | |
| run: | | |
| curl -fsSL -o conftest.tar.gz \ | |
| "https://github.com/open-policy-agent/conftest/releases/download/v${CONFTEST_VERSION}/conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz" | |
| tar xzf conftest.tar.gz conftest | |
| sudo install conftest /usr/local/bin/conftest | |
| conftest --version | |
| - name: Verify policy logic (hard gate) | |
| id: conftest-verify | |
| run: conftest verify --policy policy/opa | |
| - name: Write OPA policy summary | |
| if: always() | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| VERIFY_OUTCOME: ${{ steps.conftest-verify.outcome }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${VERIFY_OUTCOME}" == "success" ]]; then | |
| result="passed" | |
| else | |
| result="failed" | |
| fi | |
| { | |
| printf '## OPA/conftest tenant-isolation gate\n\n' | |
| printf '**Result:** %s\n\n' "${result}" | |
| printf '| Gate | Result |\n' | |
| printf '| --- | --- |\n' | |
| printf '| `conftest verify --policy policy/opa` | %s |\n\n' "${result}" | |
| } > opa-policy-summary.md | |
| python scripts/ci_summary.py \ | |
| --title "OPA/conftest tenant-isolation gate" \ | |
| --input-markdown opa-policy-summary.md \ | |
| --output opa-policy-summary.md \ | |
| --comment-marker forge-ci-summary:opa-policy | |
| checkov: | |
| name: Checkov IaC scan (informational) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read # Required to checkout repository content. | |
| security-events: write # Required to upload Checkov SARIF to code scanning. | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout Repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: '3.12' | |
| - name: Run Checkov (soft-fail, informational) | |
| # Installed via pip + pinned (Renovate-managed) rather than a 3rd-party | |
| # action SHA. Informational: never blocks the PR (continue-on-error + | |
| # --soft-fail); findings surface in the Security tab. Flip --soft-fail | |
| # off after triage (register follow-up). | |
| # NOTE: --output-file-path takes the SARIF FILENAME (not a dir) in this | |
| # checkov version; writing to '.' raises IsADirectoryError. | |
| continue-on-error: true | |
| run: | | |
| pip install "checkov==3.2.334" | |
| checkov --directory modules/ --framework terraform \ | |
| --skip-path '.*/\.terraform/.*' \ | |
| --skip-download \ | |
| --soft-fail \ | |
| --output cli --output sarif --output-file-path console,results.sarif | |
| - name: Upload Checkov SARIF | |
| if: always() | |
| continue-on-error: true | |
| uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 | |
| with: | |
| sarif_file: results.sarif |