Skip to main content

Shift-left carbon analysis for Terraform — suggests lower-carbon cloud regions in pull requests.

Project description

🌱 CarbonGate

Shift-left carbon analysis for Terraform. Suggests lower-carbon cloud regions right in your Pull Requests.

🇫🇷 Lire en français


CarbonGate is a deterministic, zero-token, zero-latency CLI tool that analyzes Terraform infrastructure diffs and recommends AWS regions with lower carbon intensity — before you merge.

The Sweet Spot

You give away the software (free, open-source, runs on their own CI runner). You sell the data (fresh Parquet feed with real-time carbon intensity from emissions.dev). No dashboards. No LLM hallucinations. Just a YAML file, a ruthless parser, a DuckDB engine faster than electricity, and a gate (the data) that you control.

For buyers & sales calls

One-page pitch (FR), demo script, pricing summary, objections: docs/SALES_ONE_PAGER.md · product sheet: docs/FICHE_PRODUIT.md.

For executives (FR)

IMPACT© methodology — audit framework, ROI ranges, CSRD scope, 3× fee-back guarantee.
IMPACT Pre-Scan — qualification 1 repo + call 30 min → livrable 1 page (gratuit sous conditions).
Case study #0 — public terraform-aws-vpc demo (~86 % modeled carbon savings eu-west-1 → eu-north-1; not a paying client).
Trigger events playbook — CSRD / FinOps signals, CFO outreach, CHASE prioritization.
Executive audit template: gtm/templates/audit_report_exec.md · KPI Shield: CFO / CTO.


How It Works

Developer pushes Terraform on GitHub
        │
        ▼
GitHub Action triggers (ubuntu-latest)
        │
        ├── 1. Parse diff → extract AWS regions (regex, <0.1s)
        ├── 2. Query DuckDB over Parquet → find lowest-carbon alternative in same zone (0.01s)
        ├── 3. If savings ≥ 30%, format Markdown PR comment
        └── 4. Post comment via GitHub API

No API call per PR run. The intelligence is baked into the Parquet file. Your compute cost: $0.00.

Quick Start

pip install carbongate
carbongate --diff-file path/to/pr.diff
carbongate --hcl-file main.tf --tfvars-file terraform.tfvars  # optional
carbongate --plan-json plan.json             # terraform plan -json
carbongate --plan-json plan.json --json      # machine-readable output

Install with MCP support: pip install carbongate[mcp]

Agent-native usage (MCP)

CarbonGate exposes an MCP server for AI agents (Claude, Cursor, etc.):

carbongate serve

Configure your MCP client:

{
  "mcpServers": {
    "carbongate": {
      "command": "carbongate",
      "args": ["serve"]
    }
  }
}

Tools exposed:

Tool Input Description
analyze_plan plan_json_path Analyze a terraform plan -json file for carbon savings
analyze_diff diff_path + tfvars_path? + threshold_pct? Analyze a git diff file for carbon savings
suggest_region region Get the best lower-carbon alternative for an AWS region

JSON output is versioned (schema_version: 1) for agent workflow stability. JSON Schema available for automated validation.

MCP example: analyze a diff

# Agent calls via MCP — no shell required
analyze_diff(
    diff_path="/path/to/terraform.diff",
    tfvars_path="/path/to/terraform.tfvars",  # optional: resolves var.region
    threshold_pct=20.0                         # optional: lower threshold
)
# → {"schema_version": 1, "suggestions": [{"current_region": "us-east-1", ...}]}

From source (dev):

git clone https://github.com/carbongate/carbongate.git && cd carbongate
pip install -e ".[dev]"
carbongate --diff-file tests/fixtures/sample.diff

Regenerate carbon data (optional): EMISSIONS_DEV_API_KEY=em_live_xxxx python data_packer.py

Output Example

## 🌱 CarbonGate Shift-Left Analysis

⚠️ **Optimization detected** for your infrastructure deployment:

- **Current Region:** `us-east-1` (Carbon: 322.0 gCO₂eq/kWh)
- **Suggested Region:** `us-west-2` (Carbon: 122.5 gCO₂eq/kWh)

💰 **Estimated Carbon Savings:** `62.0%`
📉 **Spot Price Multiplier:** `0.8x` (Lower is cheaper)

> 💡 **Action:** Consider updating your Terraform config to use the suggested
> region(s) and reduce both carbon footprint and cloud costs.

Architecture

carbongate/
├── data_packer.py       # Polars → Fetches real CI from emissions.dev, writes Parquet
├── query_engine.py      # DuckDB in-process: get_best_alternative(region) → dict
├── tf_parser.py         # Deterministic regex + plan JSON: extract AWS regions
├── judge.py             # Decision engine: ≥30% savings threshold, Markdown + JSON
├── carbongate_cli.py     # Typer CLI entry point (carbongate analyze / serve)
├── mcp_server.py        # MCP server for AI agent integration (3 tools)
├── entrypoint.sh        # GitHub Action entrypoint (fetches diff via API)
├── Dockerfile           # python:3.12-slim, ready for GitHub Actions
├── action.yml           # GitHub Action definition
├── pyproject.toml       # Package metadata (pip install .)
└── data/
    ├── eu/eu_carbon.parquet    # EU region carbon data
    └── us/us_carbon.parquet    # US region carbon data

Module Dependency Graph

                    ┌─────────────┐
                    │ carbongate_cli │  Typer CLI, GitHub API integration
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
  diff.txt ────────►│  judge.py   │  Decision engine (≥30% threshold)
                    └──┬──────┬───┘
                       │      │
              ┌────────▼──┐ ┌─▼───────────┐
              │ tf_parser │ │query_engine  │  DuckDB over Parquet
              └───────────┘ └──────┬───────┘
                                   │
                          ┌────────▼──────────┐
                          │  data_packer.py   │  Polars → Parquet
                          │  emissions.dev    │  Real CI data
                          └───────────────────┘

Data Flow

emissions.dev API (real CI data)
        │
        ▼
data_packer.py ─── Polars DataFrame ───► data/{eu,us}/*.parquet
                                              │
                                              ▼
query_engine.py ─── DuckDB read_parquet() ───► get_best_alternative()
                                              │
                                              ▼
tf_parser.py ─── extract regions from diff ──► judge.py ──► Markdown PR comment

Region coverage

Detected in PR diff Notes
Literal region = "eu-west-1" provider, resources, locals
availability_zone Mapped to region when possible
region = var.region Resolved if terraform.tfvars / --tfvars-file sets region or aws_region
Auto tfvars terraform.tfvars, *.auto.tfvars, *.tfvars next to input file

Not covered (yet): unresolved var.region without tfvars, GCP/Azure, hourly carbon scheduling.

See Infracost integration, PyPI publish checklist, and sales one-pager (demo commands, pricing, objections).

Supported Regions

AWS Region Country Data Source Best EU/US pair (feed) Typical savings
eu-west-1 Ireland emissions.dev (IE) eu-north-1 ~86%
eu-west-2 United Kingdom emissions.dev (GB) eu-north-1 ~84%
eu-west-3 France emissions.dev (FR) eu-north-1 ~17% (below threshold)
eu-central-1 Germany emissions.dev (DE) eu-north-1 ~90%
eu-north-1 Sweden emissions.dev (SE) already optimal
us-east-1 Virginia eGRID 2023 (×0.92) us-west-2 ~62%
us-east-2 Ohio eGRID 2023 (×1.25) us-west-2 ~72%
us-west-1 California eGRID 2023 (×0.55) us-west-2 ~36%
us-west-2 Oregon eGRID 2023 (×0.35) already optimal

Savings are vs the lowest-carbon region in the same zone (EU or US), using the bundled Parquet feed. See query_engine.get_best_alternative().

30% savings threshold

CarbonGate only posts a PR comment when estimated carbon savings are ≥ 30% (SAVINGS_THRESHOLD_PCT in judge.py, overridable with --threshold). This avoids noisy suggestions for marginal gains.

Silent output is expected, not a bug, when:

  • The region is already near-optimal in its zone (e.g. eu-west-3 at ~42 gCO₂eq/kWh vs eu-north-1 at ~35 → ~17% savings, below threshold).
  • No resolvable region in the diff (e.g. var.region without tfvars, or only removed lines).

Example — eu-west-3 (no suggestion):

carbongate --diff-file tests/fixtures/eu-west-3-only.diff
🔍 Analyzing diff for carbon optimization opportunities...
✅ No carbon optimization detected (all regions within 30% of optimal).

Example — eu-west-1 (suggestion emitted):

carbongate --diff-file gtm/audits/terraform-aws-vpc/audit_input.diff
## 🌱 CarbonGate Shift-Left Analysis
...
💰 **Estimated Carbon Savings:** `86.4%`

CLI options (Phase 0)

Flag Description
analyze --diff-file Git diff from PR
analyze --hcl-file Static .tf scan (synthetic diff)
analyze --plan-json terraform plan -json / terraform show -json output
analyze --tfvars-file Explicit tfvars (repeatable); also auto-discovered
analyze --threshold Min savings % to comment (default 30)
analyze --api-key Fresh Parquet from emissions.dev
analyze --json Machine-readable JSON output (all input modes)
serve Start MCP server for AI agent integration

Configuration

Variable Required Description
EMISSIONS_DEV_API_KEY No emissions.dev API key (free: 500 req/month). Without it, uses realistic mock data.

Using the Action (published vs local)

Published (after release on GitHub):

- uses: carbongate/carbongate@v0.2
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    api-key: ${{ secrets.CARBONGATE_API_KEY }}

Same repository (development):

- uses: ./
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}

See GitHub Marketplace checklist.

GitHub Action Setup

Add to .github/workflows/carbongate.yml (or use cost + carbon with Infracost):

name: CarbonGate Analysis
on:
  pull_request:
    paths:
      - '**/*.tf'
      - '**/*.tfvars'

jobs:
  carbongate:
    runs-on: ubuntu-latest
    steps:
      - uses: carbongate/carbongate@v0.2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          api-key: ${{ secrets.CARBONGATE_API_KEY }}

Tech Stack

Component Technology Why
Data processing Polars Zero-copy, columnar, faster than Pandas
Query engine DuckDB In-process OLAP, reads Parquet in <1ms
CLI Typer Type-safe Click wrapper
Data format Apache Parquet Compressed columnar, 2MB per zone
Data source emissions.dev Free tier, annual CI per country + cloud region
Packaging Docker / pip GitHub Action + PyPI ready (85 pytest)

Performance

  • Parse diff: <0.1s (regex)
  • Query DuckDB: <0.01s (in-memory Parquet)
  • End-to-end: <0.2s
  • API calls per PR run: 0 (data pre-baked in Parquet)
  • Compute cost per PR: $0.00

License

MIT


CarbonGate — Analyse Carbone Shift-Left pour Terraform

🇫🇷 Français

CarbonGate est un outil CLI déterministe, zéro-token, zéro-latence qui analyse les diffs d'infrastructure Terraform et recommande des régions AWS avec une intensité carbone plus faible — avant le merge.

Le Sweet Spot

Tu donnes le logiciel (gratuit, open-source, tourne sur leur propre runner CI). Tu vends la donnée (feed Parquet frais avec l'intensité carbone temps réel d'emissions.dev). Pas de dashboards. Pas d'hallucinations LLM. Juste un fichier YAML, un parser impitoyable, un moteur DuckDB plus rapide que l'électricité, et une porte d'entrée (la donnée) que tu contrôles.

Comment ça marche

Le développeur pousse du Terraform sur GitHub
        │
        ▼
GitHub Action se déclenche (ubuntu-latest)
        │
        ├── 1. Parse le diff → extrait les régions AWS (regex, <0.1s)
        ├── 2. Interroge DuckDB sur le Parquet → trouve l'alternative la moins carbonée dans la même zone (0.01s)
        ├── 3. Si économies ≥ 30%, formate un commentaire Markdown PR
        └── 4. Poste le commentaire via l'API GitHub

Aucun appel API par exécution de PR. L'intelligence est pré-cuite dans le fichier Parquet. Ton coût compute : 0,00 €.

Démarrage Rapide

git clone https://github.com/carbongate/carbongate.git
cd carbongate
pip install duckdb polars requests typer
export EMISSIONS_DEV_API_KEY=em_live_xxxx    # Gratuit sur https://emissions.dev
python data_packer.py
carbongate --diff-file chemin/vers/terraform.diff

Architecture

carbongate/
├── data_packer.py       # Polars → Récupère CI réelle d'emissions.dev, écrit Parquet
├── query_engine.py      # DuckDB in-process : get_best_alternative(region) → dict
├── tf_parser.py         # Regex déterministe : extrait les régions AWS du diff git
├── judge.py             # Moteur de décision : seuil ≥30%, formatage Markdown
├── carbongate_cli.py    # Point d'entrée CLI Typer (analyze / serve)
├── mcp_server.py        # Serveur MCP pour intégration agent IA (3 tools)
├── entrypoint.sh        # Entrypoint GitHub Action
├── Dockerfile           # python:3.12-slim, prêt pour GitHub Actions
├── action.yml           # Définition GitHub Action
├── pyproject.toml       # Métadonnées package (pip install .)
└── data/
    ├── eu/eu_carbon.parquet
    └── us/us_carbon.parquet

Régions supportées

Région AWS Pays Source données Meilleur pair (feed) Économie typique
eu-west-1 Irlande emissions.dev (IE) eu-north-1 ~86 %
eu-west-2 Royaume-Uni emissions.dev (GB) eu-north-1 ~84 %
eu-west-3 France emissions.dev (FR) eu-north-1 ~17 % (sous seuil)
eu-central-1 Allemagne emissions.dev (DE) eu-north-1 ~90 %
eu-north-1 Suède emissions.dev (SE) déjà optimal
us-east-1 Virginie eGRID 2023 (×0.92) us-west-2 ~62 %
us-east-2 Ohio eGRID 2023 (×1.25) us-west-2 ~72 %
us-west-1 Californie eGRID 2023 (×0.55) us-west-2 ~36 %
us-west-2 Oregon eGRID 2023 (×0.35) déjà optimal

Seuil 30 %

CarbonGate ne commente une PR que si l'économie carbone estimée est ≥ 30 % (SAVINGS_THRESHOLD_PCT dans judge.py). Exemple : eu-west-3 (~42 gCO₂eq/kWh) vs eu-north-1 (~35) → ~17 % → sortie silencieuse (« No carbon optimization detected »), pas un bug.


AI / LLM / Agent Usage

For AI Agents (Claude Code, Cursor, Copilot, etc.)

CarbonGate is designed to be operated by AI agents as well as humans. The codebase follows these agent-friendly conventions:

  1. Single-file modules: each .py file is a self-contained module with a clear single responsibility
  2. Type-safe: all public functions use TypedDict or explicit return types
  3. Deterministic: zero LLM calls in the pipeline; everything is regex + SQL
  4. Silent on success: functions return None when there's nothing to report; no spam
  5. Idempotent: data_packer.py can be re-run safely; query_engine.py reads latest data
  6. Mock fallback: works without API keys for dev/testing

For LLM Context Windows

When an AI agent reads this project, the optimal files to load are:

Priority File Purpose
1 README.md This file — architecture overview
2 pyproject.toml Dependencies and entry points
3 cli.py Understand the CLI surface
4 judge.py Decision logic + Markdown format
5 query_engine.py DuckDB query patterns
6 tf_parser.py Region extraction regex
7 data_packer.py Data pipeline (only if modifying data sources)

llms.txt

# CarbonGate — Shift-left carbon analysis for Terraform
# Architecture (7 files, 1019 lines)

## Core Pipeline
carbongate_cli.py: Typer CLI (--diff-file, --hcl-file, --plan-json, --json, --threshold, serve)
judge.py: Decision engine (≥30% savings threshold), Markdown + JSON formatter
tf_parser.py: hcl2 + regex + plan JSON recursive; tfvars; var.region resolution
query_engine.py: DuckDB in-process query engine over Parquet carbon data

## Agent Integration
mcp_server.py: FastMCP server — 3 tools: analyze_plan, analyze_diff, suggest_region
               Entry point: carbongate serve or carbongate-serve
               JSON output: schema_version: 1 envelope

## Data Pipeline
data_packer.py: Polars-based ETL. Fetches real CI from emissions.dev API, falls back to mock data.
                 Reads EMISSIONS_DEV_API_KEY from env. Writes data/{eu,us}/*.parquet.

## Deployment
action.yml: GitHub Action definition (docker-based)
entrypoint.sh: Extracts PR diff via GitHub API, runs carbongate (supports INPUT_PLAN_JSON_FILE)
Dockerfile: python:3.12-slim with curl, jq, duckdb, polars
pyproject.toml: Package metadata, dependencies, entry points (carbongate, carbongate-serve)

## Data
data/eu/eu_carbon.parquet: EU region carbon intensity (5 regions × latest timestamp)
data/us/us_carbon.parquet: US region carbon intensity (4 regions × latest timestamp)

## External APIs
emissions.dev /v1/electricity/grid: country-level grid carbon intensity (free tier: 500 req/month)
GitHub REST API: POST /repos/{owner}/{repo}/issues/{pr_number}/comments

## Key Design Decisions
- Annual average CI (not real-time): structural advantage matters more than hourly fluctuations for infra decisions
- No LLM calls in pipeline: purely deterministic (regex + SQL)
- Data pre-baked as Parquet: zero API calls per PR run, $0 compute cost for user
- AWS region → country mapping: static table, no runtime lookups
- 30% savings threshold: avoids noisy suggestions for marginal improvements

Competitor Landscape (2026)

Tool Position CarbonGate Advantage
Infracost + InfraCarbon PR comments, closed backend Open-source CLI, Parquet data feed, no dashboard lock-in
CarbonAware Scheduling, K8s/Airflow Pre-merge analysis, not post-deploy
Cloud Carbon Footprint Post-deploy reporting dashboard Shift-left: catch it before merge
Climatiq General carbon API Deprecated cloud endpoint (Sep 2026)

Built with ❤️ and DuckDB.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

carbongate-0.5.3.tar.gz (36.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

carbongate-0.5.3-py3-none-any.whl (40.7 kB view details)

Uploaded Python 3

File details

Details for the file carbongate-0.5.3.tar.gz.

File metadata

  • Download URL: carbongate-0.5.3.tar.gz
  • Upload date:
  • Size: 36.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.13

File hashes

Hashes for carbongate-0.5.3.tar.gz
Algorithm Hash digest
SHA256 99faabfce7f3068728545f437d676ae703f6dcef73d027501b0a26172ea1d8de
MD5 4fac2d3177b4be538eb35056214478e2
BLAKE2b-256 0175f92e20481a07c5aca9f5a05aff296a2f561ee998248e6e76da597500da5d

See more details on using hashes here.

File details

Details for the file carbongate-0.5.3-py3-none-any.whl.

File metadata

  • Download URL: carbongate-0.5.3-py3-none-any.whl
  • Upload date:
  • Size: 40.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.13

File hashes

Hashes for carbongate-0.5.3-py3-none-any.whl
Algorithm Hash digest
SHA256 a15f82a1563dd490ad3432e5bce88f78e288cc7e3be027270f9fc028b324998e
MD5 8481faa298365441636a31ee824746c3
BLAKE2b-256 7da2c35983cf0c984bacb7a7bcfbd1540ac03f98ceb5dbb13c5f518c59ba78b5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page