Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ pins that tag, so this file is the human-readable answer to "what's in v0.2.0?".
- **Fixed** — bug fixes.
- **Removed** — removed features (call out breaking changes loudly).

## [Unreleased]

### Changed
- `github` — added per-repo `delete_branch_on_merge` (auto-deletes the head branch on merge,
default `true`) and an org-wide default-team grant: `default_team` (default `engineers`)
is granted `default_team_permission` (default `push`) on every managed repo via
`github_team_repository`; set `default_team = ""` to opt out. Adds a `team_grants` output.
The team grant requires `GITHUB_TOKEN` with `admin:org`.

## [0.3.0] - 2026-06-08

### Added
Expand Down
37 changes: 25 additions & 12 deletions github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,38 @@ it cannot run on the credential-free `TG_BACKEND=local` path. Because GitHub rep

Per entry in `repositories`:

- `github_repository` — the repo (visibility, description, topics, `has_issues`, `auto_init`).
- `github_repository` — the repo (visibility, description, topics, `has_issues`, `auto_init`,
`delete_branch_on_merge`). Head branches are auto-deleted on merge by default.
- `github_branch_default` — sets the default branch.
- `github_branch_protection` — only when the entry includes a `branch_protection` block
(required reviews, optional required status checks, enforce-admins).

Org-wide (one grant per repo, not configured per entry):

- `github_team_repository` — grants `default_team` access to **every** managed repo at
`default_team_permission`. Skipped entirely when `default_team` is `""`. The team must
already exist in the org — this component grants access, it does not create teams.

## Auth

The provider reads `GITHUB_TOKEN` from the environment — a PAT or GitHub App token with at
least `repo` scope (`admin:org` if you manage org-internal settings). **No token is stored in
this module or in git.** Export it before running:
least `repo` scope. **`admin:org` is required** to manage the `default_team` grant (and any
org-internal settings); without it `plan` succeeds but `apply` fails on the team grant. **No
token is stored in this module or in git.** Export it before running:

```bash
export GITHUB_TOKEN=ghp_...
```

## Inputs

| Name | Type | Default | Description |
| -------------- | ------------- | -------------- | ------------------------------------------------------------------ |
| `global` | object | — | Env-wide context. Accepted for convention only; **unused** here. |
| `github_owner` | string | `officialdad` | The GitHub org (or user) the provider operates on. |
| `repositories` | map(object) | `{}` | Repositories to manage, keyed by repo name (see shape below). |
| Name | Type | Default | Description |
| ------------------------- | ----------- | ------------- | ------------------------------------------------------------------ |
| `global` | object | — | Env-wide context. Accepted for convention only; **unused** here. |
| `github_owner` | string | `officialdad` | The GitHub org (or user) the provider operates on. |
| `default_team` | string | `engineers` | Team slug granted to every repo by default; `""` disables. |
| `default_team_permission` | string | `push` | Default team's access: `pull`/`triage`/`push`/`maintain`/`admin`. |
| `repositories` | map(object) | `{}` | Repositories to manage, keyed by repo name (see shape below). |

### `repositories` entry shape

Expand All @@ -48,6 +58,8 @@ repositories = {
default_branch = "main"
has_issues = true

delete_branch_on_merge = true # auto-delete head branch on merge (default true)

# Omit this block entirely to leave the branch unprotected.
branch_protection = {
required_approving_review_count = 1
Expand All @@ -60,10 +72,11 @@ repositories = {

## Outputs

| Name | Description |
| ------------------ | -------------------------------------------- |
| `repository_names` | Map of key → full name (`owner/repo`). |
| `repository_urls` | Map of key → HTML URL. |
| Name | Description |
| ------------------ | ----------------------------------------------------- |
| `repository_names` | Map of key → full name (`owner/repo`). |
| `repository_urls` | Map of key → HTML URL. |
| `team_grants` | Map of key → `"team:permission"` from `default_team`. |

## Managing repos that already exist

Expand Down
22 changes: 17 additions & 5 deletions github/terraform/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ provider "github" {
resource "github_repository" "this" {
for_each = var.repositories

name = each.key
description = each.value.description
visibility = each.value.visibility
topics = each.value.topics
has_issues = each.value.has_issues
name = each.key
description = each.value.description
visibility = each.value.visibility
topics = each.value.topics
has_issues = each.value.has_issues
delete_branch_on_merge = each.value.delete_branch_on_merge

# Give a newly-created repo an initial commit + default branch so the branch_default and
# branch_protection resources below have something to point at. No-op on imported repos.
Expand Down Expand Up @@ -55,3 +56,14 @@ resource "github_branch_protection" "this" {
}
}
}

# Grants the org-wide default team access to every managed repo. Fanned out over the same
# repositories map. The whole block is skipped when var.default_team is empty, so the
# component still works in orgs that don't use a default team.
resource "github_team_repository" "default" {
for_each = var.default_team == "" ? {} : var.repositories

team_id = var.default_team
repository = github_repository.this[each.key].name
permission = var.default_team_permission
}
5 changes: 5 additions & 0 deletions github/terraform/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ output "repository_urls" {
value = { for k, r in github_repository.this : k => r.html_url }
description = "Map of repository key -> HTML URL."
}

output "team_grants" {
value = { for k, g in github_team_repository.default : k => "${var.default_team}:${g.permission}" }
description = "Map of repository key -> \"team:permission\" granted by the default team."
}
28 changes: 23 additions & 5 deletions github/terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,31 @@ variable "github_owner" {
default = "officialdad"
}

variable "default_team" {
type = string
description = "Team slug granted access to every managed repo by default. Empty string disables the default grant. The team must already exist in the org."
default = "engineers"
}

variable "default_team_permission" {
type = string
description = "Permission the default team receives on each repo."
default = "push"

validation {
condition = contains(["pull", "triage", "push", "maintain", "admin"], var.default_team_permission)
error_message = "default_team_permission must be one of: pull, triage, push, maintain, admin."
}
}

variable "repositories" {
type = map(object({
description = optional(string, "")
visibility = optional(string, "private")
topics = optional(list(string), [])
default_branch = optional(string, "main")
has_issues = optional(bool, true)
description = optional(string, "")
visibility = optional(string, "private")
topics = optional(list(string), [])
default_branch = optional(string, "main")
has_issues = optional(bool, true)
delete_branch_on_merge = optional(bool, true)
branch_protection = optional(object({
required_approving_review_count = optional(number, 1)
required_status_checks = optional(list(string), [])
Expand Down