Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1335156
feat: add Terraform plan parsing and validation logic with test cases
Cro22 May 7, 2026
9c9a46c
feat: implement AWS resource attribute extraction for EC2, RDS, and EBS
Cro22 May 7, 2026
61a63cc
feat: add support for AWS Lambda, NAT Gateway, and RDS Cluster Instan…
Cro22 May 7, 2026
ba1169f
feat: implement AWS Pricing API client for product lookups with pagin…
Cro22 May 7, 2026
0c47503
feat: implement disk-based caching for AWS Pricing API product lookups
Cro22 May 7, 2026
989db0a
feat: add EC2 cost estimation logic with support for root EBS volume …
Cro22 May 8, 2026
b198f8e
feat: implement EBS pricing lookup and estimation for various volume …
Cro22 May 8, 2026
48372b0
feat: implement cost estimation for Terraform resource changes with C…
Cro22 May 8, 2026
2290b50
feat: add integration tests and new utility tools for pricing workflows
Cro22 May 8, 2026
8e3b296
feat: add markdown templates for various cloud cost impact scenarios
Cro22 May 8, 2026
22666da
feat: ensure Markdown blocks are separated by blank lines for proper …
Cro22 May 8, 2026
e45998d
Merge pull request #12 from Cro22/feature/terraform
Cro22 May 9, 2026
bf10fee
feat: refactor LLM narrative generation to separate summary and text …
Cro22 May 9, 2026
48bb794
feat: enhance caveat handling by grouping assumptions by resource in …
Cro22 May 9, 2026
3a42517
chore: remove legacy `checkpoint135` code, Terraform configuration, a…
Cro22 May 9, 2026
6beea5c
feat: add `pr-check` subcommand for rendering Terraform plans as Mark…
Cro22 May 9, 2026
62dd731
feat: implement GitHub REST API client for PR comment management
Cro22 May 9, 2026
d5fcaaf
feat: add support for posting Terraform plan comments to GitHub via P…
Cro22 May 9, 2026
a0ea88d
feat: add CloudOracle GitHub Action for Terraform cost analysis and u…
Cro22 May 9, 2026
3ac3e34
test: add in-repo Action self-test workflow with toy Terraform plan
Cro22 May 9, 2026
32789f0
Merge pull request #14 from Cro22/feature/action-self-test
Cro22 May 9, 2026
70d7ab8
fix: update environment variable from ANTHROPIC_API_KEY to GEMINI_API…
Cro22 May 9, 2026
47d1e91
Merge pull request #15 from Cro22/feature/action-self-test
Cro22 May 9, 2026
145c8ba
feat: update Terraform version to latest in cost self-test workflow
Cro22 May 9, 2026
d984a90
Merge pull request #17 from Cro22/feature/action-self-test
Cro22 May 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,59 @@
# Shared .dockerignore — applies to BOTH the v1 dashboard build
# (Dockerfile) and the v2 Action build (Dockerfile.action).
#
# Rule of thumb: only list paths neither image needs in its build
# context. If a path is needed by exactly one of the two, leave it
# in and let the COPY in the other Dockerfile decide. Splitting this
# file per-Dockerfile (e.g. via Dockerfile.<name>.dockerignore) is
# avoided so we have one source of truth.

# --- Local dev / VCS / IDE noise ---
.git/
.github/
.idea/
.claude/

# --- Build artefacts that should never enter an image ---
*.exe
*.test
*.out
*.pem
oracle
oracle.exe
probe-pricing
probe-pricing.exe
cloudoracle
cloudoracle.exe

# --- Secrets ---
.env
.env.*

# --- v1 dashboard frontend artefacts ---
# (rebuilt inside the multi-stage v1 Dockerfile; ignored from the host)
web/node_modules/
web/dist/
internal/api/dist/

report.pdf
# --- Test fixtures and tests (not needed at runtime) ---
**/testdata/
**/*_test.go

# --- Other cmd/ binaries that are dev-only, not for shipping ---
cmd/probe-pricing/
cmd/checkpoint*/
cmd/narrative-preview/

# --- Project meta / sample artefacts ---
checkpoint/
docker-compose.yml
example.png
example_report.pdf
examplepdf.png
cloudoracle-report.pdf
cloudoracle
cloudoracle.exe
README.md

# --- Docker plumbing ---
Dockerfile
Dockerfile.action
.dockerignore
README.md
107 changes: 107 additions & 0 deletions .github/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# CloudOracle Action — Workflow Examples

These two YAML files are runnable references for wiring the CloudOracle
Action into a Terraform repository. Pick whichever matches your auth
posture and LLM appetite, copy it under `.github/workflows/` in your
target repo, and adjust the paths and IAM ARN.

## Which example do I want?

| File | AWS auth | LLM narrative | Best for |
|------|----------|---------------|----------|
| [`terraform-plan.yml`](terraform-plan.yml) | OIDC (recommended) | Yes (Anthropic / Gemini / OpenAI) | Production setups, security-conscious orgs |
| [`terraform-plan-no-llm.yml`](terraform-plan-no-llm.yml) | Static access keys | No (templated text) | Quick start, no LLM procurement, air-gapped CI |

Both produce a single PR comment that updates in place across pushes
(via the `cloudoracle-pr-v1` HTML marker), so the conversation thread
stays clean.

## Required permissions

The workflow needs the following at minimum:

```yaml
permissions:
pull-requests: write # to post/update the comment
contents: read # to checkout the repo
id-token: write # ONLY when using OIDC (omit for static keys)
```

GitHub's default workflow permissions vary by org policy; declaring
them explicitly makes the workflow portable.

## AWS IAM setup for OIDC

The OIDC example assumes a role trust policy of the form:

```json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:YOUR_ORG/YOUR_REPO:pull_request"
}
}
}]
}
```

The role only needs `pricing:GetProducts` on `*` — CloudOracle does not
read or modify any AWS resources beyond Pricing API metadata:

```json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "pricing:GetProducts",
"Resource": "*"
}]
}
```

GitHub's OIDC setup guide:
https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services

## LLM key as a secret

Set one of the following as a repository or organisation secret:

- `ANTHROPIC_API_KEY` (Claude — used by default in v2)
- `GEMINI_API_KEY`
- `OPENAI_API_KEY`

Pass it via `env:` in the step that uses the Action (see
`terraform-plan.yml`). Without any key the Action degrades silently to
the templated narrative — the PR comment is still posted, just less
narrated.

## Action inputs

| Input | Required | Default | What it does |
|-------|----------|---------|--------------|
| `plan-file` | yes | — | Path to `terraform show -json` output |
| `region` | no | `us-east-2` | AWS region for pricing |
| `output-file` | no | `` (empty) | Also write the Markdown to this file (e.g. for artefact upload) |
| `marker` | no | `cloudoracle-pr-v1` | HTML comment marker for upsert |
| `no-llm` | no | `false` | Force templated narrative |
| `github-token` | no | `${{ github.token }}` | Token for the comment POST/PATCH |

## Behaviour notes

- The Action only **posts** when `GITHUB_EVENT_NAME` is `pull_request`
or `pull_request_target`. Other events render the Markdown and exit;
use `output-file` to capture it elsewhere.
- The Action exits with differentiated codes:
- `0`: success
- `1`: input error (missing plan file, bad flags)
- `2`: pricing error (AWS API failure)
- `3`: output error (file write failed)
- `4`: GitHub error (post/update failed)
42 changes: 42 additions & 0 deletions .github/examples/terraform-plan-no-llm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Terraform Plan with Cost Comment (No LLM)

# Minimal example for teams that:
# - cannot or do not want to send plan data to an LLM provider
# - haven't set up OIDC and prefer long-lived AWS access keys
# - want the smallest possible permissions surface
#
# The output is the deterministic templated narrative ("This plan adds
# N resources..."), which is functional but less informative than the
# LLM-narrated version in terraform-plan.yml.
on:
pull_request:
paths: ['**.tf']

permissions:
pull-requests: write
contents: read

jobs:
cost-impact:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-2

- uses: hashicorp/setup-terraform@v3

- run: terraform init

- run: terraform plan -out=tf.plan

- run: terraform show -json tf.plan > tf-plan.json

- uses: Cro22/CloudOracle@v2.0.0
with:
plan-file: tf-plan.json
no-llm: 'true'
64 changes: 64 additions & 0 deletions .github/examples/terraform-plan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Terraform Plan with Cost Comment

# Triggers on PRs that touch Terraform files. The path filter is a soft
# optimisation: jobs are skipped when no .tf-shaped files changed, so a
# PR editing only README.md doesn't burn a runner minute on a no-op cost
# comment. Tighten the patterns to your monorepo's layout if needed.
on:
pull_request:
paths:
- '**.tf'
- '**.tfvars'
- '.terraform.lock.hcl'

permissions:
pull-requests: write # CloudOracle needs this to post/update the comment.
id-token: write # Required by aws-actions/configure-aws-credentials when using OIDC.
contents: read

jobs:
cost-impact:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

# OIDC role assumption — the recommended path for cloud auth in
# CI. No long-lived AWS credentials sitting in repo secrets. See
# README.md in this directory for a sample IAM trust policy.
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsCloudOracle
aws-region: us-east-2

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0

- name: Terraform init
run: terraform init

- name: Terraform plan
run: terraform plan -out=tf.plan

- name: Convert plan to JSON
run: terraform show -json tf.plan > tf-plan.json

# CloudOracle reads tf-plan.json, queries the AWS Pricing API for
# each changed resource, asks the LLM for a 1-3 sentence narrative,
# and posts/upserts a comment on the PR using the workflow token.
- name: CloudOracle cost analysis
uses: Cro22/CloudOracle@v2.0.0
with:
plan-file: tf-plan.json
region: us-east-2
env:
# The LLM auto-detects which provider to use based on which key
# is set; you only need one of these. Pick whichever provider
# your org has procurement for. Without any key, CloudOracle
# falls back silently to the templated narrative.
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
# OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
67 changes: 67 additions & 0 deletions .github/workflows/cost-self-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Action self-test (Cost Comment)

# In-repo smoke test for the CloudOracle Action. Runs the Action
# against the toy Terraform plan in e2e-test/, using `uses: ./` so
# Docker builds the image from the current checkout instead of
# pulling a published tag. This catches regressions in the Action
# manifest, Dockerfile.action, entrypoint.sh, or the pr-check
# command before they reach a tagged release.

on:
pull_request:
branches: [main]
paths:
- 'action.yml'
- 'Dockerfile.action'
- 'entrypoint.sh'
- 'cmd/oracle/**'
- 'internal/iac/**'
- 'internal/pricing/**'
- 'internal/diff/**'
- 'internal/github/**'
- 'e2e-test/**'
- '.github/workflows/cost-self-test.yml'
workflow_dispatch:

permissions:
pull-requests: write
contents: read

jobs:
cost-impact:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-2

- uses: hashicorp/setup-terraform@v3
with:
terraform_version: latest

- name: Terraform init
working-directory: e2e-test
run: terraform init

- name: Terraform plan
working-directory: e2e-test
run: terraform plan -out=tf.plan

- name: Convert plan to JSON
working-directory: e2e-test
run: terraform show -json tf.plan > tf-plan.json

# `uses: ./` builds Dockerfile.action from the checked-out tree,
# so any changes to the Action code on this branch are exercised
# end-to-end before publishing a tag.
- name: CloudOracle (in-repo build)
uses: ./
with:
plan-file: e2e-test/tf-plan.json
region: us-east-2
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
Loading
Loading