Skip to main content

Native CI Integration: Rich Plan Summaries Without Extra Actions

· 5 min read
Erik Osterman
Founder @ Cloud Posse

When you see complex bash scripts and conditional logic in GitHub Actions workflows, that's a signal: the underlying tool wasn't designed for CI. Atmos now has built-in CI integration that makes the same command work identically locally and in CI—no wrapper scripts, no extra actions, no hidden complexity.

The Problem with CI Glue Code

Look at any mature infrastructure repository's CI workflows. You'll find bash scripts parsing terraform output with grep and awk, conditional logic to handle plan files across jobs, and environment variable gymnastics to pass data between steps.

This complexity isn't accidental—it's compensation. When tools aren't designed for CI, teams build layers of glue code to bridge the gap. The cost is real: workflows that work in CI fail locally (and vice versa), debugging requires reproducing the entire CI environment, and tribal knowledge accumulates in workflow files.

The Reproducibility Principle

Infrastructure tools should follow a simple principle: the same command should produce the same behavior everywhere.

# This should work identically:
atmos terraform plan vpc -s prod # locally
atmos terraform plan vpc -s prod # in GitHub Actions
atmos terraform plan vpc -s prod # in GitLab CI

When a tool is truly CI-native, your workflow files become trivial:

# Before: Complex workflow with hidden logic
- name: Plan
run: |
output=$(atmos terraform plan vpc -s prod 2>&1)
echo "$output"
# Parse for changes...
# Upload artifacts...
# Post PR comment...

# After: CI-native tool
- name: Plan
run: atmos terraform plan vpc -s prod

What This Enables

Previously, getting beautiful plan summaries in GitHub Actions required using separate actions like github-action-atmos-terraform-plan. These wrapped the CLI with CI-specific behaviors, creating two codebases that evolved separately.

Now, Atmos handles everything natively. The CLI detects when it's running in CI and automatically generates the same rich output you're used to—resource badges, collapsible diffs, terraform outputs.

What You Get

  • Rich job summaries — resource badges, collapsible diffs, plan/apply templates (written to $GITHUB_STEP_SUMMARY)
  • Live status checks — real-time progress ("Plan in progress" → "3 to add, 1 to change, 0 to destroy")
  • Output variables — plan/apply results exported to $GITHUB_OUTPUT for downstream jobs
  • Planfile storage — store planfiles in S3, GitHub Artifacts, or local filesystem with SHA256 integrity verification
  • Custom templates — Go template syntax for full control over summaries and comments
  • Auto-detection — CI mode enabled automatically from CI=true or GITHUB_ACTIONS=true
  • Same command works locally and in CI
  • PR comments (coming soon) — auto-updated plan summaries on pull requests

Quick Start

Here's a minimal workflow using profiles and auth with OIDC:

name: Terraform Plan

on:
pull_request:
branches: [main]

jobs:
plan:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
env:
ATMOS_PROFILE: ci
ATMOS_IDENTITY: plat-dev/admin
steps:
- uses: actions/checkout@v4
- uses: cloudposse/github-action-setup-atmos@v2

- name: Terraform Plan
run: atmos terraform plan mycomponent -s dev-us-east-1

That's it. Atmos detects GitHub Actions automatically and writes the plan summary to $GITHUB_STEP_SUMMARY.

Example Output

Changes Found for vpc in dev-us-east-1

create destroy

Caution

Terraform will delete resources! This plan contains resource delete operations. Please check the plan result very carefully.

Resources: 3 to add, 0 to change, 1 to destroy.

To reproduce this locally, run:

atmos terraform plan vpc -s dev-us-east-1

Create

+ aws_vpc.main
+ aws_subnet.public[0]
+ aws_subnet.public[1]

Destroy

- aws_security_group.deprecated
Terraform Plan Summary
# random_id.id2 will be created
+ resource "random_id" "id2" {
+ b64_std = (known after apply)
+ b64_url = (known after apply)
+ byte_length = 8
+ dec = (known after apply)
+ hex = (known after apply)
+ id = (known after apply)
}

Plan: 1 to add, 0 to change, 0 to destroy.

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

cluster_id = "cluster-754a1c6160064d0b"
lb_id = "load-balancer-763d296bbfc8ccc6"
vpc_id = "vpc-69e5cf6a55e1eb81"
Terraform Outputs
OutputValue
cluster_idcluster-754a1c6160064d0b
lb_idload-balancer-763d296bbfc8ccc6
vpc_idvpc-69e5cf6a55e1eb81

Learn More