name: 'Terraform' env: AWS_ACCOUNT_NUMBER: '' AWS_PLAN_ROLE_NAME: github_actions_plan AWS_APPLY_ROLE_NAME: github_actions_admin # Always required. Used for authenticating to AWS, but can also act as your # default region if you don't want to specify in the provider configuration. AWS_REGION: us-east-1 # You must change these to fit your project. TF_VAR_project: change-me TF_VAR_label: change-me TF_VAR_owner: Your Name Here # If storing Terraform in a subdirectory, specify it here. TERRAFORM_DIRECTORY: . # Pinned versions of tools to use. # Check for new releases: # - https://github.com/hashicorp/terraform/releases # - https://github.com/fugue/regula/releases # - https://github.com/terraform-linters/tflint/releases TERRAFORM_VERSION: 1.2.6 REGULA_VERSION: 2.9.0 TFLINT_VERSION: 0.39.1 # Terraform configuration options TERRAFORM_PARALLELISM: 10 # These variables are passed to Terraform based on GitHub information. TF_VAR_repo: ${{ github.repository }} # This workflow is triggered in the following ways. on: # Any push or merge to these branches. push: branches: - dev - prod # Any pull request targeting these branches (plan only). pull_request: branches: - dev - prod # Any manual trigger on these branches. workflow_dispatch: branches: - dev - prod # ------------------------------------------------------------------- # The rest of this workflow can operate without adjustments. Edit the # below content at your own risk! # ------------------------------------------------------------------- # Used to connect to AWS IAM permissions: id-token: write contents: read pull-requests: write # Only run one workflow at a time for each Terraform state. This prevents # lockfile conflicts, especially during PR vs push. concurrency: terraform-${{ github.base_ref || github.ref }} jobs: terraform: name: 'Terraform' # Change this if you need to run your deployment on-prem. runs-on: ubuntu-latest steps: # Downloads the current repo code to the runner. - name: Checkout Repo Code uses: actions/checkout@v2 # Install Nix - name: Install Nix uses: cachix/install-nix-action@v17 # Build the image - name: Build Image run: nix build .#aws # Login to AWS - name: AWS Assume Role uses: aws-actions/configure-aws-credentials@v1.6.1 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} # Exports all GitHub Secrets as environment variables prefixed by # "TF_VAR_", which exposes them to Terraform. The name of each GitHub # Secret must match its Terraform variable name exactly. - name: Export Secrets to Terraform Variables env: ALL_SECRETS: ${{ toJson(secrets) }} run: | echo "$ALL_SECRETS" \ | jq "to_entries | .[] | \"TF_VAR_\" + ( .key | ascii_downcase ) + \"=\" + .value" \ | tr -d \" >> $GITHUB_ENV # Installs the Terraform binary and some other accessory functions. - name: Setup Terraform uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ env.TERRAFORM_VERSION }} # Checks whether Terraform is formatted properly. If this fails, you # should install the pre-commit hook. - name: Check Formatting run: | terraform fmt -no-color -check -diff -recursive # Downloads a Terraform code lint test. - uses: terraform-linters/setup-tflint@v1 name: Setup TFLint with: tflint_version: v${{ env.TFLINT_VERSION }} # Sets up linting with this codebase. - name: Init TFLint working-directory: ${{ env.TERRAFORM_DIRECTORY }} run: tflint --init # Lints the current code. - name: Run TFLint working-directory: ${{ env.TERRAFORM_DIRECTORY }} run: | tflint -f compact find ./modules/* -type d -maxdepth 0 | xargs -I __ tflint -f compact --disable-rule=terraform_required_providers --disable-rule=terraform_required_version __ # Connects to remote state backend and download providers. - name: Terraform Init working-directory: ${{ env.TERRAFORM_DIRECTORY }} run: | terraform init \ -backend-config="role_arn=${{ env.AWS_STATE_ROLE_ARN }}" \ -backend-config="region=us-east-1" \ -backend-config="workspace_key_prefix=accounts/${{ env.AWS_ACCOUNT_NUMBER }}/${{ github.repository }}" \ -backend-config="key=state.tfstate" \ -backend-config="dynamodb_table=global-tf-state-lock" # Set the Terraform Workspace to the current branch name. - name: Set Terraform Workspace working-directory: ${{ env.TERRAFORM_DIRECTORY }} shell: bash run: | export WORKSPACE=${{ github.base_ref || github.ref_name }} terraform workspace select ${WORKSPACE} || terraform workspace new $WORKSPACE echo "TF_WORKSPACE=$(echo ${WORKSPACE} | sed 's/\//_/g')" >> $GITHUB_ENV # Checks differences between current code and infrastructure state. - name: Terraform Plan id: plan working-directory: ${{ env.TERRAFORM_DIRECTORY }} run: | terraform plan \ -input=false \ -no-color \ -out=tfplan \ -parallelism=${TERRAFORM_PARALLELISM} \ -var-file=variables-${TF_WORKSPACE}.tfvars # Gets the results of the plan for pull requests. - name: Terraform Show Plan id: show working-directory: ${{ env.TERRAFORM_DIRECTORY }} run: terraform show -no-color tfplan # Adds the results of the plan to the pull request. - name: Comment Plan uses: actions/github-script@v6 if: github.event_name == 'pull_request' env: STDOUT: "```terraform\n${{ steps.show.outputs.stdout }}```" with: github-token: ${{ secrets.GITHUB_TOKEN }} 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('Terraform Format and Style') }) // 2. Prepare format of the comment const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
Validation Output \`\`\`\n ${{ steps.validate.outputs.stdout }} \`\`\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
Show Plan \`\`\`\n ${process.env.PLAN} \`\`\`
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`; // 3. 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 }) } # Downloads Regula and checks whether the plan meets compliance requirements. - name: Regula Compliance Check shell: bash working-directory: ${{ env.TERRAFORM_DIRECTORY }} run: | REGULA_URL="https://github.com/fugue/regula/releases/download/v${REGULA_VERSION}/regula_${REGULA_VERSION}_Linux_x86_64.tar.gz" curl -sL "$REGULA_URL" -o regula.tar.gz tar xzf regula.tar.gz terraform show -json tfplan | ./regula run # Deploys infrastructure or changes to infrastructure. - name: Terraform Apply if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' working-directory: ${{ env.TERRAFORM_DIRECTORY }} run: | terraform apply \ -auto-approve \ -input=false \ -parallelism=${TERRAFORM_PARALLELISM} \ tfplan