CI/CD Integration
Integrate tf2report into your CI/CD pipelines for automated Terraform plan analysis.
GitHub Actions
Basic Integration
.github/workflows/terraform.yml
name: Terraform Plan
on:
pull_request:
branches: [main]
jobs:
plan:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Install tf2report
run: go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: |
terraform plan -out=tfplan
terraform show -json tfplan > terraform.tfplan.json
- name: Generate Report
run: tf2report --plan terraform.tfplan.json --format markdown > report.md
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('report.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: report
});
Advanced GitHub Actions with Checks
.github/workflows/terraform-advanced.yml
name: Terraform Plan Analysis
on:
pull_request:
branches: [main]
jobs:
plan-and-analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Install tf2report
run: go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: |
terraform plan -out=tfplan
terraform show -json tfplan > terraform.tfplan.json
- name: Generate JSON Report
run: tf2report --plan terraform.tfplan.json --format json > report.json
- name: Check for Destructive Changes
run: |
DESTRUCTIVE=$(jq '.summary.to_destroy + .summary.to_replace' report.json)
if [ "$DESTRUCTIVE" -gt 0 ]; then
echo "::warning::Destructive changes detected: $DESTRUCTIVE resources"
fi
- name: Generate Markdown Report
run: tf2report --plan terraform.tfplan.json > report.md
- name: Generate Security Review
run: |
tf2report --plan terraform.tfplan.json \
--type aws_iam_role \
--type aws_iam_policy \
--type aws_security_group \
--type aws_kms_key \
> security-review.md
- name: Comment PR with Reports
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('report.md', 'utf8');
const security = fs.readFileSync('security-review.md', 'utf8');
const comment = `## Terraform Plan Report\n\n${report}\n\n## Security Review\n\n${security}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
- name: Upload Reports as Artifacts
uses: actions/upload-artifact@v4
with:
name: terraform-reports
path: |
report.md
report.json
security-review.md
GitLab CI/CD
Basic Integration
.gitlab-ci.yml
stages:
- plan
- report
terraform:plan:
stage: plan
image: hashicorp/terraform:latest
script:
- terraform init
- terraform plan -out=tfplan
- terraform show -json tfplan > terraform.tfplan.json
artifacts:
paths:
- terraform.tfplan.json
expire_in: 1 week
terraform:report:
stage: report
image: golang:1.25
before_script:
- go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest
script:
- tf2report --plan terraform.tfplan.json > report.md
- tf2report --plan terraform.tfplan.json --format json > report.json
artifacts:
paths:
- report.md
- report.json
expire_in: 1 week
Advanced GitLab CI with MR Comments
.gitlab-ci.yml
stages:
- plan
- analyze
- comment
variables:
TF_ROOT: ${CI_PROJECT_DIR}
terraform:plan:
stage: plan
image: hashicorp/terraform:latest
script:
- cd ${TF_ROOT}
- terraform init
- terraform plan -out=tfplan
- terraform show -json tfplan > terraform.tfplan.json
artifacts:
paths:
- ${TF_ROOT}/terraform.tfplan.json
terraform:analyze:
stage: analyze
image: golang:1.25
before_script:
- go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest
script:
- cd ${TF_ROOT}
- tf2report --plan terraform.tfplan.json > report.md
- tf2report --plan terraform.tfplan.json --format json > report.json
# Check for destructive changes
- |
DESTRUCTIVE=$(jq '.summary.to_destroy + .summary.to_replace' report.json)
if [ "$DESTRUCTIVE" -gt 0 ]; then
echo "WARNING: $DESTRUCTIVE destructive changes detected"
exit 1
fi
artifacts:
paths:
- ${TF_ROOT}/report.md
- ${TF_ROOT}/report.json
allow_failure: true
terraform:comment:
stage: comment
image: curlimages/curl:latest
script:
- |
REPORT=$(cat ${TF_ROOT}/report.md)
curl --request POST --header "PRIVATE-TOKEN: ${CI_JOB_TOKEN}" \
--data "body=${REPORT}" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
only:
- merge_requests
Jenkins Pipeline
Declarative Pipeline
Jenkinsfile
pipeline {
agent any
environment {
GO_VERSION = '1.25'
}
stages {
stage('Setup') {
steps {
sh 'go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest'
}
}
stage('Terraform Plan') {
steps {
sh '''
terraform init
terraform plan -out=tfplan
terraform show -json tfplan > terraform.tfplan.json
'''
}
}
stage('Generate Reports') {
steps {
sh '''
tf2report --plan terraform.tfplan.json > report.md
tf2report --plan terraform.tfplan.json --format json > report.json
'''
}
}
stage('Check Destructive Changes') {
steps {
script {
def destructive = sh(
script: 'jq ".summary.to_destroy + .summary.to_replace" report.json',
returnStdout: true
).trim().toInteger()
if (destructive > 0) {
echo "WARNING: ${destructive} destructive changes detected"
currentBuild.result = 'UNSTABLE'
}
}
}
}
stage('Archive Reports') {
steps {
archiveArtifacts artifacts: '*.md,*.json', fingerprint: true
}
}
}
}
Azure DevOps
azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: GoTool@0
inputs:
version: '1.25'
- script: |
go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest
displayName: 'Install tf2report'
- script: |
terraform init
terraform plan -out=tfplan
terraform show -json tfplan > terraform.tfplan.json
displayName: 'Terraform Plan'
- script: |
$(go env GOPATH)/bin/tf2report --plan terraform.tfplan.json > report.md
$(go env GOPATH)/bin/tf2report --plan terraform.tfplan.json --format json > report.json
displayName: 'Generate Reports'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.SourcesDirectory)'
artifactName: 'terraform-reports'
CircleCI
.circleci/config.yml
version: 2.1
orbs:
terraform: circleci/terraform@3.2
jobs:
plan-and-report:
docker:
- image: cimg/go:1.25
steps:
- checkout
- run:
name: Install Terraform
command: |
wget https://releases.hashicorp.com/terraform/1.10.5/terraform_1.10.5_linux_amd64.zip
unzip terraform_1.10.5_linux_amd64.zip
sudo mv terraform /usr/local/bin/
- run:
name: Install tf2report
command: go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest
- run:
name: Terraform Plan
command: |
terraform init
terraform plan -out=tfplan
terraform show -json tfplan > terraform.tfplan.json
- run:
name: Generate Report
command: |
~/go/bin/tf2report --plan terraform.tfplan.json > report.md
~/go/bin/tf2report --plan terraform.tfplan.json --format json > report.json
- store_artifacts:
path: report.md
- store_artifacts:
path: report.json
workflows:
terraform:
jobs:
- plan-and-report
Bitbucket Pipelines
bitbucket-pipelines.yml
pipelines:
pull-requests:
'**':
- step:
name: Terraform Plan and Report
image: golang:1.25
script:
- apt-get update && apt-get install -y wget unzip
- wget https://releases.hashicorp.com/terraform/1.10.5/terraform_1.10.5_linux_amd64.zip
- unzip terraform_1.10.5_linux_amd64.zip
- mv terraform /usr/local/bin/
- go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest
- terraform init
- terraform plan -out=tfplan
- terraform show -json tfplan > terraform.tfplan.json
- tf2report --plan terraform.tfplan.json > report.md
- tf2report --plan terraform.tfplan.json --format json > report.json
artifacts:
- report.md
- report.json
Docker-Based CI/CD
Dockerfile
Dockerfile
FROM hashicorp/terraform:latest AS terraform
FROM golang:1.25 AS builder
RUN go install github.com/germainlefebvre4/tf2report/cmd/tf2report@latest
FROM alpine:latest
COPY --from=terraform /bin/terraform /usr/local/bin/terraform
COPY --from=builder /go/bin/tf2report /usr/local/bin/tf2report
WORKDIR /workspace
ENTRYPOINT ["/bin/sh", "-c"]
CMD ["terraform plan -out=tfplan && terraform show -json tfplan > terraform.tfplan.json && tf2report --plan terraform.tfplan.json"]
Usage
docker build -t terraform-reporter .
docker run -v $(pwd):/workspace terraform-reporter
Common CI/CD Patterns
Pattern: Fail on Destructive Changes
#!/bin/bash
tf2report --plan terraform.tfplan.json --format json > report.json
DESTRUCTIVE=$(jq '.summary.to_destroy + .summary.to_replace' report.json)
if [ "$DESTRUCTIVE" -gt 0 ]; then
echo "❌ FAILURE: $DESTRUCTIVE destructive changes detected"
tf2report --plan terraform.tfplan.json --action delete --action replace
exit 1
fi
Pattern: Require Approval for Large Changes
#!/bin/bash
tf2report --plan terraform.tfplan.json --format json > report.json
TOTAL=$(jq '.summary.total_changes' report.json)
if [ "$TOTAL" -gt 10 ]; then
echo "⚠️ WARNING: $TOTAL changes detected - manual approval required"
exit 1
fi
Pattern: Multiple Environment Reports
#!/bin/bash
for ENV in dev staging prod; do
terraform workspace select $ENV
terraform plan -out=tfplan
terraform show -json tfplan > ${ENV}.tfplan.json
tf2report --plan ${ENV}.tfplan.json > reports/${ENV}-report.md
done
Next Steps
- Basic Usage - Simple examples
- Filtering - Filter reports in CI/CD