From 17983e3f02f870ec664a75230e0f9d19b97b87e2 Mon Sep 17 00:00:00 2001 From: Martin Bruzina Date: Thu, 3 Jul 2025 18:21:55 +0200 Subject: [PATCH 1/4] feat: add organization item, rename default props to repo defaults --- README.md | 29 +++++++++++++++-------------- terraform/main.tf | 22 +++++++++++----------- test.yaml | 7 ++++--- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index d63ad95..364808b 100644 --- a/README.md +++ b/README.md @@ -154,20 +154,21 @@ The GitHub organization YAML configuration post a Terraform plan as a pull reque ```yaml --- -default-properties: # OPTIONAL - # Global properties - visibility: public # OPTIONAL, DEFAULT public - # Global features - has_issues: true # OPTIONAL, DEFAULT false - has_discussions: true # OPTIONAL, DEFAULT false - has_projects: true # OPTIONAL, DEFAULT false - has_wiki: true # OPTIONAL, DEFAULT false - # Global settings - allow_merge_commit: false # OPTIONAL, DEFAULT true - allow_squash_merge: true # OPTIONAL, DEFAULT true - allow_rebase_merge: true # OPTIONAL, DEFAULT true - allow_auto_merge: true # OPTIONAL, DEFAULT false - delete_branch_on_merge: true # OPTIONAL, DEFAULT false +organization: + repository-defaults: # OPTIONAL + # Global properties + visibility: public # OPTIONAL, DEFAULT public + # Global features + has_issues: true # OPTIONAL, DEFAULT false + has_discussions: true # OPTIONAL, DEFAULT false + has_projects: true # OPTIONAL, DEFAULT false + has_wiki: true # OPTIONAL, DEFAULT false + # Global settings + allow_merge_commit: false # OPTIONAL, DEFAULT true + allow_squash_merge: true # OPTIONAL, DEFAULT true + allow_rebase_merge: true # OPTIONAL, DEFAULT true + allow_auto_merge: true # OPTIONAL, DEFAULT false + delete_branch_on_merge: true # OPTIONAL, DEFAULT false repositories: - name: repo-slug # Repository metadata diff --git a/terraform/main.tf b/terraform/main.tf index 6c83c7d..0354bd6 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -1,6 +1,6 @@ locals { config = yamldecode(file(var.path)) - default_properties = try(local.config.default-properties, null) + repository_defaults = try(local.config.organization.repository-defaults, null) repositories = local.config.repositories } @@ -14,19 +14,19 @@ resource "github_repository" "repo" { topics = try(each.value.topics, null) # Properties - visibility = try(each.value.visibility, local.default_properties.visibility, null) + visibility = try(each.value.visibility, local.repository_defaults.visibility, null) is_template = try(each.value.is_template, null) # Features - has_issues = try(each.value.has_issues, local.default_properties.has_issues, null) - has_discussions = try(each.value.has_discussions, local.default_properties.has_discussions, null) - has_projects = try(each.value.has_projects, local.default_properties.has_projects, null) - has_wiki = try(each.value.has_wiki, local.default_properties.has_wiki, null) + has_issues = try(each.value.has_issues, local.repository_defaults.has_issues, null) + has_discussions = try(each.value.has_discussions, local.repository_defaults.has_discussions, null) + has_projects = try(each.value.has_projects, local.repository_defaults.has_projects, null) + has_wiki = try(each.value.has_wiki, local.repository_defaults.has_wiki, null) # Settings - allow_merge_commit = try(each.value.allow_merge_commit, local.default_properties.allow_merge_commit, null) - allow_squash_merge = try(each.value.allow_squash_merge, local.default_properties.allow_squash_merge, null) - allow_rebase_merge = try(each.value.allow_rebase_merge, local.default_properties.allow_rebase_merge, null) - allow_auto_merge = try(each.value.allow_auto_merge, local.default_properties.allow_auto_merge, null) - delete_branch_on_merge = try(each.value.delete_branch_on_merge, local.default_properties.delete_branch_on_merge, null) + allow_merge_commit = try(each.value.allow_merge_commit, local.repository_defaults.allow_merge_commit, null) + allow_squash_merge = try(each.value.allow_squash_merge, local.repository_defaults.allow_squash_merge, null) + allow_rebase_merge = try(each.value.allow_rebase_merge, local.repository_defaults.allow_rebase_merge, null) + allow_auto_merge = try(each.value.allow_auto_merge, local.repository_defaults.allow_auto_merge, null) + delete_branch_on_merge = try(each.value.delete_branch_on_merge, local.repository_defaults.delete_branch_on_merge, null) } diff --git a/test.yaml b/test.yaml index 1e5de7f..8185995 100644 --- a/test.yaml +++ b/test.yaml @@ -1,7 +1,8 @@ --- -default-properties: - # Global features - has_issues: true +organization: + repository-defaults: + # Global features + has_issues: true repositories: - name: .github # Repository metadata From 869225e3877940b941069d997fe2946593f1bab8 Mon Sep 17 00:00:00 2001 From: Martin Bruzina Date: Thu, 3 Jul 2025 22:03:56 +0200 Subject: [PATCH 2/4] feat: add all-repository rulesets --- README.md | 29 +++++++++++++++++++++---- terraform/main.tf | 55 +++++++++++++++++++++++++++++++++++++++++++++-- test.yaml | 19 +++++++++++++++- 3 files changed, 96 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 364808b..69689a2 100644 --- a/README.md +++ b/README.md @@ -152,23 +152,44 @@ The GitHub organization YAML configuration post a Terraform plan as a pull reque ### GitHub Organization YAML +The example below demonstrates the full range of capabilities available in the organization YAML configuration. + ```yaml --- organization: repository-defaults: # OPTIONAL - # Global properties + # All-repository default properties visibility: public # OPTIONAL, DEFAULT public - # Global features + # All-repository default features has_issues: true # OPTIONAL, DEFAULT false has_discussions: true # OPTIONAL, DEFAULT false has_projects: true # OPTIONAL, DEFAULT false has_wiki: true # OPTIONAL, DEFAULT false - # Global settings + # All-repository default settings allow_merge_commit: false # OPTIONAL, DEFAULT true allow_squash_merge: true # OPTIONAL, DEFAULT true allow_rebase_merge: true # OPTIONAL, DEFAULT true allow_auto_merge: true # OPTIONAL, DEFAULT false delete_branch_on_merge: true # OPTIONAL, DEFAULT false + # All-repository default rulesets + rulesets: + - name: "Main Branch" + target: branch # REQUIRED, VALUES branch or tag + enforcement: active # REQUIRED, VALUES disabled or active + conditions: # OPTIONAL, DEFAULT empty + ref_name: + include: # OPTIONAL, DEFAULT empty, VALUE array of ref names or patterns to include, special values ~ALL and ~DEFAULT_BRANCH also accepted + - ~DEFAULT_BRANCH + exclude: # OPTIONAL, DEFAULT empty + rules: + creation: false # OPTIONAL, DEFAULT true + update: false # OPTIONAL, DEFAULT true + update_allows_fetch_and_merge: false # OPTIONAL, DEFAULT false + deletion: false # OPTIONAL, DEFAULT true + required_linear_history: true # OPTIONAL, DEFAULT false + required_signatures: true # OPTIONAL, DEFAULT false + pull_request: # OPTIONAL, DEFAULT empty MEANING does not require a pull request before merging + required_approving_review_count: 0 # OPTIONAL, DEFAULT 0 repositories: - name: repo-slug # Repository metadata @@ -192,7 +213,7 @@ repositories: delete_branch_on_merge: true # OPTIONAL, DEFAULT false ``` -Defaults are the same as in the Terraform provider `github` resource `github_repository`, see [Terraform Registry / Providers / integrations / github / resources / github_repository](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository#argument-reference). +Defaults are usually the same as in the Terraform provider `github` resource `github_repository`, see [Terraform Registry / Providers / integrations / github / resources / github_repository](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository#argument-reference). ### Local Usage diff --git a/terraform/main.tf b/terraform/main.tf index 0354bd6..7b15535 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -1,7 +1,13 @@ locals { - config = yamldecode(file(var.path)) + config = yamldecode(file(var.path)) repository_defaults = try(local.config.organization.repository-defaults, null) - repositories = local.config.repositories + repositories = local.config.repositories + repo_ruleset_combinations = [ + for pair in setproduct(local.repositories, local.repository_defaults.rulesets) : { + repository = pair[0] + ruleset = pair[1] + } + ] } resource "github_repository" "repo" { @@ -30,3 +36,48 @@ resource "github_repository" "repo" { allow_auto_merge = try(each.value.allow_auto_merge, local.repository_defaults.allow_auto_merge, null) delete_branch_on_merge = try(each.value.delete_branch_on_merge, local.repository_defaults.delete_branch_on_merge, null) } + +resource "github_repository_ruleset" "this" { + for_each = { + for combo in local.repo_ruleset_combinations : + "${combo.repository.name}-${combo.ruleset.name}" => combo + } + + # Metadata + name = each.value.ruleset.name + repository = each.value.repository.name + target = try(each.value.ruleset.target, null) + enforcement = try(each.value.ruleset.enforcement, null) + + # Conditions + dynamic "conditions" { + for_each = try(length(each.value.ruleset.conditions) > 0 ? [each.value.ruleset.conditions] : [], []) + content { + ref_name { + include = try(each.value.ruleset.conditions.ref_name.include, []) + exclude = try(each.value.ruleset.conditions.ref_name.exclude, []) + } + } + } + + # Rules + rules { + # Lifecycle rules + creation = try(each.value.ruleset.rules.creation, null) + update = try(each.value.ruleset.rules.update, null) + update_allows_fetch_and_merge = try(each.value.ruleset.rules.update_allows_fetch_and_merge, null) + deletion = try(each.value.ruleset.rules.deletion, null) + + # Commit and history rules + required_linear_history = try(each.value.ruleset.rules.required_linear_history, null) + required_signatures = try(each.value.ruleset.rules.required_signatures, null) + + # PR rules + dynamic "pull_request" { + for_each = try(length(each.value.ruleset.rules.pull_request) > 0 ? [each.value.ruleset.rules.pull_request] : [], []) + content { + required_approving_review_count = try(pull_request.value.required_approving_review_count, null) + } + } + } +} diff --git a/test.yaml b/test.yaml index 8185995..60f5f0a 100644 --- a/test.yaml +++ b/test.yaml @@ -1,8 +1,25 @@ --- organization: repository-defaults: - # Global features + # All-repository default features has_issues: true + # All-repository default rulesets + rulesets: + - name: "Main Branch" + target: branch + enforcement: active + conditions: + ref_name: + include: + - ~DEFAULT_BRANCH + rules: + creation: false + update: false + deletion: false + pull_request: + required_approving_review_count: 0 + required_linear_history: true + required_signatures: true repositories: - name: .github # Repository metadata From 5b62a32425b5eebb60681efa3c498e1a72fa7e02 Mon Sep 17 00:00:00 2001 From: Martin Bruzina Date: Thu, 3 Jul 2025 22:08:58 +0200 Subject: [PATCH 3/4] feat: rename repo defaults to all-repositories --- README.md | 2 +- terraform/main.tf | 34 +++++++++++++++++----------------- test.yaml | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 69689a2..f88ec63 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ The example below demonstrates the full range of capabilities available in the o ```yaml --- organization: - repository-defaults: # OPTIONAL + all-repositories: # OPTIONAL # All-repository default properties visibility: public # OPTIONAL, DEFAULT public # All-repository default features diff --git a/terraform/main.tf b/terraform/main.tf index 7b15535..4eabdc8 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -1,9 +1,9 @@ locals { - config = yamldecode(file(var.path)) - repository_defaults = try(local.config.organization.repository-defaults, null) - repositories = local.config.repositories - repo_ruleset_combinations = [ - for pair in setproduct(local.repositories, local.repository_defaults.rulesets) : { + config = yamldecode(file(var.path)) + all_repositories = try(local.config.organization.all-repositories, null) + repositories = local.config.repositories + all_repositories_rulesets = [ + for pair in setproduct(local.repositories, local.all_repositories.rulesets) : { repository = pair[0] ruleset = pair[1] } @@ -20,27 +20,27 @@ resource "github_repository" "repo" { topics = try(each.value.topics, null) # Properties - visibility = try(each.value.visibility, local.repository_defaults.visibility, null) + visibility = try(each.value.visibility, local.all_repositories.visibility, null) is_template = try(each.value.is_template, null) # Features - has_issues = try(each.value.has_issues, local.repository_defaults.has_issues, null) - has_discussions = try(each.value.has_discussions, local.repository_defaults.has_discussions, null) - has_projects = try(each.value.has_projects, local.repository_defaults.has_projects, null) - has_wiki = try(each.value.has_wiki, local.repository_defaults.has_wiki, null) + has_issues = try(each.value.has_issues, local.all_repositories.has_issues, null) + has_discussions = try(each.value.has_discussions, local.all_repositories.has_discussions, null) + has_projects = try(each.value.has_projects, local.all_repositories.has_projects, null) + has_wiki = try(each.value.has_wiki, local.all_repositories.has_wiki, null) # Settings - allow_merge_commit = try(each.value.allow_merge_commit, local.repository_defaults.allow_merge_commit, null) - allow_squash_merge = try(each.value.allow_squash_merge, local.repository_defaults.allow_squash_merge, null) - allow_rebase_merge = try(each.value.allow_rebase_merge, local.repository_defaults.allow_rebase_merge, null) - allow_auto_merge = try(each.value.allow_auto_merge, local.repository_defaults.allow_auto_merge, null) - delete_branch_on_merge = try(each.value.delete_branch_on_merge, local.repository_defaults.delete_branch_on_merge, null) + allow_merge_commit = try(each.value.allow_merge_commit, local.all_repositories.allow_merge_commit, null) + allow_squash_merge = try(each.value.allow_squash_merge, local.all_repositories.allow_squash_merge, null) + allow_rebase_merge = try(each.value.allow_rebase_merge, local.all_repositories.allow_rebase_merge, null) + allow_auto_merge = try(each.value.allow_auto_merge, local.all_repositories.allow_auto_merge, null) + delete_branch_on_merge = try(each.value.delete_branch_on_merge, local.all_repositories.delete_branch_on_merge, null) } resource "github_repository_ruleset" "this" { for_each = { - for combo in local.repo_ruleset_combinations : - "${combo.repository.name}-${combo.ruleset.name}" => combo + for repository_ruleset in local.all_repositories_rulesets : + "${repository_ruleset.repository.name}-${repository_ruleset.ruleset.name}" => repository_ruleset } # Metadata diff --git a/test.yaml b/test.yaml index 60f5f0a..1ad21e4 100644 --- a/test.yaml +++ b/test.yaml @@ -1,6 +1,6 @@ --- organization: - repository-defaults: + all-repositories: # All-repository default features has_issues: true # All-repository default rulesets From 35bd5b18b31a94c458ab4611351b9b87cf530249 Mon Sep 17 00:00:00 2001 From: Martin Bruzina Date: Thu, 3 Jul 2025 22:23:42 +0200 Subject: [PATCH 4/4] refactor: better all-repository ruleset naming --- terraform/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/main.tf b/terraform/main.tf index 4eabdc8..35f3cbb 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -37,10 +37,10 @@ resource "github_repository" "repo" { delete_branch_on_merge = try(each.value.delete_branch_on_merge, local.all_repositories.delete_branch_on_merge, null) } -resource "github_repository_ruleset" "this" { +resource "github_repository_ruleset" "all_repositories" { for_each = { for repository_ruleset in local.all_repositories_rulesets : - "${repository_ruleset.repository.name}-${repository_ruleset.ruleset.name}" => repository_ruleset + format("%s/%s", "${repository_ruleset.repository.name}", replace(lower("${repository_ruleset.ruleset.name}"), " ", "_")) => repository_ruleset } # Metadata