249 lines
7.9 KiB
YAML

name: Terraform CI/CD Pipeline
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: true
type: choice
options:
- plan
- apply
- destroy
env:
TF_VERSION: "1.9.0"
WORKING_DIR: "./terraform" # Adjust to your terraform directory
jobs:
# Stage 1: Linting and Syntax Checks
lint-and-validate:
name: Lint and Validate
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: https://github.com/hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Terraform Format Check
id: fmt
run: terraform fmt -check -recursive
working-directory: ${{ env.WORKING_DIR }}
continue-on-error: true
- name: Terraform Init (for validation)
run: terraform init -backend=false
working-directory: ${{ env.WORKING_DIR }}
- name: Terraform Validate
run: terraform validate
working-directory: ${{ env.WORKING_DIR }}
- name: Check Format Result
if: steps.fmt.outcome == 'failure'
run: |
echo "❌ Terraform formatting check failed. Run 'terraform fmt -recursive' to fix."
exit 1
# Stage 2: Security Scanning
security-scan:
name: Security Scan
runs-on: ubuntu-latest
needs: lint-and-validate
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Checkov (Open Source Security Scanner)
uses: https://github.com/bridgecrewio/checkov-action@v12
with:
directory: ${{ env.WORKING_DIR }}
framework: terraform
soft_fail: false # Set to true to not fail the pipeline on security issues
output_format: cli
- name: Run tfsec (Terraform Security Scanner)
uses: https://github.com/aquasecurity/tfsec-action@v1.0.3
with:
working_directory: ${{ env.WORKING_DIR }}
soft_fail: false
# Stage 3: Terraform Init and Plan
plan:
name: Terraform Plan
runs-on: ubuntu-latest
needs: security-scan
if: github.event_name == 'push' || github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && (github.event.inputs.action == 'plan' || github.event.inputs.action == 'apply'))
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: https://github.com/hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
terraform_wrapper: false
- name: Configure Terraform Credentials
run: |
# Add your cloud provider credentials here
# Example for AWS:
# echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" >> $GITHUB_ENV
# echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" >> $GITHUB_ENV
# Example for Azure:
# echo "ARM_CLIENT_ID=${{ secrets.ARM_CLIENT_ID }}" >> $GITHUB_ENV
# echo "ARM_CLIENT_SECRET=${{ secrets.ARM_CLIENT_SECRET }}" >> $GITHUB_ENV
# echo "ARM_SUBSCRIPTION_ID=${{ secrets.ARM_SUBSCRIPTION_ID }}" >> $GITHUB_ENV
# echo "ARM_TENANT_ID=${{ secrets.ARM_TENANT_ID }}" >> $GITHUB_ENV
# For GCP, you might need to create a credentials file
echo "Configure your provider credentials here"
- name: Terraform Init
run: terraform init
working-directory: ${{ env.WORKING_DIR }}
env:
# Add backend configuration secrets if needed
TF_CLI_ARGS_init: "-backend-config=access_key=${{ secrets.BACKEND_ACCESS_KEY }}"
- name: Terraform Plan
run: terraform plan -out=tfplan.binary
working-directory: ${{ env.WORKING_DIR }}
- name: Convert Plan to JSON
run: terraform show -json tfplan.binary > tfplan.json
working-directory: ${{ env.WORKING_DIR }}
- name: Upload Terraform Plan
uses: actions/upload-artifact@v4
with:
name: terraform-plan
path: |
${{ env.WORKING_DIR }}/tfplan.binary
${{ env.WORKING_DIR }}/tfplan.json
${{ env.WORKING_DIR }}/.terraform/
${{ env.WORKING_DIR }}/.terraform.lock.hcl
retention-days: 30
- name: Comment Plan on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('${{ env.WORKING_DIR }}/tfplan.json', 'utf8');
const output = `#### Terraform Plan 📖
<details><summary>Show Plan</summary>
\`\`\`json
${plan}
\`\`\`
</details>`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
# Stage 4: Terraform Apply
apply:
name: Terraform Apply
runs-on: ubuntu-latest
needs: plan
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'apply')
environment:
name: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: https://github.com/hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
terraform_wrapper: false
- name: Download Terraform Plan
uses: actions/download-artifact@v4
with:
name: terraform-plan
path: ${{ env.WORKING_DIR }}
- name: Configure Terraform Credentials
run: |
# Same credentials configuration as in plan stage
echo "Configure your provider credentials here"
- name: Restore Terraform Init Files
run: |
# The .terraform directory is already restored from artifacts
echo "Terraform initialization files restored"
- name: Terraform Apply
run: terraform apply -auto-approve tfplan.binary
working-directory: ${{ env.WORKING_DIR }}
- name: Output Terraform Outputs
run: terraform output -json > terraform-outputs.json
working-directory: ${{ env.WORKING_DIR }}
- name: Upload Terraform Outputs
uses: actions/upload-artifact@v4
with:
name: terraform-outputs
path: ${{ env.WORKING_DIR }}/terraform-outputs.json
retention-days: 90
# Stage 5: Terraform Destroy (Manual/Authorized Only)
destroy:
name: Terraform Destroy
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'destroy'
environment:
name: production-destroy # Requires manual approval in repository settings
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: https://github.com/hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
terraform_wrapper: false
- name: Configure Terraform Credentials
run: |
# Same credentials configuration as previous stages
echo "Configure your provider credentials here"
- name: Terraform Init
run: terraform init
working-directory: ${{ env.WORKING_DIR }}
- name: Terraform Destroy
run: terraform destroy -auto-approve
working-directory: ${{ env.WORKING_DIR }}
- name: Notify Destroy Completion
run: |
echo "🔥 Terraform infrastructure has been destroyed!"
echo "Destroyed by: ${{ github.actor }}"
echo "Timestamp: $(date -u)"