Test infra plan/apply with multiple projects#354
Conversation
|
| name: Get Terraform Version | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| terraform_version: ${{ steps.get-version.outputs.terraform_version }} | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
|
|
||
| - name: Get terraform version from .terraform-version file | ||
| id: get-version | ||
| uses: pagopa/dx/.github/actions/get-terraform-version@main | ||
| with: | ||
| default_version: "1.10.4" | ||
|
|
||
| detect-projects: |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, fix this by explicitly setting a permissions block that grants the minimal required scopes for the GITHUB_TOKEN. You can add it at the workflow root so it applies to all jobs, or per job if some need additional rights. For a workflow that only needs to read repository contents (e.g., checkout code, run Terraform, static analysis) and not update PRs or issues, contents: read is usually sufficient.
For this specific file, the best minimal non-breaking fix is to add a workflow-level permissions block right after the on: section and before env:. This will apply to all jobs (get-terraform-version, detect-projects, tf-modules-check, tf_plan, etc.) unless a job explicitly overrides it. Based on the visible snippets, these jobs only need to read repository contents, so contents: read is an appropriate least-privilege setting. No imports or additional methods are required; this is purely a YAML configuration change in .github/workflows/infra_apply.yaml.
Concretely:
- In
.github/workflows/infra_apply.yaml, insert:
permissions:
contents: readafter the workflow_call inputs block (line 35/36) and before the existing env: block (line 37). No other changes are necessary.
| @@ -34,6 +34,9 @@ | ||
| required: false | ||
| default: "" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} | ||
| ARM_USE_OIDC: true |
|
|
||
| - name: Get terraform version from .terraform-version file | ||
| id: get-version | ||
| uses: pagopa/dx/.github/actions/get-terraform-version@main |
Check warning
Code scanning / CodeQL
Unpinned tag for a non-immutable Action in workflow Medium
| name: Detect Terraform Projects | ||
| runs-on: ubuntu-latest | ||
| needs: [get-terraform-version] | ||
| outputs: | ||
| matrix: ${{ steps.detect.outputs.matrix }} | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Detect Terraform projects | ||
| id: detect | ||
| env: | ||
| BASE_PATH: ${{ inputs.base_path }} | ||
| ENVIRONMENT: ${{ inputs.environment }} | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| BASE_REF: ${{ github.event.pull_request.base.ref }} | ||
| BEFORE: ${{ github.event.before }} | ||
| SHA: ${{ github.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| BASE_PATH="${BASE_PATH%/}" | ||
| DIR="$BASE_PATH/$ENVIRONMENT" | ||
|
|
||
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | ||
| CHANGED=$(find "$DIR" -maxdepth 2 -name '*.tf' -not -path "*/_modules/*" 2>/dev/null || true) | ||
| elif [ "$EVENT_NAME" = "pull_request" ]; then | ||
| CHANGED=$(git diff --name-only "origin/${BASE_REF}...HEAD" -- "$DIR" || true) | ||
| else | ||
| CHANGED=$(git diff --name-only "$BEFORE" "$SHA" -- "$DIR" || true) | ||
| fi | ||
|
|
||
| # The two layouts are mutually exclusive: | ||
| # - flat: a .tf exists directly in $DIR → $DIR is the single project root | ||
| # - multi-project: no .tf directly in $DIR → each first-level subdir is a project root | ||
| IS_FLAT=$(printf '%s\n' "$CHANGED" | while IFS= read -r file; do | ||
| rel="${file#"${DIR}/"}" | ||
| case "$rel" in | ||
| */*) ;; # file in a subdir: not a flat indicator | ||
| *.tf) echo 1; break ;; # .tf directly in $DIR: flat project | ||
| esac | ||
| done) | ||
|
|
||
| if [ -n "$IS_FLAT" ]; then | ||
| MATRIX=$(jq -cn --arg d "$DIR" '[$d]') | ||
| else | ||
| MATRIX=$(printf '%s\n' "$CHANGED" \ | ||
| | while IFS= read -r file; do | ||
| [ -z "$file" ] && continue | ||
| rel="${file#"${DIR}/"}" | ||
| case "$rel" in */*) echo "${DIR}/${rel%%/*}" ;; esac | ||
| done \ | ||
| | sort -u \ | ||
| | jq -R . | jq -sc .) | ||
| fi | ||
|
|
||
| MATRIX=${MATRIX:-[]} | ||
| echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" | ||
| echo "Detected matrix: $MATRIX" | ||
|
|
||
| tf-modules-check: |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
To fix the problem, explicitly define a permissions block that restricts the GITHUB_TOKEN to the least privileges needed. In this workflow, jobs primarily read repository contents (checkout, git diff) and do not appear to need write access or special scopes like pull-requests: write. The minimal safe baseline is contents: read at the workflow root so that it applies to all jobs (get-terraform-version, detect-projects, tf-modules-check, tf_plan, tf_apply, etc.) that don’t override it.
The best fix without changing existing functionality is to add a root-level permissions block just after the on: trigger configuration (before env: or concurrency:). This will ensure all jobs use a read-only token for repository contents. No other code changes are required in the jobs themselves. Concretely, in .github/workflows/infra_apply.yaml, insert:
permissions:
contents: readbetween the on: block and the env: block (around existing lines 36–37). This does not affect any other behavior of the workflow, only the GitHub token’s allowed operations.
| @@ -34,6 +34,9 @@ | ||
| required: false | ||
| default: "" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} | ||
| ARM_USE_OIDC: true |
| uses: pagopa/dx/.github/workflows/static_analysis.yaml@main | ||
| name: Check terraform registry modules hashes | ||
| needs: [get-terraform-version, detect-projects] | ||
| if: ${{ needs.detect-projects.outputs.matrix != '[]' }} | ||
| secrets: inherit | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| dir: ${{ fromJSON(needs.detect-projects.outputs.matrix) }} | ||
| with: | ||
| terraform_version: ${{ needs.get-terraform-version.outputs.terraform_version }} | ||
| check_to_run: lock_modules | ||
| folder: ${{ matrix.dir }}/ | ||
| verbose: true | ||
|
|
||
| tf_plan: |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
To fix the problem, explicitly declare a permissions block to restrict the GITHUB_TOKEN on the tf-modules-check job (and optionally at the workflow root, but the reported issue is specifically on this job). Since this job runs static analysis and does not obviously need to write to the repo, a safe and minimal starting point is contents: read. If the reusable workflow requires additional scopes, they can be added later, but we should not assume write access is needed.
Concretely:
- Edit
.github/workflows/infra_apply.yaml. - In the
tf-modules-checkjob definition (around line 134–149), add apermissions:section indented to the same level asneeds:andstrategy:. - Set:
contents: readas a minimal permission.- If you know this job needs nothing else (no PR comments, etc.), keep only that. Given the provided snippet, we will add just
contents: read.
No imports or other code changes are needed; only the YAML for this job is updated.
| @@ -137,6 +137,8 @@ | ||
| needs: [get-terraform-version, detect-projects] | ||
| if: ${{ needs.detect-projects.outputs.matrix != '[]' }} | ||
| secrets: inherit | ||
| permissions: | ||
| contents: read | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: |
| done | ||
|
|
||
| - name: Cloud Login | ||
| uses: pagopa/dx/actions/csp-login@main |
Check warning
Code scanning / CodeQL
Unpinned tag for a non-immutable Action in workflow Medium
|
|
||
| - name: Get terraform version from .terraform-version file | ||
| id: get-version | ||
| uses: pagopa/dx/.github/actions/get-terraform-version@main |
Check warning
Code scanning / CodeQL
Unpinned tag for a non-immutable Action in workflow Medium
| done | ||
|
|
||
| - name: Cloud Login | ||
| uses: pagopa/dx/actions/csp-login@main |
Check warning
Code scanning / CodeQL
Unpinned tag for a non-immutable Action in workflow Medium
|
|
||
| - name: Terraform Setup | ||
| id: set-terraform-version | ||
| uses: pagopa/dx/.github/actions/terraform-setup@main |
Check warning
Code scanning / CodeQL
Unpinned tag for a non-immutable Action in workflow Medium
|
|
||
| - name: Terraform Plan | ||
| id: plan | ||
| uses: pagopa/dx/actions/filter-terraform-plan@main |
Check warning
Code scanning / CodeQL
Unpinned tag for a non-immutable Action in workflow Medium
| - name: Post Plan on PR | ||
| id: comment | ||
| if: always() && github.event_name == 'pull_request' | ||
| uses: pagopa/dx/actions/pr-comment@main |
Check warning
Code scanning / CodeQL
Unpinned tag for a non-immutable Action in workflow Medium
📋 Pre-commit Output LogGenerated on Tue Mar 31 15:12:23 UTC 2026 |
| name: Fork PR Gate | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| run: ${{ steps.evaluate.outputs.run }} | ||
| steps: | ||
| - name: Evaluate fork PR | ||
| id: evaluate | ||
| env: | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | ||
| CURRENT_REPO: ${{ github.repository }} | ||
| run: | | ||
| # Only block forked PRs; allow all other events | ||
| if [ "$EVENT_NAME" = "pull_request" ] && [ -n "$PR_HEAD_REPO" ] && [ "$PR_HEAD_REPO" != "$CURRENT_REPO" ]; then | ||
| echo "Blocking forked PR from $PR_HEAD_REPO" | ||
| echo "run=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "run=true" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| get-terraform-version: |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
Generally, the fix is to explicitly declare a permissions block for the workflow (or per job) that grants only the minimal scopes required. Since this workflow primarily checks out code and runs Terraform, it only needs read access to repository contents by default.
The best minimally invasive change is to add a root-level permissions block near the top of .github/workflows/infra_plan.yaml (for example, immediately after the on: configuration and before env:). This block should set contents: read, which aligns with GitHub’s recommended baseline for workflows that only need to read the repo. All jobs (including gate, get-terraform-version, and subsequent Terraform-related jobs) will then inherit these least-privilege permissions, preserving existing functionality while constraining the GITHUB_TOKEN by default. No imports or additional methods are needed because this is a YAML configuration change only.
| @@ -34,6 +34,9 @@ | ||
| required: false | ||
| default: "" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} | ||
| ARM_USE_OIDC: true |
| name: Get Terraform Version | ||
| runs-on: ubuntu-latest | ||
| needs: gate | ||
| if: ${{ needs.gate.outputs.run == 'true' }} | ||
| outputs: | ||
| terraform_version: ${{ steps.get-version.outputs.terraform_version }} | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
|
|
||
| - name: Get terraform version from .terraform-version file | ||
| id: get-version | ||
| uses: pagopa/dx/.github/actions/get-terraform-version@main | ||
| with: | ||
| default_version: "1.10.4" | ||
|
|
||
| detect-projects: |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, fix this by explicitly declaring a permissions: block that grants only the minimal required permissions to the GITHUB_TOKEN. You can put this block at the workflow root (applies to all jobs not overriding it) or on the specific job that CodeQL flagged. Given the suggestion and the job’s behavior (only checking out code and running an internal action), contents: read is sufficient.
The best minimally invasive fix is to add a root-level permissions: block after the on: section so it applies to all jobs, using contents: read. This both resolves the CodeQL warning and documents that the workflow only needs read access to the repository contents. No imports or additional methods are needed; this is a YAML-only change within .github/workflows/infra_plan.yaml.
Concretely, in .github/workflows/infra_plan.yaml, insert:
permissions:
contents: readbetween the on: workflow_call: ... inputs block and the existing env: block (after line 35 and before line 37 in the snippet). This keeps all existing functionality while constraining the GITHUB_TOKEN for all jobs, including get-terraform-version.
| @@ -34,6 +34,9 @@ | ||
| required: false | ||
| default: "" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} | ||
| ARM_USE_OIDC: true |
| name: Detect Terraform Projects | ||
| runs-on: ubuntu-latest | ||
| needs: [gate, get-terraform-version] | ||
| if: ${{ needs.gate.outputs.run == 'true' }} | ||
| outputs: | ||
| matrix: ${{ steps.detect.outputs.matrix }} | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Detect Terraform projects | ||
| id: detect | ||
| env: | ||
| BASE_PATH: ${{ inputs.base_path }} | ||
| ENVIRONMENT: ${{ inputs.environment }} | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| BASE_REF: ${{ github.event.pull_request.base.ref }} | ||
| BEFORE: ${{ github.event.before }} | ||
| SHA: ${{ github.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| BASE_PATH="${BASE_PATH%/}" | ||
| DIR="$BASE_PATH/$ENVIRONMENT" | ||
|
|
||
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | ||
| CHANGED=$(find "$DIR" -maxdepth 2 -name '*.tf' -not -path "*/_modules/*" 2>/dev/null || true) | ||
| elif [ "$EVENT_NAME" = "pull_request" ]; then | ||
| CHANGED=$(git diff --name-only "origin/${BASE_REF}...HEAD" -- "$DIR" || true) | ||
| else | ||
| CHANGED=$(git diff --name-only "$BEFORE" "$SHA" -- "$DIR" || true) | ||
| fi | ||
|
|
||
| # The two layouts are mutually exclusive: | ||
| # - flat: a .tf exists directly in $DIR → $DIR is the single project root | ||
| # - multi-project: no .tf directly in $DIR → each first-level subdir is a project root | ||
| IS_FLAT=$(printf '%s\n' "$CHANGED" | while IFS= read -r file; do | ||
| rel="${file#"${DIR}/"}" | ||
| case "$rel" in | ||
| */*) ;; # file in a subdir: not a flat indicator | ||
| *.tf) echo 1; break ;; # .tf directly in $DIR: flat project | ||
| esac | ||
| done) | ||
|
|
||
| if [ -n "$IS_FLAT" ]; then | ||
| MATRIX=$(jq -cn --arg d "$DIR" '[$d]') | ||
| else | ||
| MATRIX=$(printf '%s\n' "$CHANGED" \ | ||
| | while IFS= read -r file; do | ||
| [ -z "$file" ] && continue | ||
| rel="${file#"${DIR}/"}" | ||
| case "$rel" in */*) echo "${DIR}/${rel%%/*}" ;; esac | ||
| done \ | ||
| | sort -u \ | ||
| | jq -R . | jq -sc .) | ||
| fi | ||
|
|
||
| MATRIX=${MATRIX:-[]} | ||
| echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" | ||
| echo "Detected matrix: $MATRIX" | ||
|
|
||
| tf-modules-check: |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, the problem is fixed by explicitly defining a permissions block that limits the GITHUB_TOKEN to the minimal scopes required. For this workflow, all shown jobs (gate, get-terraform-version, detect-projects, plus downstream reusable-workflow jobs) only need to read repository contents and metadata; they do not push commits, create releases, or modify issues/PRs. Therefore, contents: read at the workflow root is an appropriate default minimal permission. If any downstream reusable workflow requires broader permissions, it can define or override its own permissions block.
The best fix without changing functionality is to add a root-level permissions: section near the top of .github/workflows/infra_plan.yaml, alongside on: and env:, setting contents: read. This restricts the default for all jobs that don’t define permissions themselves, including detect-projects (the one flagged by CodeQL). No other code changes, imports, or definitions are needed, and the workflow behavior remains the same because all current operations are compatible with read-only repository access.
Concretely: in .github/workflows/infra_plan.yaml, between the on: block (lines 1–12 shown) and the env: block (line 37), add:
permissions:
contents: readNo additional methods or dependencies are required.
| @@ -34,6 +34,9 @@ | ||
| required: false | ||
| default: "" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} | ||
| ARM_USE_OIDC: true |
| uses: pagopa/dx/.github/workflows/static_analysis.yaml@main | ||
| name: Check terraform registry modules hashes | ||
| needs: [gate, get-terraform-version, detect-projects] | ||
| if: ${{ needs.gate.outputs.run == 'true' && needs.detect-projects.outputs.matrix != '[]' }} | ||
| secrets: inherit | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| dir: ${{ fromJSON(needs.detect-projects.outputs.matrix) }} | ||
| with: | ||
| terraform_version: ${{ needs.get-terraform-version.outputs.terraform_version }} | ||
| check_to_run: lock_modules | ||
| folder: ${{ matrix.dir }}/ | ||
| verbose: true | ||
|
|
||
| tf_plan: |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
To fix this, we should add an explicit permissions block to the tf-modules-check job so that the GITHUB_TOKEN is restricted to only what is needed. Since this job runs a static analysis workflow and does not appear to push code or change repository state, a conservative and safe default is read-only repository contents: contents: read. If the called reusable workflow needs more (e.g., to comment on PRs), that should be granted there; in this caller, we’ll keep it minimal.
Concretely, in .github/workflows/infra_plan.yaml, within the tf-modules-check job (starting at line 158), add a permissions section at the same indentation level as needs, if, secrets, and strategy. The new block should be:
permissions:
contents: readThis does not change any existing logic or behavior beyond restricting the default GITHUB_TOKEN rights and aligns with the least‑privilege recommendation. No imports or additional definitions are required.
| @@ -161,6 +161,8 @@ | ||
| needs: [gate, get-terraform-version, detect-projects] | ||
| if: ${{ needs.gate.outputs.run == 'true' && needs.detect-projects.outputs.matrix != '[]' }} | ||
| secrets: inherit | ||
| permissions: | ||
| contents: read | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: |
|
Tip ✅ All Terraform module locks are up to dateNo module changes detected - everything is in sync! 📋 Pre-commit Output LogGenerated on Thu Apr 2 11:21:48 UTC 2026 |
|
Tip ✅ All Terraform module locks are up to dateNo module changes detected - everything is in sync! 📋 Pre-commit Output LogGenerated on Thu Apr 2 11:21:52 UTC 2026 |
|
Tip ✅ All Terraform module locks are up to dateNo module changes detected - everything is in sync! 📋 Pre-commit Output LogGenerated on Thu Apr 2 11:21:53 UTC 2026 |
📖 Terraform Plan (infra/resources/dev/dummy_project) - successShow PlanNo changes detected. |
📖 Terraform Plan (infra/resources/dev/base_project) - successShow PlanNo changes detected. |
PR for testing