Skip to content

vmvarela/terraform-github-governance

Repository files navigation

GitHub Repository Governance Module

Release Terraform

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.

Quick Start

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

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"]
  }
}

Repository Options

Renaming without recreation

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

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 and bypass actors

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>

Custom properties and workspace

workspace = "platform"   # stored as a custom property on every repo

repositories = {
  api = {
    properties = {
      cost_center = "engineering"
      tier        = "production"
    }
  }
}

Testing

terraform test           # all tests (governance + submodule)
terraform test -filter=tests/governance.tftest.hcl

27 tests cover: preset application and overrides, naming patterns, renaming, merge strategies, feature toggles, security settings, environments, workspace injection.

Examples

Requirements

Name Version
terraform >= 1.7
github >= 6.8.0

Providers

Name Version
github 6.11.1

Modules

Name Source Version
repositories ./modules/repository n/a

Resources

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

Inputs

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({
description = optional(string)
visibility = optional(string)
default_branch = optional(string)
topics = optional(list(string), [])
properties = optional(map(string), {})
protected_branches = optional(list(string))
allow_bypass = optional(list(string), [])
required_approvals = optional(number)
required_checks = optional(list(string))
prevent_force_push = optional(bool)
prevent_branch_deletion = optional(bool)

# Merge settings
allow_merge_commit = optional(bool)
allow_squash_merge = optional(bool)
allow_rebase_merge = optional(bool)
allow_auto_merge = optional(bool)
delete_branch_on_merge = optional(bool)
allow_update_branch = optional(bool)
squash_merge_commit_title = optional(string)
squash_merge_commit_message = optional(string)
merge_commit_title = optional(string)
merge_commit_message = optional(string)

# Feature toggles
has_issues = optional(bool)
has_wiki = optional(bool)
has_projects = optional(bool)
has_discussions = optional(bool)

# Security
vulnerability_alerts = optional(bool)
web_commit_signoff_required = optional(bool)
}))
{
"default": {}
}
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({
# Optional preset to apply (defaults to "default")
preset = optional(string, "default")

# Optional explicit name (allows renaming without Terraform recreation)
name = optional(string)

# Core configuration (can override preset)
description = optional(string)
visibility = optional(string)
default_branch = optional(string)
topics = optional(list(string))
properties = optional(map(string))

# Template
is_template = optional(bool)
template = optional(object({
repository = string
include_all_branches = optional(bool, false)
}))

# Access & Permissions
permissions = optional(map(string))
deploy_keys = optional(map(object({
key = string
read_only = optional(bool, false)
})))
allowed_roles = optional(list(string))

# Automation (Global)
webhooks = optional(map(object({
url = string
events = list(string)
secret = optional(string)
})))
repository_secrets = optional(map(string))
repository_variables = optional(map(string))

# CI/CD Environments
environments = optional(map(object({
required_approvers = optional(list(string), [])
secrets = optional(map(string))
variables = optional(map(string))
})))

# Branch Protection (flattened overrides)
protected_branches = optional(list(string))
allow_bypass = optional(list(string))
required_approvals = optional(number)
required_checks = optional(list(string))
prevent_force_push = optional(bool)
prevent_branch_deletion = optional(bool)

# Merge settings (can override preset)
allow_merge_commit = optional(bool)
allow_squash_merge = optional(bool)
allow_rebase_merge = optional(bool)
allow_auto_merge = optional(bool)
delete_branch_on_merge = optional(bool)
allow_update_branch = optional(bool)
squash_merge_commit_title = optional(string)
squash_merge_commit_message = optional(string)
merge_commit_title = optional(string)
merge_commit_message = optional(string)

# Feature toggles (can override preset)
has_issues = optional(bool)
has_wiki = optional(bool)
has_projects = optional(bool)
has_discussions = optional(bool)

# Repository-only settings (not in presets)
archived = optional(bool)
homepage_url = optional(string)

# Security (can override preset)
vulnerability_alerts = optional(bool)
web_commit_signoff_required = optional(bool)
}))
{} 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

Outputs

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.

About

A comprehensive Terraform module for applying consistent governance to a GitHub organization and its repositories

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages