Infrastructure as Code (IaC) for managing GitHub repositories in this organization.
This repository uses OpenTofu as the IaC engine to manage GitHub repository settings declaratively. Repository configurations are stored as YAML files, a script generates the IaC variable file, and GitHub Actions CI/CD pipelines validate and apply changes automatically.
.
├── .devcontainer/ # Dev Container configuration (VS Code / GitHub Codespaces)
│ └── devcontainer.json
├── .github/
│ └── workflows/
│ ├── ci.yml # CI: schema validation + consolidated IaC checks and plan
│ └── cd.yml # CD: apply on CI success for main (or manual debug trigger)
├── config/ # One YAML file per managed GitHub repository
│ ├── github-config.yaml
│ └── example-service.yaml
├── schemas/
│ └── repository.schema.json # JSON Schema (draft-07) for config/ YAML files
├── scripts/
│ ├── generate_tfvars.py # Generates src/repositories.auto.tfvars from config/
│ └── validate_config.py # Validates config/ YAML files against the schema
├── sonar-project.properties # SonarCloud project configuration
├── src/ # Root IaC configuration
│ ├── main.tf # Calls github_repository module for every repository
│ ├── variables.tf # Input variable definitions
│ ├── outputs.tf # Output definitions
│ ├── terraform.tf # Provider + backend configuration
│ └── modules/
│ └── github_repository/ # Reusable module: manages one GitHub repository
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── test/ # IaC native tests (.tftest.hcl)
├── github_repository.tftest.hcl
└── root_module.tftest.hcl
- OpenTofu >= 1.6
- Python >= 3.10 with
pyyamlandjsonschemainstalled (pip install pyyaml jsonschema) - pre-commit installed (
pip install pre-commit) - A GitHub token with repository administration permissions for local plan/apply operations
Install and enable Git hooks locally:
pre-commit installRun all hooks manually:
pre-commit run --all-filesRepository config files in config/ are validated against schemas/repository.schema.json
(JSON Schema draft-07). Validation is blocking in CI and also runs automatically via
pre-commit on every staged config/*.yaml change.
python scripts/validate_config.pyTo also produce a Sonar-compatible generic issue report:
python scripts/validate_config.py --report reports/sonar-issues.jsonNote: files named example*.yaml are ignored by scripts/generate_tfvars.py and are treated as examples only.
All options:
| Option | Default | Description |
|---|---|---|
--config-dir DIR |
config |
Directory with YAML config files |
--schema FILE |
schemas/repository.schema.json |
Path to JSON Schema file |
--report FILE |
(none) | Write a Sonar-compatible generic issue report to FILE |
Each error line shows the YAML field path and a description, for example:
[FAIL] config/my-repo.yaml
- visibility: 'staging' is not one of ['public', 'private', 'internal']
- branch_protection.required_approving_review_count: 10 is greater than the maximum of 6
Fix the flagged fields in the YAML file to match the allowed values described in the YAML Config Reference below.
The schema version is noted in the description field of schemas/repository.schema.json.
Bump the version string whenever the schema gains new constraints or breaking changes.
SonarCloud is configured via sonar-project.properties. The CI yaml-validate job
produces reports/sonar-issues.json (Sonar Generic Issue Data format) and then runs
the SonarCloud scan, which picks up both Python analysis of scripts/ and the external
YAML validation issues.
To enable the SonarCloud scan, add a SONAR_TOKEN secret to the repository
(Settings → Secrets and variables → Actions).
This repository follows:
- Keep a Changelog in CHANGELOG.md
- Semantic Versioning
- Conventional Commit messages for automatic semantic bump detection
Version bump rules:
feat:-> minorfeat!:orBREAKING CHANGE:-> majorfix:-> patch
Release flow on main:
- CI succeeds.
- Release workflow calculates next version with GitVersion.
- Workflow validates that
CHANGELOG.mdcontains## [x.y.z]for that version. - Workflow creates and pushes tag
vx.y.z. - Workflow publishes a GitHub Release using the matching changelog section.
When preparing a release, move relevant entries from ## Unreleased to a new dated version section in CHANGELOG.md.
- CI (
.github/workflows/ci.yml):- Validates YAML config schema.
- Runs
generate-tfvarsto generate the tfvars artifact and performtofu fmt -check -recursive. - Runs
iacfor init/validate, tests, and plan. - Uploads approved plan artifact as
tfplan-<sha>when plan succeeds.
- CD (
.github/workflows/cd.yml):- Auto-triggered by successful CI on
main(workflow_run). - Can be started manually (
workflow_dispatch) for debugging from other branches. - Manual trigger requires:
ci_run_id: CI run ID that produced the plan artifact.- Start the workflow from the same commit as the selected CI run (
head_sha).
- Optional manual input:
plan_artifact_name: artifact name. Usually omit this and let the workflow derive the default from the CI run'shead_sha(typicallytfplan-<sha>).
- Auto-triggered by successful CI on
- Create or edit a YAML file in
config/(one file per repository):
# config/my-new-repo.yaml
name: my-new-repo
description: "My new repository."
visibility: private # public | private | internal
auto_init: true
import_id: true # optional: set true when repo already exists and must be imported
topics:
- my-topic
branch_protection:
pattern: main
require_signed_commits: false
required_pull_request_reviews:
required_approving_review_count: 1- Regenerate the tfvars file:
python scripts/generate_tfvars.py- Preview the changes:
cd src
tofu init
tofu plan-
Open a Pull Request — CI will run
tofu fmt,validate, tests, andplanautomatically. -
Merge the PR — CD applies the changes to GitHub.
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | filename | Repository name (defaults to the YAML filename without extension) |
description |
string | "" |
Short description |
visibility |
string | "private" |
public, private, or internal |
is_template |
bool | false |
Mark as a template repository |
auto_init |
bool | true |
Initialize with a README |
gitignore_template |
string | null |
e.g. "Terraform", "Python" |
license_template |
string | null |
e.g. "mit", "apache-2.0" |
topics |
list | [] |
Repository topics |
archived |
bool | false |
Archive (make read-only) the repository |
import_id |
bool | null |
Set to true to import an existing repo into OpenTofu state before apply |
branch_protection |
object | null |
See below |
| Field | Type | Default | Description |
|---|---|---|---|
pattern |
string | "main" |
Branch name pattern |
enforce_admins |
bool | false |
Enforce rules for admins |
require_signed_commits |
bool | true |
Require GPG-signed commits |
required_status_checks.strict |
bool | true |
Require up-to-date branch |
required_status_checks.contexts |
list | [] |
Required CI check names |
required_pull_request_reviews.dismiss_stale_reviews |
bool | true |
Dismiss stale approvals on new commits |
required_pull_request_reviews.require_code_owner_reviews |
bool | false |
Require CODEOWNERS review |
required_pull_request_reviews.required_approving_review_count |
number | 1 |
Minimum approvals required |
Tests use the IaC built-in test framework (OpenTofu >= 1.6 required):
cd src
tofu init -backend=false
cp ../test/*.tftest.hcl .
tofu test| Name | Type | Description |
|---|---|---|
GH_APP_ID |
Secret | GitHub App ID used to mint short-lived installation tokens |
GH_APP_PRIVATE_KEY |
Secret | GitHub App private key (PEM) used by actions/create-github-app-token |
Open the repository in VS Code and choose Reopen in Container (or use GitHub Codespaces). The dev container includes:
- IaC tooling (OpenTofu) + TFLint
- Python 3.12 + PyYAML + jsonschema + Ruff
- GitHub CLI
- VS Code extensions: HashiCorp Terraform, Python, Ruff, YAML, GitHub Actions, GitLens