name: BUILD | Terraform # TODO: Do not plan on environment that is not changed to prevent lockins. # TODO: Optimize steps (reduce WET code). # TODO: Test execution with multiple environments, modules and change on main, on PR merge and on PR update. # TODO: Add step for cost limit check. # TODO: Add dependency graph generation per environment. # TODO: Add a notification alert to Slack on fail/success. # TODO: Add caching for the .terraform folder between jobs and workflows. # TODO: Optimize checkout steps with a single checkout and shallow clone. # TODO: Add manual workflow for unlocking a specific environment. # TODO: Add condition to stop execution if there are no changes in the PR (verify with testing). # TODO: Split tests from build workflow. # TODO: Integrate SARIF reports where possible. on: workflow_dispatch: pull_request: branches: - main types: - closed env: GOOGLE_PROJECT: ${{ vars.GOOGLE_PROJECT }} GOOGLE_REGION: ${{ vars.GOOGLE_REGION }} GOOGLE_ZONE: ${{ vars.GOOGLE_ZONE }} GITHUB_TOKEN: ${{ secrets.TF_VAR_GITHUB_TOKEN }} GITHUB_OWNER: ${{ secrets.TF_VAR_GITHUB_OWNER }} jobs: ###################################################################### # LINT: Check Terraform formatting and perform additional checks on all environments. ###################################################################### # TODO: Add Megalitner. lint: name: Lint runs-on: self-hosted steps: - name: Setup | Repository uses: actions/checkout@v3 - name: Setup | Terraform uses: hashicorp/setup-terraform@v2 with: terraform_version: 1.6.2 - name: Lint | Terraform run: terraform fmt -check -diff -recursive ./environments ###################################################################### # SCAN: Check repository for leaked secrets. ###################################################################### scan: name: Secrets Scan runs-on: self-hosted steps: - name: Setup | Repository uses: actions/checkout@v3 - name: Scan | Secrets uses: max/secret-scan@master with: exclude_path: ".env.example" continue-on-error: true ###################################################################### # SAST: Perform static analysis on Terraform code per environment. ###################################################################### # TODO: Make those blocking and add a way to ignore issues. sast: name: SAST needs: - lint # - scan runs-on: self-hosted permissions: contents: read pull-requests: write defaults: run: working-directory: environments/${{ matrix.path }} strategy: fail-fast: false matrix: path: - dev-01 - dev-01-k8s - iam - mgmt-01 - prod-01 - prod-01-k8s steps: - name: Setup | Repository uses: actions/checkout@v3 # BUG: Fix only one message per PR even when multiple environments are scanned. - name: SAST | KICS scan uses: checkmarx/kics-github-action@v1.7.0 with: ignore_on_exit: results enable_comments: true fail_on: medium platform_type: terraform cloud_provider: GCP path: environments/${{ matrix.path }} output_path: myResults/ - name: SAST | tfsec scan continue-on-error: true uses: aquasecurity/tfsec-pr-commenter-action@v1.2.0 with: working_directory: "" github_token: ${{ secrets.GITHUB_TOKEN }} ###################################################################### # BUILD: Plan, validate and perform other Terraform procedures per environment. ###################################################################### build: name: Build environment: ${{ matrix.path }} needs: - lint - sast runs-on: self-hosted strategy: fail-fast: false matrix: path: - dev-01 - dev-01-k8s - iam - mgmt-01 - prod-01 - prod-01-k8s steps: - name: Show Region run: | echo ${{ vars.GCP_REGION }} - name: Setup | Repository uses: actions/checkout@v3 - name: Setup | Terraform uses: hashicorp/setup-terraform@v2 with: terraform_version: 1.6.1 - name: Setup | Secrets uses: oNaiPs/secrets-to-env-action@v1 with: secrets: ${{ toJSON(secrets) }} include: GOOGLE_CREDENTIALS, TF_VAR_INFRACOST_API_KEY, TF_VAR_GITHUB_OWNER, TF_VAR_GITHUB_RUNNER_TOKEN, TF_VAR_GITHUB_TOKEN, TF_VAR_GITHUB_FLUX_TOKEN - name: Setup | Helpers id: helpers run: | echo "DATE_NOW=$(date -u +'%T | %m.%d.%Y UTC')" >> "$GITHUB_OUTPUT" - name: Terraform | Initialize run: | cd environments/${{ matrix.path }} terraform init -input=false - name: Setup | Infracost uses: infracost/actions/setup@v2 with: api-key: ${{ secrets.INFRACOST_API_KEY }} # TODO: Add Infracost result and diff here. - name: Terraform | Plan id: plan continue-on-error: true run: | cd environments/${{ matrix.path }} terraform plan -input=false -no-color -out=tfplan \ && terraform show -no-color tfplan - name: Terraform | Check Plan Status if: steps.plan.outcome == 'failure' run: | if [ $? != 0 ]; then echo "❌ Terraform plan failed. ❌" fi - name: Terraform | Reformat Plan if: steps.plan.outcome == 'success' run: | echo '${{ steps.plan.outputs.stdout || steps.plan.outputs.stderr }}' \ | sed -E 's/^([[:space:]]+)([-+])/\2\1/g' > plan.txt - name: Terraform | Plan to ENV if: steps.plan.outcome == 'success' run: | PLAN=$(cat plan.txt) echo "PLAN<> $GITHUB_ENV echo "$PLAN" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Terraform | Comment Plan if: steps.plan.outcome == 'success' uses: mshick/add-pr-comment@v2 with: allow-repeats: true repo-token: ${{ secrets.GITHUB_TOKEN }} message-id: pr-comment-${{ matrix.path }} message: | ## Terraform Plan 📝: ${{ matrix.path }} Generated at: ${{ steps.helpers.outputs.DATE_NOW }} ```diff ${{ env.PLAN }} ```