Manage GitHub repositories at scale from a single tfvars file. Define named presets once, apply them across repositories, and override individual fields per repo. The module handles branch protection rulesets, permissions, environments, secrets, webhooks, and naming conventions.
For single-repository management, use the modules/repository submodule directly.
module "governance" {
source = "path/to/module"
organization = "my-org"
repository_naming = "myorg-%s" # Creates: myorg-api-service, myorg-worker, etc.
presets = {
service = {
protected_branches = ["main"]
required_approvals = 2
required_checks = ["ci", "security-scan"]
allow_bypass = ["org-admin"]
}
}
repositories = {
api-service = {
preset = "service"
description = "Main API"
topics = ["api", "backend"]
}
worker = {
preset = "service"
description = "Background worker"
}
}
}Presets are named bundles of defaults. Every field is optional — unset fields fall back to the module's built-in defaults (private visibility, main branch, 1 approval required, force push blocked).
presets = {
default = {} # always present; override base defaults here
production = {
protected_branches = ["main", "release/*"]
required_approvals = 2
required_checks = ["ci", "security-scan"]
allow_bypass = ["org-admin", "team:sre"]
delete_branch_on_merge = true
}
library = {
visibility = "public"
protected_branches = ["main"]
required_checks = ["test", "lint"]
has_wiki = false
}
}A repository inherits its preset, then applies per-repo overrides on top:
repositories = {
critical-api = {
preset = "production"
required_approvals = 3 # override: bump from 2 to 3
topics = ["critical"]
}
}The map key is Terraform's stable identifier. Add a name field to change the GitHub name without destroying the resource:
repositories = {
auth-service = { # key never changes
name = "auth-svc-v2" # GitHub name can change freely
preset = "production"
}
}Environments with optional required reviewers, secrets, and variables:
environments = {
production = {
required_approvers = ["team:sre", "user:alice"]
secrets = { API_KEY = "prod-value" }
variables = { ENV = "production" }
}
staging = {
variables = { ENV = "staging" }
}
}Reviewers must have at least push permission. The module auto-elevates any reviewer who doesn't — set a higher role explicitly in permissions if needed.
permissions = {
"team:engineers" = "push"
"team:external" = "pull"
"user:alice" = "admin"
}
allow_bypass = ["org-admin", "role:maintain", "team:sre", "app:renovate"]Valid bypass formats: org-admin · role:maintain|write|admin · team:<slug> · app:<slug>
workspace = "platform" # stored as a custom property on every repo
repositories = {
api = {
properties = {
cost_center = "engineering"
tier = "production"
}
}
}terraform test # all tests (governance + submodule)
terraform test -filter=tests/governance.tftest.hcl27 tests cover: preset application and overrides, naming patterns, renaming, merge strategies, feature toggles, security settings, environments, workspace injection.
examples/simple/— basic preset usageexamples/complete/— overrides, renaming, templates, environments
| Name | Version |
|---|---|
| terraform | >= 1.7 |
| github | >= 6.8.0 |
| Name | Version |
|---|---|
| github | 6.11.1 |
| Name | Source | Version |
|---|---|---|
| repositories | ./modules/repository | n/a |
| Name | Type |
|---|---|
| github_app.bypass_apps | data source |
| github_organization_repository_roles.all | data source |
| github_organization_teams.all | data source |
| github_user.referenced_users | data source |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| github_app_ids | Optional map of app slug -> app installation ID. If empty, fetches apps individually via data source. Used for branch protection bypass actors. | map(number) |
{} |
no |
| github_team_ids | Optional map of team slug -> team ID. If empty, fetches all organization teams via data source. Used for branch protection bypass actors and environment reviewers. | map(number) |
{} |
no |
| github_user_ids | Optional map of user login -> numeric user ID. If empty, repository module resolves IDs individually via data source (less efficient). Used for environment reviewers. Cannot be auto-fetched org-wide as GitHub API returns node IDs (strings) not numeric IDs. | map(number) |
{} |
no |
| organization | GitHub organization name where repositories will be created. | string |
n/a | yes |
| presets | Preset configurations map. Select with repositories[*].preset; falls back to 'default'. | map(object({ |
{ |
no |
| repositories | Map of repositories to create. The map key is a stable identifier (won't trigger recreation). Use 'name' field to rename repositories safely. | map(object({ |
{} |
no |
| repository_naming | sprintf-style format string for repository names. Use a single '%s' placeholder for the repository key. Example: '%s' (no prefix), 'myorg-%s' (with prefix). | string |
"%s" |
no |
| workspace | Optional workspace/namespace name for logical grouping of repositories. If provided, will be stored as a custom property on each repository. | string |
null |
no |
| Name | Description |
|---|---|
| organization | The GitHub organization name. |
| repositories | Map of repository keys to their full output details from the repository module. |
| repository_names | Map of repository keys to their GitHub names. |
| repository_urls | Map of repository keys to their HTML URLs. |
| workspace | The workspace name applied to all repositories. |