This is a part of my guide to how I manage multiple GitHub repositories. This post focuses on configuring repository settings on GitHub using Terraform. Back to main guide.
Repository Management with Terraform
I use Terraform to declaratively configure:
- Repo metadata: description, topics, features
- Branch settings: default branch and protection rules
- GitHub Actions secrets: shared credentials for NuGet and Docker
Example Configuration
Github.tf
defines:
locals {
github_owner = "LordMike"
github_token = "REPLACE_ME"
repositories = jsondecode(file("repos.json")).repositories
repos = keys(local.repositories)
repos_public = [for r in local.repos : r if lookup(local.repositories[r], "public", true)]
nuget_repos = [for r in local.repos : r if lookup(local.repositories[r], "nuget", true)]
docker_repos = [for r in local.repos : r if lookup(local.repositories[r], "docker", false)]
docker_username = "lordmike"
docker_key = "REPLACE_ME"
nuget_key = "REPLACE_ME"
}
provider "github" {
owner = local.github_owner
token = local.github_token
}
resource "github_repository" "repository" {
for_each = toset(local.repos)
name = split("/", each.key)[1]
description = lookup(local.repositories[each.key], "description", "")
topics = lookup(local.repositories[each.key], "topics", null)
has_issues = lookup(local.repositories[each.key], "has_issues", true)
has_wiki = lookup(local.repositories[each.key], "has_wiki", false)
has_projects = lookup(local.repositories[each.key], "has_projects", false)
has_downloads = lookup(local.repositories[each.key], "has_downloads", false)
delete_branch_on_merge = lookup(local.repositories[each.key], "delete_branch_on_merge", true)
}
resource "github_branch_default" "default_branch" {
for_each = toset(local.repos)
repository = split("/", each.key)[1]
branch = "master"
}
resource "github_branch_protection" "protect_master" {
for_each = toset(local.repos_public)
repository_id = split("/", each.key)[1]
pattern = "master"
enforce_admins = false
allows_deletions = false
allows_force_pushes = false
}
resource "github_actions_secret" "nuget_key" {
for_each = toset(local.nuget_repos)
repository = split("/", each.key)[1]
secret_name = "NUGET_KEY"
plaintext_value = local.nuget_key
}
resource "github_actions_secret" "docker_username" {
for_each = toset(local.docker_repos)
repository = split("/", each.key)[1]
secret_name = "DOCKER_USERNAME"
plaintext_value = local.docker_username
}
resource "github_actions_secret" "docker_key" {
for_each = toset(local.docker_repos)
repository = split("/", each.key)[1]
secret_name = "DOCKER_KEY"
plaintext_value = local.docker_key
}
Tips
- Use
for_each
and filtered locals (e.g.docker_repos
) to simplify resource declarations - Use
lookup
to define defaults and override per-repo settings
Terraform Imports (for existing repos)
Use terraform import
with escaped resource keys:
terraform import 'github_repository.repository["LordMike/MBW.Utilities.ReflectedCast"]' LordMike/MBW.Utilities.ReflectedCast
terraform import 'github_branch_default.default_branch["LordMike/MBW.Utilities.ReflectedCast"]' LordMike/MBW.Utilities.ReflectedCast
Apply Changes
terraform refresh # Fetch GitHub state
terraform apply # Preview and apply
terraform apply -refresh=false # Apply without refreshing state
This setup allows me to declaratively manage all my GitHub repos at scale.