Skip to content

GregoireF/iac

Repository files navigation

iac

Agency-level Infrastructure as Code — GitHub automation, secrets management, and AI-powered workflows, fully managed with OpenTofu.

OpenTofu Plan Security Scan Unit Tests OpenSSF Scorecard License: MIT OpenTofu


Stack

Layer Tool Purpose
IaC engine OpenTofu >= 1.9 Plan + apply all infrastructure
Remote backend HCP Terraform Remote execution, encrypted state, workspace secrets
Auth GitHub App (ephemeral tokens) Scoped GitHub API access — no long-lived PATs
Secrets Doppler Project-level secrets synced across all repos
CI/CD GitHub Actions Automated plan on PR, apply on merge
AI GitHub Models API (free, GITHUB_TOKEN) PR review, issue triage, changelog generation
Scanning Trivy + Checkov + OSSF Scorecard IaC misconfig, policy enforcement, supply-chain scoring
Testing tofu test (mock) + Terratest (integration) Unit + real-API coverage

Repository structure

.
├── .devcontainer/                  # VS Code dev container (ready to use)
├── .github/
│   ├── CODEOWNERS
│   ├── SECURITY.md
│   ├── pull_request_template.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   └── feature.yml
│   ├── dependabot.yml              # Weekly dependency updates
│   ├── copilot-instructions.md     # GitHub Copilot context
│   ├── scripts/                    # Python automation (stdlib only — no pip)
│   │   ├── github_models.py        # Shared Models API + GitHub REST client
│   │   ├── ai_issue_triage.py      # Labels + triage comment on issue open
│   │   ├── ai_pr_review.py         # Code review on IaC PRs
│   │   └── ai_changelog.py         # Release notes on tag creation
│   └── workflows/
│       ├── _tofu-plan.yml          # Reusable: fmt-check → init → validate → plan → PR comment
│       ├── _tofu-apply.yml         # Reusable: init → apply -auto-approve
│       ├── tofu-github-plan.yml    # Caller: github stack — plan on PR
│       ├── tofu-github-apply.yml   # Caller: github stack — apply on merge
│       ├── tofu-doppler-plan.yml   # Caller: doppler stack — plan on PR
│       ├── tofu-doppler-apply.yml  # Caller: doppler stack — apply on merge
│       ├── tofu-test.yml           # tofu test (mock_provider) on module changes
│       ├── integration-test.yml    # Terratest weekly (real GitHub API)
│       ├── security-scan.yml       # Trivy + Checkov → GitHub Security tab
│       ├── scorecard.yml           # OSSF Scorecard weekly
│       ├── ai-issue-triage.yml     # AI: triage on issue open
│       ├── ai-pr-review.yml        # AI: code review on IaC PR
│       ├── ai-changelog.yml        # AI: release notes on tag
│       ├── auto-merge.yml          # Dependabot patch/minor auto-merge
│       ├── docs.yml                # terraform-docs enforce (PR) + auto-commit (main)
│       ├── stale.yml               # Close stale issues/PRs
│       └── release.yml             # Manual semver bump + GitHub release
│
├── modules/
│   └── github/
│       └── repository/             # Module: repo + branch ruleset + environments + deploy keys
│           ├── main.tf
│           ├── variables.tf
│           ├── outputs.tf
│           ├── README.md           # Auto-generated by terraform-docs
│           ├── templates/          # Files injected into managed repos
│           └── tests/
│               └── defaults.tftest.hcl   # 11 mock_provider unit tests
│
├── scripts/
│   └── new-stack.sh                # Generator: scaffold a new provider stack + 2 GHA workflows
│
├── tests/
│   ├── go.mod
│   └── github_repository_test.go  # Terratest integration tests (real API)
│
└── terraform/
    ├── github/                     # Stack: repos, labels, keys, files, Actions vars
    └── doppler/                    # Stack: Doppler projects, configs, service tokens

CI/CD pipeline

PR open / update
  ├── tofu-github-plan      → fmt-check + init + validate + plan → comment on PR
  ├── tofu-test             → mock_provider unit tests (modules/**/tests/)
  ├── ai-pr-review          → AI review on terraform/** + modules/** changes
  └── security-scan         → Trivy + Checkov → GitHub Security tab

Merge to main
  ├── tofu-github-apply     → tofu apply -auto-approve
  └── tofu-doppler-apply    → tofu apply -auto-approve

Weekly (scheduled)
  ├── scorecard             → OSSF Scorecard → Security tab
  ├── integration-test      → Terratest real-API tests (Sunday 02:00 UTC)
  └── stale                 → Mark/close inactive issues + PRs (Monday 08:00 UTC)

Issue opened            → ai-issue-triage  (labels + comment)
Release created         → ai-changelog     (AI-written release notes)
Dependabot PR opened    → auto-merge       (patch/minor merged, major labelled priority:high)

Manual (workflow_dispatch)
  └── release             → semver bump → create GitHub release

Local setup

Prerequisites

Tool Install Purpose
mise curl https://mise.run | sh Go + Python version manager
tenv brew install tofuutils/tap/tenv OpenTofu version manager
just brew install just Task runner (see justfile)
pre-commit pip install pre-commit Git hooks (fmt, validate, scan)
Trivy brew install trivy IaC misconfiguration scanner
Checkov pip install checkov Policy-as-code scanner
tflint brew install tflint HCL linter
terraform-docs brew install terraform-docs Module documentation

One-shot setup

mise install          # Install Go 1.23 + Python 3.12 (reads .mise.toml)
just setup            # Install OpenTofu, pre-commit hooks, Go deps

Common tasks

just fmt              # Format all HCL in-place
just validate         # Validate all stacks
just lint             # tflint on all stacks
just test             # mock_provider unit tests (no API calls)
just security         # Trivy + Checkov scans
just check            # All of the above — mirrors CI

just plan github      # Plan the github stack
just plan doppler     # Plan the doppler stack
just apply github     # Apply (prompts for confirmation)

just new-stack stripe # Scaffold a new provider stack
just docs             # Regenerate terraform-docs for all modules
just version          # Show all tool versions

just test-integration               # Run Terratest (creates real GitHub resources)
just test-integration TestMyTest    # Run a specific Terratest function

Bootstrap

1. Create a GitHub App

GitHub → Settings → Developer settings → GitHub Apps → New GitHub App

Permission Access Required for
Administration Read & Write Create + configure repositories
Contents Read & Write Inject .github/ files into repos
Issues Read & Write AI triage labels + comments
Metadata Read Repository metadata
Pull requests Read & Write Plan comments, auto-merge
Workflows Read & Write Repository Actions variables

After creation:

  1. Note the App ID
  2. Generate a private key → download .pem file
  3. Install the App on your GitHub account → note the Installation ID

2. Create HCP Terraform organization + workspaces

  1. Sign in at app.terraform.io
  2. Create an organization and replace "YOUR_TFC_ORG" in both terraform/*/terraform.tf files
  3. Create two workspaces (execution mode: Remote):

Workspace github

Variable Type Sensitive Value
github_owner Terraform No Your GitHub username
github_app_id Terraform Yes App ID from step 1
github_app_installation_id Terraform Yes Installation ID from step 1
github_app_pem_file Terraform Yes Full contents of .pem file

Workspace doppler

Variable Type Sensitive Value
doppler_token Terraform Yes Doppler service token (account scope)
  1. Create a Team API token → add as GitHub Actions secret TF_API_TOKEN
  2. Add the following GitHub Actions repository variable (Settings → Variables → Actions):
Variable Value Used by
TF_DOPPLER_WORKSPACE_ID HCP Terraform workspace ID for the doppler workspace (e.g. ws-xxxx) tofu-github-apply.yml pre-step that enables global remote state

3. First apply

just plan github      # Review the import of existing repos
just apply github     # Import + configure all repos

just plan doppler     # Preview Doppler project creation
just apply doppler    # Create Doppler projects + service tokens

After a successful apply, the import blocks in terraform/github/imports.tf can be removed.


Bumping the module version

terraform/github/repos.tf references the modules/github/repository module via a pinned git tag:

source = "git::https://github.com/GregoireF/iac.git//modules/github/repository?ref=v1.0.0&depth=1"

After any change to modules/github/repository/:

  1. Merge the module change to main
  2. Run Actions → release → Run workflow — choose patch, minor, or major
  3. The workflow creates a new tag (e.g. v1.1.0) and a GitHub Release
  4. Open a PR to update the ref= in terraform/github/repos.tf to the new tag

Adding a new GitHub repo

Edit terraform/github/locals.tf and add an entry to local.repositories:

my-new-repo = {
  description            = "My new repository."
  topics                 = ["example"]
  visibility             = "public"
  has_issues             = true
  has_wiki               = false
  has_projects           = false
  allow_merge_commit     = false
  allow_squash_merge     = true
  allow_rebase_merge     = false
  delete_branch_on_merge = true
  archived               = false
  allow_auto_merge       = true
  inject_standard_files  = true

  branch_protection = {
    enabled                      = true
    required_status_checks       = []
    enforce_conventional_commits = true
  }
}

Open a PR → plan comment posted automatically → merge → repo created.


Adding a new provider stack

just new-stack cloudflare

This creates:

  • terraform/cloudflare/ with terraform.tf, providers.tf, variables.tf, locals.tf, outputs.tf
  • .github/workflows/tofu-cloudflare-plan.yml
  • .github/workflows/tofu-cloudflare-apply.yml

Then:

  1. Edit terraform/cloudflare/terraform.tf — add required_providers + set TFC org name
  2. Edit terraform/cloudflare/providers.tf — configure provider
  3. Create HCP Terraform workspace cloudflare + set variables
  4. Open a PR → automated plan → merge → apply

Testing

Unit tests (mock_provider — zero API calls)

just test
# runs tofu init -backend=false && tofu test for every module with a tests/ directory

Integration tests (real GitHub API — creates + destroys test repos)

export GITHUB_TOKEN=<pat-with-repo-scope>
export GITHUB_OWNER=<your-github-username>
just test-integration

These run on a weekly schedule in CI (Sunday 02:00 UTC). They create temporary repos with a random suffix and destroy them with defer terraform.Destroy().


Security model

Control Detail
No long-lived credentials GitHub App generates ephemeral tokens per run
Secrets never in code GitHub App PEM + all secrets live in HCP Terraform workspace variables
CI gets minimum access Workflows receive GITHUB_TOKEN only; TF_API_TOKEN scoped to HCP Terraform
Doppler Non-sensitive workspace config via service tokens; secrets stay encrypted in Doppler
Pre-commit gates detect-private-key, tofu fmt, Trivy, Checkov run before every push
PR gates tofu validate, tofu test, security scan on every PR
OSSF Scorecard Weekly scoring pushed to GitHub Security tab
Branch protection Conventional commits enforced via ruleset; no force-push to main
Dependabot Patch/minor auto-merged; major gets priority:high and blocks merge

AI integrations

All AI features use the GitHub Models API with the built-in GITHUB_TOKEN — no external API key, no cost.

Workflow Trigger Model What it does
ai-issue-triage Issue opened gpt-4o Suggests labels + posts triage comment
ai-pr-review PR on terraform/** gpt-4o Reviews diff for security, best practices, correctness
ai-changelog Release created gpt-4o-mini Writes structured release notes from commits

Scripts are in .github/scripts/ and use Python stdlib only — no pip install, no container.


Why these tools?

Decision Rationale
OpenTofu over Terraform MPL-2 licence (BSL-free), mock_provider in tests, community momentum
HCP Terraform over self-hosted state Remote execution, encrypted state, free tier sufficient
GitHub App over PAT Ephemeral tokens, scoped permissions, no expiry surprises
Doppler over SOPS/Vault Free tier, native GitHub Actions sync, zero infra to manage
GitHub Models over OpenAI Free with GITHUB_TOKEN, no external key, sufficient quality
justfile over Makefile POSIX-safe, better syntax, self-documenting (just --list)
mise over asdf/pyenv Unified Go + Python version management, faster, .mise.toml is clean
Dependabot over Renovate Zero config, free, sufficient for 2 stacks; Renovate is a future option for better grouping

Security

Security vulnerabilities should be reported according to the security policy.

Supply-chain security is continuously monitored via OSSF Scorecard. Results are published to the GitHub Security tab after every push to main.


License

MIT — Copyright © 2026 GregoireF

Requirements

No requirements.

Providers

No providers.

Modules

No modules.

Resources

No resources.

Inputs

No inputs.

Outputs

No outputs.

About

Infrastructure as Code — experiments, modules and production-ready patterns.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Contributors