feat: Added slack notif support for both apply and destroy #7
This file contains 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
--- | ||
run-name: 'Terraform workflow' | ||
on: | ||
workflow_call: | ||
inputs: | ||
working_directory: | ||
required: true | ||
type: string | ||
description: 'Root directory of the terraform where all resources exist.' | ||
provider: | ||
required: true | ||
type: string | ||
description: 'Cloud provider to run the workflow. e.g. azurerm, aws, gcp or digitalocean' | ||
aws_region: | ||
required: false | ||
type: string | ||
default: us-east-2 | ||
description: 'AWS region of terraform deployment.' | ||
gcp_region: | ||
required: false | ||
type: string | ||
description: 'GCP region of terraform deployment.' | ||
var_file: | ||
required: false | ||
type: string | ||
description: 'Terraform var file directory. e.g. vars/dev.tfvars' | ||
destroy: | ||
required: false | ||
type: boolean | ||
default: false | ||
description: 'Set true to to destroy terraform infrastructure.' | ||
approvers: | ||
required: false | ||
type: string | ||
description: 'Approvals list to approve apply or destroy' | ||
terraform_version: | ||
type: string | ||
default: 1.3.6 | ||
description: 'Required erraform version ' | ||
timeout: | ||
required: false | ||
type: number | ||
default: 10 | ||
description: 'Timeout for approval step' | ||
minimum-approvals: | ||
required: false | ||
type: number | ||
default: 1 | ||
description: 'Minimum approvals required to accept the plan' | ||
skip_approval: | ||
required: false | ||
type: boolean | ||
default: false | ||
description: 'Set true to skip approval step' | ||
token_format: | ||
required: false | ||
type: string | ||
default: access_token | ||
description: 'Output format for the generated authentication token. For OAuth 2.0 access tokens, specify "access_token". For OIDC tokens, specify "id_token". To skip token generation, leave this value empty' | ||
access_token_lifetime: | ||
required: false | ||
type: string | ||
default: 300s | ||
description: 'Desired lifetime duration of the access token, in seconds' | ||
project_id: | ||
required: false | ||
type: string | ||
description: 'ID of the default project to use for future API calls and invocations.' | ||
create_credentials_file: | ||
required: false | ||
type: string | ||
default: true | ||
description: 'If true, the action will securely generate a credentials file which can be used for authentication via gcloud and Google Cloud SDKs.' | ||
secrets: | ||
AZURE_CREDENTIALS: | ||
required: false | ||
description: 'Azure Credentials to install Azure in github runner.' | ||
AZURE_CLIENT_ID: | ||
required: false | ||
description: 'Client ID for service principal in Azure.' | ||
AZURE_SUBSCRIPTION_ID: | ||
required: false | ||
description: 'Subscription ID in Azure.' | ||
AZURE_TENANT_ID: | ||
required: false | ||
description: 'Tenant ID in Azure.' | ||
AZURE_CLIENT_SECRET: | ||
required: false | ||
description: 'Client Secret for the Azure app registration.' | ||
AWS_ACCESS_KEY_ID: | ||
required: false | ||
description: 'AWS Access Key ID to install AWS CLI.' | ||
BUILD_ROLE: | ||
required: false | ||
description: 'AWS OIDC role for aws authentication.' | ||
AWS_SECRET_ACCESS_KEY: | ||
required: false | ||
description: 'AWS Secret access key to install AWS CLI' | ||
AWS_SESSION_TOKEN: | ||
required: false | ||
description: 'AWS Session Token to install AWS CLI' | ||
GCP_CREDENTIALS: | ||
required: false | ||
description: 'The Google Cloud JSON service account key to use for authentication' | ||
DIGITALOCEAN_ACCESS_TOKEN: | ||
required: false | ||
description: 'The DigitalOcean Personal Access Token for Application & API' | ||
env-vars: | ||
required: false | ||
description: 'Pass required environment variables' | ||
WORKLOAD_IDENTITY_PROVIDER: | ||
required: false | ||
description: 'The full identifier of the Workload Identity Provider' | ||
SERVICE_ACCOUNT: | ||
required: false | ||
description: 'The service account to be used' | ||
SLACK_WEBHOOK: | ||
description: "Slack webhook URL" | ||
required: true | ||
jobs: | ||
terraform-workflow: | ||
runs-on: ubuntu-latest | ||
outputs: | ||
tfplanExitCode: ${{ steps.tf-plan.outputs.exitcode }} | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
- name: Set environment variables | ||
run: | | ||
( | ||
cat <<'_EOT' | ||
${{ secrets.env-vars }} | ||
_EOT | ||
) >> "$GITHUB_ENV" | ||
- name: Install AWS CLI | ||
if: ${{ inputs.provider == 'aws' }} | ||
uses: aws-actions/configure-aws-credentials@v4 | ||
with: | ||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }} | ||
role-to-assume: ${{ secrets.BUILD_ROLE }} | ||
aws-region: ${{ inputs.aws_region }} | ||
role-duration-seconds: 900 | ||
role-skip-session-tagging: true | ||
- name: Install Azure CLI | ||
if: ${{ inputs.provider == 'azurerm' }} | ||
uses: azure/login@v2 | ||
with: | ||
creds: ${{ secrets.AZURE_CREDENTIALS }} | ||
- name: 'Authenticate to Google Cloud' | ||
if: ${{ inputs.provider == 'gcp' }} | ||
uses: 'google-github-actions/auth@v2' | ||
with: | ||
credentials_json: '${{ secrets.GCP_CREDENTIALS }}' | ||
create_credentials_file: ${{ inputs.create_credentials_file }} | ||
token_format: ${{ inputs.token_format }} | ||
workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }} | ||
service_account: ${{ secrets.SERVICE_ACCOUNT }} | ||
access_token_lifetime: ${{ inputs.access_token_lifetime }} | ||
project_id: ${{ inputs.project_id }} | ||
- name: Install doctl | ||
if: ${{ inputs.provider == 'digitalocean' }} | ||
uses: digitalocean/action-doctl@v2 | ||
with: | ||
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} | ||
- name: Set up Terraform | ||
uses: hashicorp/setup-terraform@v3 | ||
with: | ||
terraform_version: ${{ inputs.terraform_version }} | ||
- name: 'Terraform Format' | ||
if: ${{ inputs.destroy != true }} | ||
id: fmt | ||
uses: 'dflook/terraform-fmt-check@v1' | ||
with: | ||
actions_subcommand: 'fmt' | ||
path: ${{ inputs.working_directory }} | ||
- name: terraform init | ||
run: | | ||
export ARM_CLIENT_ID="${{ secrets.AZURE_CLIENT_ID }}" | ||
export ARM_CLIENT_SECRET="${{ secrets.AZURE_CLIENT_SECRET }}" | ||
export ARM_TENANT_ID="${{ secrets.AZURE_TENANT_ID }}" | ||
export ARM_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" | ||
cd ${{ inputs.working_directory }} | ||
terraform init | ||
- name: 'Terraform validate' | ||
if: ${{ inputs.destroy != true }} | ||
id: validate | ||
uses: dflook/terraform-validate@v1 | ||
with: | ||
path: ${{ inputs.working_directory }} | ||
- name: Terraform Plan | ||
id: tf-plan | ||
run: | | ||
export exitcode=0 | ||
cd ${{ inputs.working_directory }} | ||
export ARM_CLIENT_ID="${{ secrets.AZURE_CLIENT_ID }}" | ||
export ARM_CLIENT_SECRET="${{ secrets.AZURE_CLIENT_SECRET }}" | ||
export ARM_TENANT_ID="${{ secrets.AZURE_TENANT_ID }}" | ||
export ARM_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" | ||
if [ "${{ inputs.destroy }}" = true ]; then | ||
if [ -n "${{ inputs.var_file }}" ]; then | ||
terraform plan -destroy -out tfplan --var-file=${{ inputs.var_file }} | ||
else | ||
terraform plan -destroy -out tfplan | ||
fi | ||
else | ||
if [ -n "${{ inputs.var_file }}" ]; then | ||
terraform plan -out tfplan --var-file=${{ inputs.var_file }} | ||
else | ||
terraform plan -out tfplan | ||
fi | ||
fi | ||
- name: Create String Output | ||
id: tf-plan-string | ||
run: | | ||
cd ${{ inputs.working_directory }} | ||
TERRAFORM_PLAN=$(terraform show -no-color tfplan) | ||
delimiter="$(openssl rand -hex 8)" | ||
echo "summary<<${delimiter}" >> $GITHUB_OUTPUT | ||
echo "## Terraform Plan Output" >> $GITHUB_OUTPUT | ||
echo "<details><summary>Click to expand</summary>" >> $GITHUB_OUTPUT | ||
echo "" >> $GITHUB_OUTPUT | ||
echo '```terraform' >> $GITHUB_OUTPUT | ||
echo "$TERRAFORM_PLAN" >> $GITHUB_OUTPUT | ||
echo '```' >> $GITHUB_OUTPUT | ||
echo "</details>" >> $GITHUB_OUTPUT | ||
echo "${delimiter}" >> $GITHUB_OUTPUT | ||
- name: "Accept plan or deny" | ||
if: ${{ inputs.skip_approval == false }} | ||
uses: trstringer/manual-approval@v1 | ||
timeout-minutes: ${{ inputs.timeout }} | ||
with: | ||
secret: ${{ github.TOKEN }} | ||
approvers: ${{ inputs.approvers }} | ||
minimum-approvals: ${{ inputs.minimum-approvals }} | ||
issue-title: "Terraform Plan for Infrastructure Update" | ||
- name: terraform apply | ||
if: ${{ inputs.destroy != true }} | ||
run: | | ||
export ARM_CLIENT_ID="${{ secrets.AZURE_CLIENT_ID }}" | ||
export ARM_CLIENT_SECRET="${{ secrets.AZURE_CLIENT_SECRET }}" | ||
export ARM_TENANT_ID="${{ secrets.AZURE_TENANT_ID }}" | ||
export ARM_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" | ||
if [ -n "${{ inputs.var_file }}" ]; then | ||
cd ${{ inputs.working_directory }} | ||
terraform apply -var-file="${{ inputs.var_file }}" -auto-approve | ||
else | ||
cd ${{ inputs.working_directory }} | ||
terraform apply -auto-approve | ||
fi | ||
- name: Find Errored Terraform State | ||
if: ${{ always() }} | ||
run: | | ||
cd ${{ inputs.working_directory }} | ||
if [ -f "errored.tfstate" ]; then | ||
ls -la errored.tfstate | ||
echo "Uploading errored.tfstate as artifact..." | ||
else | ||
echo "Errored Terraform state file not found." | ||
fi | ||
- name: Upload Errored Terraform State Artifact | ||
if: ${{ always() }} && success() && steps.find_errored_tfstate.outputs['errored_found'] == 'true' | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: errored_tfstate | ||
path: ${{ inputs.working_directory }}/errored.tfstate | ||
- name: Terraform destroy | ||
if: ${{ inputs.destroy == true }} | ||
id: destroy | ||
run: | | ||
export ARM_CLIENT_ID="${{ secrets.AZURE_CLIENT_ID }}" | ||
export ARM_CLIENT_SECRET="${{ secrets.AZURE_CLIENT_SECRET }}" | ||
export ARM_TENANT_ID="${{ secrets.AZURE_TENANT_ID }}" | ||
export ARM_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}" | ||
if [ -n "${{ inputs.var_file }}" ]; then | ||
cd ${{ inputs.working_directory }} | ||
terraform destroy -var-file="${{ inputs.var_file }}" -auto-approve | ||
else | ||
cd ${{ inputs.working_directory }} | ||
terraform destroy -auto-approve | ||
fi | ||
# - name: Notify Slack | ||
# if: ${{ always() }} | ||
# env: | ||
# SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | ||
# run: | | ||
# if [ "${{ job.status }}" == "success" ]; then | ||
# STATUS="Success ✅" | ||
# COLOR="good" | ||
# MESSAGE="Terraform deployment completed successfully." | ||
# else | ||
# STATUS="Failed ❌" | ||
# COLOR="danger" | ||
# MESSAGE="Terraform deployment failed. Check logs for details." | ||
# fi | ||
# RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | ||
# curl -X POST -H 'Content-type: application/json' \ | ||
# --data "$(jq -n --arg color "$COLOR" --arg status "$STATUS" --arg message "$MESSAGE" \ | ||
# --arg repo "$GITHUB_REPOSITORY" --arg branch "$GITHUB_REF_NAME" --arg sha "$GITHUB_SHA" \ | ||
# --arg run_url "$RUN_URL" \ | ||
# '{attachments: [{color: $color, title: ("Terraform Deployment: " + $status), text: $message, fields: [{title: "Repository", value: $repo, short: true}, {title: "Branch", value: $branch, short: true}, {title: "Commit", value: $sha, short: true}, {title: "Run URL", value: $run_url, short: false}]}]}')" \ | ||
# $SLACK_WEBHOOK_URL | ||
- name: Notify Slack | ||
if: ${{ always() }} | ||
env: | ||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | ||
run: | | ||
# Determine the operation type (apply or destroy) using a custom environment variable or flag | ||
if [[ "${{ inputs.destroy }}" == "true" ]]; then | ||
OPERATION="Destroy" | ||
else | ||
OPERATION="Apply" | ||
fi | ||
# Check the job status | ||
if [ "${{ job.status }}" == "success" ]; then | ||
STATUS="Success ✅" | ||
COLOR="good" | ||
MESSAGE="Terraform $OPERATION completed successfully." | ||
else | ||
STATUS="Failed ❌" | ||
COLOR="danger" | ||
MESSAGE="Terraform $OPERATION failed. Check logs for details." | ||
fi | ||
# Create the Run URL | ||
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | ||
# Send the notification to Slack | ||
curl -X POST -H 'Content-type: application/json' \ | ||
--data "$(jq -n --arg color "$COLOR" --arg status "$STATUS" --arg message "$MESSAGE" \ | ||
--arg operation "$OPERATION" --arg repo "$GITHUB_REPOSITORY" --arg branch "$GITHUB_REF_NAME" --arg sha "$GITHUB_SHA" \ | ||
--arg run_url "$RUN_URL" \ | ||
'{attachments: [{color: $color, title: ("Terraform " + $operation + ": " + $status), text: $message, fields: [{title: "Operation", value: $operation, short: true}, {title: "Repository", value: $repo, short: true}, {title: "Branch", value: $branch, short: true}, {title: "Commit", value: $sha, short: true}, {title: "Run URL", value: $run_url, short: false}]}]}')" \ | ||
$SLACK_WEBHOOK_URL | ||
... |