Stop drowning in verbose plan output. Generate structured Markdown reports that render perfectly in PR comments.
Reviewing Terraform changes in PRs is painful with raw plan output
# azurerm_network_security_rule.security_rule[2] will be created
+ resource "azurerm_network_security_rule" "rule" {
+ access = "Allow"
+ destination_address_prefix = "10.0.0.0/8"
+ destination_port_range = "22"
+ direction = "Inbound"
+ name = "ssh_from_corporate"
+ priority = 100
+ protocol = "Tcp"
+ source_address_prefix = "10.0.0.0/8"
+ source_port_range = "*"
+ resource_group_name = "rg-network"
+ network_security_group_name = "nsg-prod"
}
# azurerm_network_security_rule.security_rule[3] will be created
+ resource "azurerm_network_security_rule" "rule" {
+ access = "Deny"
+ destination_address_prefix = "*"
+ destination_port_range = "3389"
...
Raw plan output is overwhelming and difficult to scan
Hard to understand what's actually changing and why
Constantly mapping resource IDs to names in your head
Index changes instead of semantic "what actually changed"
Firewall and NSG rules rendered with protocols, ports, and actions
Principal IDs, role names, and scopes resolved to readable text
Shows computed diffs for large values like JSON policies or scripts
Designed and tested for GitHub and Azure DevOps rendering
Click to enlarge
Run one command to convert your Terraform plan
terraform show -json plan.tfplan | docker run -i oocx/tfplan2md
Everything you need for better Terraform PR reviews
Side-by-side "Before" and "After" values with inline highlighting show exactly what changed.
Learn more →Azure Firewall rule collections rendered as readable tables with protocols, ports, and actions.
Learn more →Network Security Group rules as readable tables, making security changes easy to audit.
Learn more →Resolves cryptic Principal IDs, role names, and scopes to human-readable names.
Learn more →Multi-line values like JSON policies show computed diffs with inline highlighting.
Learn more →Designed and tested for rendering in markdown pull requests on Azure DevOps & GitHub.
Learn more →Displays friendly names for resources instead of complex resource ID strings.
Learn more →Integrate tfplan2md into your GitHub Actions, Azure Pipelines, or GitLab CI workflow. Automatically post plan reports as PR comments.
View Integration Guides- name: Generate markdown report
run: |
terraform show -json plan.tfplan | \
docker run -i oocx/tfplan2md > plan.md
- name: Post PR comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('plan.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: plan
});
- script: terraform show -json plan.tfplan > plan.json
displayName: 'Convert to JSON'
workingDirectory: terraform
- script: |
docker run -v $(pwd):/data oocx/tfplan2md \
/data/plan.json --output /data/plan.md
displayName: 'Generate markdown report'
workingDirectory: terraform
- bash: |
MARKDOWN=$(cat terraform/plan.md)
URL="$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.ID)/pullRequests/$(System.PullRequest.PullRequestId)/threads?api-version=7.0"
BODY=$(jq -n \
--arg content "$MARKDOWN" \
'{comments: [{content: $content, commentType: 1}], status: 1}')
curl -X POST "$URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $(System.AccessToken)" \
-d "$BODY"
displayName: 'Post PR comment'