Skip to content

Commit

Permalink
Adding plan only workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
Lena Sadowska authored and Lena Sadowska committed Oct 19, 2023
1 parent 5b77ac6 commit 8d2cce3
Showing 1 changed file with 289 additions and 0 deletions.
289 changes: 289 additions & 0 deletions .github/workflows/terraform-plan-aws.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
## Plan only workflow for testing purposes. Allows working directory
# distinction to test with different modules.

name: Terraform Validate and Plan
on:
workflow_call:
secrets:
infracost-api-key:
description: 'The API key for infracost'
required: false
inputs:
aws-account-id:
description: 'The AWS account ID to deploy to'
required: true
type: number
aws-role-name-readonly:
default: '${{ github.event.repository.name }}-ro'
description: 'The Read Only AWS role to assume for PR branch executions'
required: false
type: string
aws-role-name-readwrite:
default: '${{ github.event.repository.name }}-rw'
description: 'The Read/Write AWS role to assume for main branch executions'
required: false
type: string
aws-region:
default: 'eu-west-2'
description: 'The AWS region to deploy to'
required: false
type: string
enable-infracost:
default: false
description: 'Whether to run infracost on the Terraform Plan (secrets.infracost-api-key must be set if enabled)'
required: false
type: boolean
runs-on:
default: "ubuntu-latest"
description: 'Single label value for the GitHub runner to use (custom value only applies to Terraform Plan and Apply steps)'
required: false
type: string
terraform-log-level:
default: ''
description: 'The log level of terraform'
required: false
type: string
terraform-state-key:
default: '${{ github.event.repository.name }}.tfstate'
description: 'The key of the terraform state'
required: false
type: string
terraform-values-file:
default: 'values/production.tfvars'
description: 'The values file to use'
required: false
type: string
terraform-version:
default: '1.5.7'
description: 'The version of terraform to use'
required: false
type: string
working-directory:
default: '.'
description: 'Working directory'
required: false
type: string


env:
TF_LOG: ${{ inputs.terraform-log-level }}
AWS_WEB_IDENTITY_TOKEN_FILE: /tmp/web_identity_token_file

permissions:
id-token: write
contents: read
pull-requests: write

jobs:
terraform-format:
name: "Terraform Format"
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
outputs:
result: ${{ steps.format.outcome }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ inputs.terraform-version }}
- name: Terraform Format
id: format
uses: dflook/terraform-fmt-check@v1
terraform-lint:
name: "Terraform Lint"
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
outputs:
result: ${{ steps.lint.outcome }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Setup Linter
uses: terraform-linters/setup-tflint@v3
- name: Linter Initialize
run: tflint --init
- name: Linting Code
id: lint
run: tflint -f compact
terraform-plan:
name: "Terraform Plan"
if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
runs-on: ${{ inputs.runs-on }}
defaults:
run:
working-directory: ${{ inputs.working-directory }}
outputs:
result-auth: ${{ steps.auth.outcome }}
result-init: ${{ steps.init.outcome }}
result-validate: ${{ steps.validate.outcome }}
result-s3-backend-check: ${{ steps.s3-backend-check.outcome }}
result-plan: ${{ steps.plan.outcome }}
plan-stdout: ${{ steps.plan.outputs.stdout }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ inputs.terraform-version }}
- name: Retrieve Web Identity Token for AWS Authentication
run: |
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq -r '.value' > $AWS_WEB_IDENTITY_TOKEN_FILE
- name: Determine AWS Role
id: role
run: |
if [[ "${GITHUB_REF##*/}" == "main" ]]; then
echo "name=${{ inputs.aws-role-name-readwrite }}" >> $GITHUB_OUTPUT
else
echo "name=${{ inputs.aws-role-name-readonly }}" >> $GITHUB_OUTPUT
fi
- name: Authenticate with AWS
id: auth
uses: aws-actions/configure-aws-credentials@v2
with:
aws-region: ${{ inputs.aws-region }}
role-session-name: ${{ github.event.repository.name }}
role-to-assume: arn:aws:iam::${{ inputs.aws-account-id }}:role/${{ steps.role.outputs.name }}
mask-aws-account-id: 'no'
- name: Terraform Init
id: init
run: terraform init -backend-config="bucket=${{ inputs.aws-account-id }}-${{ inputs.aws-region }}-tfstate" -backend-config="key=${{ inputs.terraform-state-key }}" -backend-config="encrypt=true" -backend-config="dynamodb_table=${{ inputs.aws-account-id }}-${{ inputs.aws-region }}-tflock" -backend-config="region=${{ inputs.aws-region }}"
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform S3 Backend Check
id: s3-backend-check
run: |
if grep -E '^[^#]*backend\s+"s3"' terraform.tf; then
echo "Terraform configuration references an S3 backend."
else
echo "Terraform configuration does not reference an S3 backend."
exit 1
fi
- name: Terraform Plan
id: plan
run: |
terraform plan -var-file=${{ inputs.terraform-values-file }} -no-color -input=false -out=tfplan
- name: Terraform Plan JSON Output
run: |
terraform show -json tfplan > tfplan.json
- name: Upload tfplan
uses: actions/upload-artifact@v3
with:
name: tfplan
path: "tfplan*"
retention-days: 1
get-cost-estimate:
name: "Get Cost Estimate"
if: github.event_name == 'pull_request' && inputs.enable-infracost
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
needs:
- terraform-plan
steps:
- name: Setup Infracost
uses: infracost/actions/setup@v2
with:
api-key: ${{ secrets.infracost-api-key }}
currency: GBP
- name: Download tfplan
uses: actions/download-artifact@v3
with:
name: tfplan
- name: Generate Infracost Cost Estimate
run: |
infracost breakdown --path=tfplan.json \
--format=json \
--out-file=/tmp/infracost.json
- name: Post Infracost comment
run: |
infracost comment github --path=/tmp/infracost.json \
--repo=$GITHUB_REPOSITORY \
--github-token=${{github.token}} \
--pull-request=${{github.event.pull_request.number}} \
--behavior=update
update-pr:
name: "Update PR"
if: github.event_name == 'pull_request' && (success() || failure())
runs-on: ubuntu-latest
needs:
- terraform-format
- terraform-lint
- terraform-plan
steps:
- name: Add PR Comment
uses: actions/github-script@v6
env:
PLAN: "${{ needs.terraform-plan.outputs.plan-stdout }}"
with:
script: |
// 1. Retrieve existing bot comments for the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
})
const botComment = comments.find(comment => {
return comment.user.type === 'Bot' && comment.body.includes('Pull Request Review Status')
})
// 2. Check output length
const PLAN = process.env.PLAN || '';
const excludedStrings = ["Refreshing state...", "Reading...", "Read complete after"];
const filteredLines = PLAN.split('\n').filter(line =>
!excludedStrings.some(excludedStr => line.includes(excludedStr))
);
var planOutput = filteredLines.join('\n').trim();
if (planOutput.length < 1 || planOutput.length > 65000) {
planOutput = "Terraform Plan output is too large, please view the workflow run logs directly."
}
// 3. Prepare format of the comment
const output = `### Pull Request Review Status
* πŸ–Œ <b>Terraform Format and Style:</b> \`${{ needs.terraform-format.outputs.result }}\`
* πŸ” <b>Terraform Linting:</b> \`${{ needs.terraform-lint.outputs.result }}\`
* πŸ”‘ <b>AWS Authentication:</b> \`${{ needs.terraform-plan.outputs.result-auth }}\`
* πŸ”§ <b>Terraform Initialisation:</b> \`${{ needs.terraform-plan.outputs.result-init }}\`
* πŸ€– <b>Terraform Validation:</b> \`${{ needs.terraform-plan.outputs.result-validate }}\`
* πŸ“ <b>Terraform S3 Backend:</b> \`${{ needs.terraform-plan.outputs.result-s3-backend-check }}\`
* πŸ“– <b>Terraform Plan:</b> \`${{ needs.terraform-plan.outputs.result-plan }}\`
<details><summary><b>Output: πŸ“– Terraform Plan</b></summary>
\`\`\`
${planOutput}
\`\`\`
</details>
*<b>Pusher:</b> @${{ github.actor }}, <b>Action:</b> \`${{ github.event_name }}\`*
*<b>Workflow Run Link:</b> ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}*`;
// 4. If we have a comment, update it, otherwise create a new one
if (botComment) {
github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: output
})
} else {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
}

0 comments on commit 8d2cce3

Please sign in to comment.