autojanet/skills/terrashark/references/ci-delivery-patterns.md
Zoë cfec11bb46
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
fix: convert skill submodules to plain directories
stop-slop, taste-skill, terrashark had embedded .git dirs causing
Woodpecker clone to fail on submodule update.
2026-05-30 15:44:44 -07:00

5 KiB

CI Delivery Patterns

Use this guide to implement auditable Terraform/OpenTofu delivery pipelines.

Delivery principles

  • plan and apply are separate concerns
  • apply must consume reviewed plan artifact when architecture permits
  • policy and security checks run on every apply path
  • production applies require environment protection and approvals

Baseline stages

  1. fmt + validate
  2. lint + security scan
  3. plan creation
  4. policy checks against plan JSON
  5. approval gate
  6. apply from trusted branch/runner
  7. post-apply drift and evidence capture

GitHub Actions (production-oriented template)

name: terraform-delivery

on:
  pull_request:
    paths:
      - '**/*.tf'
      - '**/*.tfvars'
  workflow_dispatch:
  push:
    branches: [main]
    paths:
      - '**/*.tf'
      - '**/*.tfvars'

permissions:
  contents: read
  id-token: write
  pull-requests: write

concurrency:
  group: terraform-${{ github.ref }}
  cancel-in-progress: false

env:
  TF_IN_AUTOMATION: "true"

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform fmt -check
      - run: terraform init -backend=false
      - run: terraform validate

  plan:
    if: github.event_name == 'pull_request'
    needs: [validate]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
      - run: terraform plan -out=plan.bin
      - run: terraform show -json plan.bin > plan.json
      - run: conftest test plan.json --policy policy/
      - uses: actions/upload-artifact@v4
        with:
          name: reviewed-plan
          path: |
            plan.bin
            plan.json            

  apply:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    needs: [validate]
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
      - run: terraform plan -out=plan.bin
      - run: terraform apply -auto-approve plan.bin

Notes:

  • configure provider auth with OIDC (avoid static cloud keys)
  • if you require strict “apply reviewed PR plan” semantics, keep plan/apply in same workflow run or externalize signed plan storage

GitLab CI (production-oriented template)

stages:
  - validate
  - plan
  - policy
  - apply
  - verify

variables:
  TF_IN_AUTOMATION: "true"

validate:
  stage: validate
  image: hashicorp/terraform:1.7
  script:
    - terraform fmt -check
    - terraform init -backend=false
    - terraform validate

plan:
  stage: plan
  image: hashicorp/terraform:1.7
  script:
    - terraform init
    - terraform plan -out=plan.bin
    - terraform show -json plan.bin > plan.json
  artifacts:
    paths: [plan.bin, plan.json]
    expire_in: 24h

policy:
  stage: policy
  image: openpolicyagent/conftest:latest
  dependencies: [plan]
  script:
    - conftest test plan.json --policy policy/

apply:
  stage: apply
  image: hashicorp/terraform:1.7
  dependencies: [plan]
  when: manual
  allow_failure: false
  script:
    - terraform init
    - terraform apply -auto-approve plan.bin

verify:
  stage: verify
  image: hashicorp/terraform:1.7
  script:
    - terraform plan -detailed-exitcode

Pipeline hardening checklist

  • enforce branch protection on default branch
  • require CODEOWNERS review for prod-impacting paths
  • restrict apply jobs to protected runners
  • set artifact retention + access policies
  • preserve audit trail (approver, actor, commit, runtime version)

Cost and speed controls

  • run expensive integration suites only for IaC path changes
  • serialize shared-foundation applies
  • use provider plugin cache where supported
  • schedule cleanup for ephemeral test environments

Atlantis (PR-driven delivery template)

Use Atlantis when you want chat-driven, PR-scoped plan/apply with locking.

Example atlantis.yaml:

version: 3
projects:
  - name: platform
    dir: .
    workspace: default
    autoplan:
      enabled: true
      when_modified: ["**/*.tf", "**/*.tfvars"]
    workflow: default

workflows:
  default:
    plan:
      steps:
        - init
        - plan
    apply:
      steps:
        - apply

Hardening notes:

  • restrict apply to approved PRs and protected branches
  • enable Atlantis server-side locking
  • use custom workflows to add policy checks and cost steps
  • keep CI auth in OIDC where supported; avoid static secrets

Infracost (cost visibility template)

Use Infracost to surface cost deltas from plan JSON in PRs.

Pattern:

  1. run plan and export plan JSON
  2. generate Infracost breakdown
  3. publish result as PR comment or artifact

Example commands:

terraform plan -out=plan.bin
terraform show -json plan.bin > plan.json
infracost breakdown --path plan.json --format json --out-file infracost.json

Notes:

  • store plan.json and infracost.json as artifacts for auditability
  • treat cost checks like policy checks for high-risk environments