4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
283 lines
11 KiB
YAML
283 lines
11 KiB
YAML
name: terraform-bench (plan)
|
|
|
|
# Runs terraform fmt + init + validate + plan against tests/benchmarks/cloudopsbench/infra/ on every PR
|
|
# that touches the bench infra. Posts the plan output as a sticky PR comment so
|
|
# reviewers can see what would change before approving.
|
|
#
|
|
# Never applies — apply is developer-local in v1 (see tests/benchmarks/cloudopsbench/infra/README.md).
|
|
# When apply-from-CI is desirable later, add a separate workflow that runs on
|
|
# merge-to-main with a different (higher-privileged) AWS role.
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- "tests/benchmarks/cloudopsbench/infra/**"
|
|
- ".github/workflows/terraform-bench.yml"
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
id-token: write # required for AWS OIDC role assumption
|
|
|
|
concurrency:
|
|
group: terraform-bench-${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
plan:
|
|
name: terraform plan
|
|
if: github.repository == 'Tracer-Cloud/opensre' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
|
runs-on: ubuntu-latest
|
|
|
|
defaults:
|
|
run:
|
|
working-directory: tests/benchmarks/cloudopsbench/infra
|
|
|
|
env:
|
|
AWS_REGION: us-east-1
|
|
# tfplan + plan.txt artifacts are not uploaded; plan output goes into the
|
|
# PR comment. If you want a downloadable plan binary, add an
|
|
# actions/upload-artifact step after `terraform show`.
|
|
TF_IN_AUTOMATION: "true"
|
|
TF_INPUT: "0"
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Setup Terraform
|
|
uses: hashicorp/setup-terraform@v3
|
|
with:
|
|
terraform_version: "1.7.5"
|
|
# Wrapper MUST stay enabled (default) — it's what populates
|
|
# `steps.<id>.outputs.stdout` for the `terraform show` step that
|
|
# feeds the sticky PR comment. Disabling it makes the comment
|
|
# render with a blank plan body. See greptile review on initial
|
|
# PR for details.
|
|
|
|
- name: Configure AWS credentials (OIDC role assumption)
|
|
# No long-lived AWS keys. The role is provisioned by tests/benchmarks/cloudopsbench/infra/
|
|
# Terraform; ARN is hardcoded here because Terraform outputs aren't
|
|
# available before the workflow can run. Account ID is the
|
|
# tracer-cloud account.
|
|
uses: aws-actions/configure-aws-credentials@v4
|
|
with:
|
|
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/opensre-bench-terraform-plan
|
|
role-session-name: github-actions-terraform-plan-${{ github.run_id }}
|
|
aws-region: us-east-1
|
|
|
|
- name: terraform fmt
|
|
id: fmt
|
|
run: terraform fmt -check -recursive -no-color
|
|
continue-on-error: true
|
|
|
|
- name: terraform init
|
|
id: init
|
|
run: terraform init -no-color
|
|
|
|
- name: terraform validate
|
|
id: validate
|
|
run: terraform validate -no-color
|
|
|
|
- name: terraform plan
|
|
id: plan
|
|
run: terraform plan -no-color -input=false -lock=false -out=tfplan
|
|
continue-on-error: true
|
|
|
|
- name: terraform show
|
|
id: show
|
|
if: steps.plan.outcome == 'success'
|
|
run: terraform show -no-color tfplan
|
|
|
|
- name: Post plan to PR
|
|
if: github.event_name == 'pull_request' && always()
|
|
uses: actions/github-script@v7
|
|
env:
|
|
PLAN_STDOUT: ${{ steps.show.outputs.stdout }}
|
|
FMT_OUTCOME: ${{ steps.fmt.outcome }}
|
|
INIT_OUTCOME: ${{ steps.init.outcome }}
|
|
VALIDATE_OUTCOME: ${{ steps.validate.outcome }}
|
|
PLAN_OUTCOME: ${{ steps.plan.outcome }}
|
|
WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
with:
|
|
script: |
|
|
const fs = require('fs');
|
|
const MARKER = '<!-- terraform-bench-plan-comment -->';
|
|
|
|
const fmt = process.env.FMT_OUTCOME;
|
|
const init = process.env.INIT_OUTCOME;
|
|
const validate = process.env.VALIDATE_OUTCOME;
|
|
const plan = process.env.PLAN_OUTCOME;
|
|
const url = process.env.WORKFLOW_URL;
|
|
|
|
const icon = (outcome) => outcome === 'success' ? '✅' :
|
|
outcome === 'failure' ? '❌' :
|
|
outcome === 'skipped' ? '⏭️' : '⚠️';
|
|
|
|
let planBody = process.env.PLAN_STDOUT || '';
|
|
// GitHub PR comments cap at 65536 chars. Reserve ~1000 for surrounding text.
|
|
const MAX = 60000;
|
|
let truncated = false;
|
|
if (planBody.length > MAX) {
|
|
planBody = planBody.slice(-MAX);
|
|
truncated = true;
|
|
}
|
|
|
|
const body = [
|
|
MARKER,
|
|
'### terraform-bench plan',
|
|
'',
|
|
'| step | outcome |',
|
|
'| --- | --- |',
|
|
`| fmt | ${icon(fmt)} \`${fmt}\` |`,
|
|
`| init | ${icon(init)} \`${init}\` |`,
|
|
`| validate | ${icon(validate)} \`${validate}\` |`,
|
|
`| plan | ${icon(plan)} \`${plan}\` |`,
|
|
'',
|
|
fmt !== 'success' ? '> Run `terraform fmt -recursive` in `tests/benchmarks/cloudopsbench/infra/` to fix formatting.' : '',
|
|
'',
|
|
plan === 'success'
|
|
? `<details><summary>Plan output${truncated ? ' (truncated — see <a href="' + url + '">workflow logs</a> for full output)' : ''}</summary>\n\n\`\`\`hcl\n${planBody}\n\`\`\`\n\n</details>`
|
|
: `Plan did not run successfully — see [workflow logs](${url}) for details.`,
|
|
'',
|
|
`_Updated by [\`terraform-bench.yml\`](${url})._`,
|
|
].join('\n');
|
|
|
|
// Find an existing comment to update, else create a new one.
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
});
|
|
const existing = comments.find(c => c.body && c.body.includes(MARKER));
|
|
|
|
if (existing) {
|
|
await github.rest.issues.updateComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: existing.id,
|
|
body,
|
|
});
|
|
} else {
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
body,
|
|
});
|
|
}
|
|
|
|
- name: Fail if any step errored
|
|
if: |
|
|
steps.fmt.outcome == 'failure' ||
|
|
steps.init.outcome == 'failure' ||
|
|
steps.validate.outcome == 'failure' ||
|
|
steps.plan.outcome == 'failure'
|
|
run: |
|
|
echo "::error::One or more terraform steps failed. See PR comment + workflow logs."
|
|
exit 1
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
# Security + lint scanners — run in parallel with plan. #
|
|
# #
|
|
# Suppression policy (intentional skips) is defined per-tool: #
|
|
# - checkov: tests/benchmarks/cloudopsbench/infra/.checkov.yaml #
|
|
# - tfsec: tests/benchmarks/cloudopsbench/infra/.tfsec/config.yml #
|
|
# - tflint: tests/benchmarks/cloudopsbench/infra/.tflint.hcl #
|
|
# Each suppression includes a justification at the source. Day-one #
|
|
# findings should be REAL findings — change configs, not the workflow, #
|
|
# if a rule needs to be skipped. #
|
|
# #
|
|
# checkov + tfsec hard-fail (security blocks PRs). tflint soft-fails #
|
|
# because its rules cover code quality / deprecations, not security — #
|
|
# we want visibility without blocking. #
|
|
# ---------------------------------------------------------------------- #
|
|
|
|
tflint:
|
|
name: tflint
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: tests/benchmarks/cloudopsbench/infra
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Setup tflint
|
|
uses: terraform-linters/setup-tflint@v4
|
|
with:
|
|
tflint_version: latest
|
|
|
|
- name: tflint --init (download plugins)
|
|
run: tflint --init
|
|
|
|
- name: tflint
|
|
id: tflint
|
|
run: tflint --recursive --format compact
|
|
continue-on-error: true
|
|
|
|
- name: Surface tflint findings (non-blocking)
|
|
if: steps.tflint.outcome == 'failure'
|
|
run: |
|
|
echo "::warning::tflint reported findings. Code-quality only — does not block PR. Review in workflow logs."
|
|
|
|
tfsec:
|
|
name: tfsec
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
# tfsec via Docker, bypassing aquasecurity/tfsec-action.
|
|
#
|
|
# Why direct Docker run: the action shells out to api.github.com to
|
|
# resolve the latest release binary, and the aquasecurity org has an
|
|
# IP allow list that blocks GitHub Actions runner IPs — making the
|
|
# action effectively unusable from CI. The aquasec/tfsec image on
|
|
# Docker Hub has no such restriction.
|
|
#
|
|
# Migration path (v2): tfsec is in maintenance mode; rule set lives on
|
|
# in Trivy. Replace with `aquasec/trivy:latest config /src` when
|
|
# ready — same .tfsec/config.yml is recognized.
|
|
#
|
|
# Suppressions: tests/benchmarks/cloudopsbench/infra/.tfsec/config.yml. tfsec exits 1 on any
|
|
# finding by default, which fails the step — exactly the hard-fail
|
|
# behavior we want. Add a justified exclude to the config rather
|
|
# than disabling the step.
|
|
- name: tfsec scan (via Docker)
|
|
run: |
|
|
docker run --rm \
|
|
-v "${{ github.workspace }}/tests/benchmarks/cloudopsbench/infra:/src" \
|
|
aquasec/tfsec:latest \
|
|
/src \
|
|
--config-file=/src/.tfsec/config.yml \
|
|
--no-color
|
|
|
|
checkov:
|
|
name: checkov
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
# Suppressions live in tests/benchmarks/cloudopsbench/infra/.checkov.yaml. soft_fail is
|
|
# FALSE — real findings block the PR. Add a justified skip-check
|
|
# to the config rather than disabling here.
|
|
- name: checkov scan
|
|
uses: bridgecrewio/checkov-action@v12
|
|
with:
|
|
directory: tests/benchmarks/cloudopsbench/infra
|
|
framework: terraform
|
|
config_file: tests/benchmarks/cloudopsbench/infra/.checkov.yaml
|
|
quiet: true
|
|
soft_fail: false
|
|
# Re-enable SARIF upload when CodeQL alerts are wanted:
|
|
# output_format: cli,sarif
|
|
# output_file_path: console,checkov-results.sarif
|