Repository navigation
Initial commit #1
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
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| validate: | |
| name: Validate (analyzer, fmt, validate, test, tflint, trivy) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: "~> 1.9" | |
| - name: PSScriptAnalyzer | |
| shell: pwsh | |
| run: | | |
| Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | |
| Install-Module PSScriptAnalyzer -MinimumVersion 1.21.0 -Force -Scope CurrentUser | |
| $results = Invoke-ScriptAnalyzer -Path ./Sort-LdoTerraform.ps1 -Settings ./PSScriptAnalyzerSettings.psd1 | |
| if ($results) { $results | Format-Table -AutoSize | Out-String | Write-Host } | |
| $errors = @($results | Where-Object { $_.Severity -eq 'Error' }) | |
| if ($errors.Count -gt 0) { throw "PSScriptAnalyzer found $($errors.Count) error(s)." } | |
| Write-Host 'PSScriptAnalyzer: clean.' | |
| - name: Terraform fmt | |
| run: terraform fmt -check -recursive | |
| - name: Validate and test the module and examples | |
| run: | | |
| set -euo pipefail | |
| for d in . examples/minimal examples/complete; do | |
| echo "== ${d} ==" | |
| terraform -chdir="${d}" init -backend=false -input=false >/dev/null | |
| terraform -chdir="${d}" validate | |
| done | |
| terraform test | |
| - name: Setup TFLint | |
| uses: terraform-linters/setup-tflint@b480b8fcdaa6f2c577f8e4fa799e89e756bb7c93 # v6.2.2 | |
| - name: TFLint (examples) | |
| run: | | |
| set -euo pipefail | |
| for d in examples/*/; do | |
| echo "== ${d%/} ==" | |
| ( cd "${d%/}" && tflint --init && tflint --recursive ) | |
| done | |
| # The trivyignores input is avoided on purpose: trivy-action concatenates the listed files | |
| # into an extension-less temp file, and trivy decides the ignore-file format by extension, | |
| # so a YAML waiver file silently stops matching. The TRIVY_IGNOREFILE env var hands trivy | |
| # the file directly, extension intact. Trivy hard-fails when the file is missing, so the | |
| # repo always carries at least the stub .trivyignore.yaml. | |
| - name: Trivy config scan (report CRITICAL to LOW, non-blocking) | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| env: | |
| TRIVY_IGNOREFILE: .trivyignore.yaml | |
| with: | |
| scan-type: config | |
| scan-ref: . | |
| severity: CRITICAL,HIGH,MEDIUM,LOW | |
| exit-code: "0" | |
| - name: Trivy config gate (fail on HIGH or CRITICAL) | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| env: | |
| TRIVY_IGNOREFILE: .trivyignore.yaml | |
| with: | |
| scan-type: config | |
| scan-ref: . | |
| severity: HIGH,CRITICAL | |
| exit-code: "1" | |
| # Live apply-then-destroy of an example via the terraform-azure action. Runs only on a manual | |
| # dispatch, since it needs the org OIDC variables, the firewalled state account, and the org | |
| # secrets that the tenant bootstrap publishes. Trigger with: gh workflow run ci.yml | |
| self-test: | |
| name: Self-test (plan, apply, destroy) | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Plan and apply the example stacks | |
| uses: libre-devops/terraform-azure@v1 | |
| with: | |
| terraform-code-location: examples | |
| terraform-stack-to-run-json: '["minimal","complete"]' | |
| run-terraform-plan: true | |
| run-terraform-apply: true | |
| arm-client-id: ${{ vars.AZURE_CLIENT_ID }} | |
| arm-tenant-id: ${{ vars.AZURE_TENANT_ID }} | |
| arm-subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} | |
| firewall-storage-account-name: ${{ secrets.TFSTATE_STORAGE_ACCOUNT }} | |
| firewall-storage-resource-group: ${{ secrets.TFSTATE_RESOURCE_GROUP }} | |
| terraform-init-extra-args-json: '["-reconfigure","-upgrade","-backend-config=resource_group_name=${{ secrets.TFSTATE_RESOURCE_GROUP }}","-backend-config=storage_account_name=${{ secrets.TFSTATE_STORAGE_ACCOUNT }}","-backend-config=container_name=${{ secrets.TFSTATE_BLOB_CONTAINER }}"]' | |
| # ALWAYS runs, even when the apply step failed: a partial apply must still be torn down | |
| # rather than left for a human to notice. A destroy against a half-applied or empty state | |
| # is safe (it removes whatever landed). | |
| - name: Plan-destroy and destroy the example stacks | |
| if: ${{ always() }} | |
| uses: libre-devops/terraform-azure@v1 | |
| with: | |
| terraform-code-location: examples | |
| terraform-stack-to-run-json: '["minimal","complete"]' | |
| run-terraform-plan: false | |
| run-terraform-plan-destroy: true | |
| run-terraform-destroy: true | |
| arm-client-id: ${{ vars.AZURE_CLIENT_ID }} | |
| arm-tenant-id: ${{ vars.AZURE_TENANT_ID }} | |
| arm-subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} | |
| firewall-storage-account-name: ${{ secrets.TFSTATE_STORAGE_ACCOUNT }} | |
| firewall-storage-resource-group: ${{ secrets.TFSTATE_RESOURCE_GROUP }} | |
| terraform-init-extra-args-json: '["-reconfigure","-upgrade","-backend-config=resource_group_name=${{ secrets.TFSTATE_RESOURCE_GROUP }}","-backend-config=storage_account_name=${{ secrets.TFSTATE_STORAGE_ACCOUNT }}","-backend-config=container_name=${{ secrets.TFSTATE_BLOB_CONTAINER }}"]' |