name: 'Terraform Configuration Drift Detection' on: workflow_dispatch: schedule: - cron: '41 3 * * *' # runs nightly at 3:41 am #Special permissions required for OIDC authentication permissions: id-token: write contents: read issues: write #These environment variables are used by the terraform azure provider to setup OIDD authenticate. env: ARM_CLIENT_ID: "${{ secrets.AZURE_CLIENT_ID }}" ARM_SUBSCRIPTION_ID: "${{ secrets.AZURE_SUBSCRIPTION_ID }}" ARM_TENANT_ID: "${{ secrets.AZURE_TENANT_ID }}" jobs: terraform-plan: name: 'Terraform Plan' runs-on: ubuntu-latest env: #this is needed since we are running terraform with read-only permissions ARM_SKIP_PROVIDER_REGISTRATION: true outputs: tfplanExitCode: ${{ steps.tf-plan.outputs.exitcode }} steps: # Checkout the repository to the GitHub Actions runner - name: Checkout uses: actions/checkout@v3 # Install the latest version of the Terraform CLI - name: Setup Terraform uses: hashicorp/setup-terraform@v2 with: terraform_wrapper: false # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. - name: Terraform Init run: terraform init # Generates an execution plan for Terraform # An exit code of 0 indicated no changes, 1 a terraform failure, 2 there are pending changes. - name: Terraform Plan id: tf-plan run: | export exitcode=0 terraform plan -detailed-exitcode -no-color -out tfplan || export exitcode=$? echo "exitcode=$exitcode" >> $GITHUB_OUTPUT if [ $exitcode -eq 1 ]; then echo Terraform Plan Failed! exit 1 else exit 0 fi # Save plan to artifacts - name: Publish Terraform Plan uses: actions/upload-artifact@v3 with: name: tfplan path: tfplan # Create string output of Terraform Plan - name: Create String Output id: tf-plan-string run: | TERRAFORM_PLAN=$(terraform show -no-color tfplan) delimiter="$(openssl rand -hex 8)" echo "summary<<${delimiter}" >> $GITHUB_OUTPUT echo "## Terraform Plan Output" >> $GITHUB_OUTPUT echo "
Click to expand" >> $GITHUB_OUTPUT echo "" >> $GITHUB_OUTPUT echo '```terraform' >> $GITHUB_OUTPUT echo "$TERRAFORM_PLAN" >> $GITHUB_OUTPUT echo '```' >> $GITHUB_OUTPUT echo "
" >> $GITHUB_OUTPUT echo "${delimiter}" >> $GITHUB_OUTPUT # Publish Terraform Plan as task summary - name: Publish Terraform Plan to Task Summary env: SUMMARY: ${{ steps.tf-plan-string.outputs.summary }} run: | echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY # If changes are detected, create a new issue - name: Publish Drift Report if: steps.tf-plan.outputs.exitcode == 2 uses: actions/github-script@v6 env: SUMMARY: "${{ steps.tf-plan-string.outputs.summary }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const body = `${process.env.SUMMARY}`; const title = 'Terraform Configuration Drift Detected'; const creator = 'github-actions[bot]' // Look to see if there is an existing drift issue const issues = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', creator: creator, title: title }) if( issues.data.length > 0 ) { // We assume there shouldn't be more than 1 open issue, since we update any issue we find const issue = issues.data[0] if ( issue.body == body ) { console.log('Drift Detected: Found matching issue with duplicate content') } else { console.log('Drift Detected: Found matching issue, updating body') github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, body: body }) } } else { console.log('Drift Detected: Creating new issue') github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: title, body: body }) } # If changes aren't detected, close any open drift issues - name: Publish Drift Report if: steps.tf-plan.outputs.exitcode == 0 uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const title = 'Terraform Configuration Drift Detected'; const creator = 'github-actions[bot]' // Look to see if there is an existing drift issue const issues = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', creator: creator, title: title }) if( issues.data.length > 0 ) { const issue = issues.data[0] github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, state: 'closed' }) } # Mark the workflow as failed if drift detected - name: Error on Failure if: steps.tf-plan.outputs.exitcode == 2 run: exit 1