diff --git a/README.md b/README.md
index df15cc4..01b436f 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,8 @@ A [Terraform] module for creating a public or private repository on [Github].
- [Collaborator Configuration](#collaborator-configuration)
- [Branches Configuration](#branches-configuration)
- [Deploy Keys Configuration](#deploy-keys-configuration)
- - [Branch Protections Configuration](#branch-protections-configuration)
+ - [Branch Protections v3 Configuration](#branch-protections-v3-configuration)
+ - [Branch Protections v4 Configuration](#branch-protections-v4-configuration)
- [Issue Labels Configuration](#issue-labels-configuration)
- [Projects Configuration](#projects-configuration)
- [Webhooks Configuration](#webhooks-configuration)
@@ -528,11 +529,11 @@ This is due to some terraform limitation and we will update the module once terr
Default is `"md5(key)"`.
-#### Branch Protections Configuration
+#### Branch Protections v3 Configuration
- [**`branch_protections_v3`**](#var-branch_protections_v3): *(Optional `list(branch_protection_v3)`)*
- This resource allows you to configure branch protection for repositories in your organization.
+ This resource allows you to configure v3 branch protection for repositories in your organization.
When applied, the branch will be protected from forced pushes and deletion.
Additional constraints, such as required status checks or restrictions on users and teams, can also be configured.
@@ -652,6 +653,135 @@ This is due to some terraform limitation and we will update the module once terr
Default is `[]`.
+#### Branch Protections v4 Configuration
+
+- [**`branch_protections_v4`**](#var-branch_protections_v4): *(Optional `map(branch_protection_v4)`)*
+
+ This map allows you to configure v4 branch protection for repositories in your organization.
+
+ Each element in the map is a branch to be protected and the value the corresponding to the desired configuration for the branch.
+
+ When applied, the branch will be protected from forced pushes and deletion.
+ Additional constraints, such as required status checks or restrictions on users and teams, can also be configured.
+
+ **_NOTE_** This will take precedence over v3 branch protections.
+
+ Default is `null`.
+
+ Each `branch_protection_v4` object in the map accepts the following attributes:
+
+ - [**`allows_deletions`**](#attr-branch_protections_v4-allows_deletions): *(Optional `bool`)*
+
+ Setting this to true to allow the branch to be deleted.
+
+ Default is `false`.
+
+ - [**`allows_force_pushes`**](#attr-branch_protections_v4-allows_force_pushes): *(Optional `bool`)*
+
+ Setting this to true to allow force pushes on the branch.
+
+ Default is `false`.
+
+ - [**`blocks_creations`**](#attr-branch_protections_v4-blocks_creations): *(Optional `bool`)*
+
+ Setting this to true will block creating the branch.
+
+ Default is `false`.
+
+ - [**`enforce_admins`**](#attr-branch_protections_v4-enforce_admins): *(Optional `bool`)*
+
+ Setting this to true enforces status checks for repository administrators.
+
+ Default is `false`.
+
+ - [**`push_restrictions`**](#attr-branch_protections_v4-push_restrictions): *(Optional `list(string)`)*
+
+ The list of actor Names/IDs that may push to the branch.
+ Actor names must either begin with a "/" for users or the organization name followed by a "/" for teams.
+
+ Default is `[]`.
+
+ - [**`require_conversation_resolution`**](#attr-branch_protections_v4-require_conversation_resolution): *(Optional `bool`)*
+
+ Setting this to true requires all conversations on code must be resolved before a pull request can be merged.
+
+ Default is `false`.
+
+ - [**`require_signed_commits`**](#attr-branch_protections_v4-require_signed_commits): *(Optional `bool`)*
+
+ Setting this to true requires all commits to be signed with GPG.
+
+ Default is `false`.
+
+ - [**`required_linear_history`**](#attr-branch_protections_v4-required_linear_history): *(Optional `bool`)*
+
+ Setting this to true enforces a linear commit Git history, which prevents anyone from pushing merge commits to a branch.
+
+ Default is `false`.
+
+ - [**`required_pull_request_reviews`**](#attr-branch_protections_v4-required_pull_request_reviews): *(Optional `object(required_pull_request_reviews)`)*
+
+ Enforce restrictions for pull request reviews.
+
+ Default is `null`.
+
+ The `required_pull_request_reviews` object accepts the following attributes:
+
+ - [**`dismiss_stale_reviews`**](#attr-branch_protections_v4-required_pull_request_reviews-dismiss_stale_reviews): *(Optional `bool`)*
+
+ Dismiss approved reviews automatically when a new commit is pushed.
+
+ Default is `true`.
+
+ - [**`dismissal_restrictions`**](#attr-branch_protections_v4-required_pull_request_reviews-dismissal_restrictions): *(Optional `list(string)`)*
+
+ The list of actor Names/IDs with dismissal access.
+ If not empty, restrict_dismissals is ignored.
+ Actor names must either begin with a "/" for users or the organization name followed by a "/" for teams.
+
+ Default is `[]`.
+
+ - [**`pull_request_bypassers`**](#attr-branch_protections_v4-required_pull_request_reviews-pull_request_bypassers): *(Optional `list(string)`)*
+
+ The list of actor Names/IDs that are allowed to bypass pull request requirements.
+ Actor names must either begin with a "/" for users or the organization name followed by a "/" for teams.
+
+ Default is `[]`.
+
+ - [**`require_code_owner_reviews`**](#attr-branch_protections_v4-required_pull_request_reviews-require_code_owner_reviews): *(Optional `bool`)*
+
+ Require an approved review in pull requests including files with a designated code owner.
+
+ Default is `false`.
+
+ - [**`required_approving_review_count`**](#attr-branch_protections_v4-required_pull_request_reviews-required_approving_review_count): *(Optional `number`)*
+
+ Require x number of approvals to satisfy branch protection requirements.
+ If this is specified it must be a number between 0-6.
+
+ Default is `0`.
+
+ - [**`required_status_checks`**](#attr-branch_protections_v4-required_status_checks): *(Optional `object(required_status_checks)`)*
+
+ Enforce restrictions for required status checks.
+ See Required Status Checks below for details.
+
+ Default is `null`.
+
+ The `required_status_checks` object accepts the following attributes:
+
+ - [**`strict`**](#attr-branch_protections_v4-required_status_checks-strict): *(Optional `bool`)*
+
+ Require branches to be up to date before merging.
+
+ Default is `false`.
+
+ - [**`contexts`**](#attr-branch_protections_v4-required_status_checks-contexts): *(Optional `list(string)`)*
+
+ The list of status checks to require in order to merge into this branch. If default is `[]` no status checks are required.
+
+ Default is `[]`.
+
#### Issue Labels Configuration
- [**`issue_labels`**](#var-issue_labels): *(Optional `list(issue_label)`)*
diff --git a/README.tfdoc.hcl b/README.tfdoc.hcl
index ab43b59..b6e97a7 100644
--- a/README.tfdoc.hcl
+++ b/README.tfdoc.hcl
@@ -677,13 +677,13 @@ section {
}
section {
- title = "Branch Protections Configuration"
+ title = "Branch Protections v3 Configuration"
variable "branch_protections_v3" {
type = list(branch_protection_v3)
default = []
description = <<-END
- This resource allows you to configure branch protection for repositories in your organization.
+ This resource allows you to configure v3 branch protection for repositories in your organization.
When applied, the branch will be protected from forced pushes and deletion.
Additional constraints, such as required status checks or restrictions on users and teams, can also be configured.
END
@@ -832,6 +832,167 @@ section {
}
}
+ section {
+ title = "Branch Protections v4 Configuration"
+
+ variable "branch_protections_v4" {
+ type = map(branch_protection_v4)
+ default = null
+ description = <<-END
+ This map allows you to configure v4 branch protection for repositories in your organization.
+
+ Each element in the map is a branch to be protected and the value the corresponding to the desired configuration for the branch.
+
+ When applied, the branch will be protected from forced pushes and deletion.
+ Additional constraints, such as required status checks or restrictions on users and teams, can also be configured.
+
+ **_NOTE_** This will take precedence over v3 branch protections.
+ END
+
+ attribute "allows_deletions" {
+ type = bool
+ default = false
+ description = <<-END
+ Setting this to true to allow the branch to be deleted.
+ END
+ }
+
+ attribute "allows_force_pushes" {
+ type = bool
+ default = false
+ description = <<-END
+ Setting this to true to allow force pushes on the branch.
+ END
+ }
+
+ attribute "blocks_creations" {
+ type = bool
+ default = false
+ description = <<-END
+ Setting this to true will block creating the branch.
+ END
+ }
+
+ attribute "enforce_admins" {
+ type = bool
+ default = false
+ description = <<-END
+ Setting this to true enforces status checks for repository administrators.
+ END
+ }
+
+ attribute "push_restrictions" {
+ type = list(string)
+ default = []
+ description = <<-END
+ The list of actor Names/IDs that may push to the branch.
+ Actor names must either begin with a "/" for users or the organization name followed by a "/" for teams.
+ END
+ }
+
+ attribute "require_conversation_resolution" {
+ type = bool
+ default = false
+ description = <<-END
+ Setting this to true requires all conversations on code must be resolved before a pull request can be merged.
+ END
+ }
+
+ attribute "require_signed_commits" {
+ type = bool
+ default = false
+ description = <<-END
+ Setting this to true requires all commits to be signed with GPG.
+ END
+ }
+
+ attribute "required_linear_history" {
+ type = bool
+ default = false
+ description = <<-END
+ Setting this to true enforces a linear commit Git history, which prevents anyone from pushing merge commits to a branch.
+ END
+ }
+
+ attribute "required_pull_request_reviews" {
+ type = object(required_pull_request_reviews)
+ default = null
+ description = <<-END
+ Enforce restrictions for pull request reviews.
+ END
+
+ attribute "dismiss_stale_reviews" {
+ type = bool
+ default = true
+ description = <<-END
+ Dismiss approved reviews automatically when a new commit is pushed.
+ END
+ }
+
+ attribute "dismissal_restrictions" {
+ type = list(string)
+ default = []
+ description = <<-END
+ The list of actor Names/IDs with dismissal access.
+ If not empty, restrict_dismissals is ignored.
+ Actor names must either begin with a "/" for users or the organization name followed by a "/" for teams.
+ END
+ }
+
+ attribute "pull_request_bypassers" {
+ type = list(string)
+ default = []
+ description = <<-END
+ The list of actor Names/IDs that are allowed to bypass pull request requirements.
+ Actor names must either begin with a "/" for users or the organization name followed by a "/" for teams.
+ END
+ }
+
+ attribute "require_code_owner_reviews" {
+ type = bool
+ default = false
+ description = <<-END
+ Require an approved review in pull requests including files with a designated code owner.
+ END
+ }
+
+ attribute "required_approving_review_count" {
+ type = number
+ default = 0
+ description = <<-END
+ Require x number of approvals to satisfy branch protection requirements.
+ If this is specified it must be a number between 0-6.
+ END
+ }
+ }
+
+ attribute "required_status_checks" {
+ type = object(required_status_checks)
+ default = null
+ description = <<-END
+ Enforce restrictions for required status checks.
+ See Required Status Checks below for details.
+ END
+
+ attribute "strict" {
+ type = bool
+ default = false
+ description = <<-END
+ Require branches to be up to date before merging.
+ END
+ }
+
+ attribute "contexts" {
+ type = list(string)
+ default = []
+ description = <<-END
+ The list of status checks to require in order to merge into this branch. If default is `[]` no status checks are required.
+ END
+ }
+ }
+ }
+ }
+
section {
title = "Issue Labels Configuration"
diff --git a/main.tf b/main.tf
index 547e0e7..9f36b51 100644
--- a/main.tf
+++ b/main.tf
@@ -27,7 +27,17 @@ locals {
topics = concat(local.standard_topics, var.extra_topics)
template = var.template == null ? [] : [var.template]
issue_labels_create = var.issue_labels_create == null ? lookup(var.defaults, "issue_labels_create", local.issue_labels_create_computed) : var.issue_labels_create
- branch_protections_v3 = var.branch_protections_v3 == null ? var.branch_protections : var.branch_protections_v3
+ branch_protections_v3 = [
+ for b in coalesce(var.branch_protections_v3, var.branch_protections, []) : merge({
+ branch = null
+ enforce_admins = null
+ require_conversation_resolution = null
+ require_signed_commits = null
+ required_status_checks = {}
+ required_pull_request_reviews = {}
+ restrictions = {}
+ }, b)
+ ]
issue_labels_create_computed = local.has_issues || length(var.issue_labels) > 0
@@ -41,20 +51,8 @@ locals {
}
locals {
- branch_protections = try([
- for b in local.branch_protections_v3 : merge({
- branch = null
- enforce_admins = null
- require_conversation_resolution = null
- require_signed_commits = null
- required_status_checks = {}
- required_pull_request_reviews = {}
- restrictions = {}
- }, b)
- ], [])
-
required_status_checks = [
- for b in local.branch_protections :
+ for b in local.branch_protections_v3 :
length(keys(b.required_status_checks)) > 0 ? [
merge({
strict = null
@@ -63,7 +61,7 @@ locals {
]
required_pull_request_reviews = [
- for b in local.branch_protections :
+ for b in local.branch_protections_v3 :
length(keys(b.required_pull_request_reviews)) > 0 ? [
merge({
dismiss_stale_reviews = true
@@ -75,7 +73,7 @@ locals {
]
restrictions = [
- for b in local.branch_protections :
+ for b in local.branch_protections_v3 :
length(keys(b.restrictions)) > 0 ? [
merge({
users = []
@@ -177,12 +175,63 @@ resource "github_branch_default" "default" {
}
# ---------------------------------------------------------------------------------------------------------------------
-# Branch Protection
+# v4 Branch Protection
+# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_protection
+# ---------------------------------------------------------------------------------------------------------------------
+
+resource "github_branch_protection" "branch_protection" {
+ for_each = length(coalesce(var.branch_protections_v4, {})) > 0 ? var.branch_protections_v4 : {}
+
+ # ensure we have all members and collaborators added before applying
+ # any configuration for them
+ depends_on = [
+ github_repository_collaborator.collaborator,
+ github_team_repository.team_repository,
+ github_team_repository.team_repository_by_slug,
+ github_branch.branch,
+ ]
+
+ repository_id = github_repository.repository.node_id
+ pattern = each.key
+ allows_deletions = each.value.allows_deletions
+ allows_force_pushes = each.value.allows_force_pushes
+ blocks_creations = each.value.blocks_creations
+ enforce_admins = each.value.enforce_admins
+ push_restrictions = each.value.push_restrictions
+ require_conversation_resolution = each.value.require_conversation_resolution
+ require_signed_commits = each.value.require_signed_commits
+ required_linear_history = each.value.required_linear_history
+
+ dynamic "required_pull_request_reviews" {
+ for_each = each.value.required_pull_request_reviews != null ? [true] : []
+
+ content {
+ dismiss_stale_reviews = each.value.required_pull_request_reviews.dismiss_stale_reviews
+ dismissal_restrictions = each.value.required_pull_request_reviews.dismissal_restrictions
+ pull_request_bypassers = each.value.required_pull_request_reviews.pull_request_bypassers
+ require_code_owner_reviews = each.value.required_pull_request_reviews.require_code_owner_reviews
+ required_approving_review_count = each.value.required_pull_request_reviews.required_approving_review_count
+ restrict_dismissals = length(each.value.required_pull_request_reviews.dismissal_restrictions) > 0
+ }
+ }
+
+ dynamic "required_status_checks" {
+ for_each = each.value.required_status_checks != null ? [true] : []
+
+ content {
+ strict = each.value.required_status_checks.strict
+ contexts = each.value.required_status_checks.contexts
+ }
+ }
+}
+
+# ---------------------------------------------------------------------------------------------------------------------
+# v3 Branch Protection
# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_protection_v3
# ---------------------------------------------------------------------------------------------------------------------
resource "github_branch_protection_v3" "branch_protection" {
- count = length(local.branch_protections)
+ count = length(coalesce(var.branch_protections_v4, {})) == 0 ? length(local.branch_protections_v3) : 0
# ensure we have all members and collaborators added before applying
# any configuration for them
@@ -194,10 +243,10 @@ resource "github_branch_protection_v3" "branch_protection" {
]
repository = github_repository.repository.name
- branch = local.branch_protections[count.index].branch
- enforce_admins = local.branch_protections[count.index].enforce_admins
- require_conversation_resolution = local.branch_protections[count.index].require_conversation_resolution
- require_signed_commits = local.branch_protections[count.index].require_signed_commits
+ branch = local.branch_protections_v3[count.index].branch
+ enforce_admins = local.branch_protections_v3[count.index].enforce_admins
+ require_conversation_resolution = local.branch_protections_v3[count.index].require_conversation_resolution
+ require_signed_commits = local.branch_protections_v3[count.index].require_signed_commits
dynamic "required_status_checks" {
for_each = local.required_status_checks[count.index]
diff --git a/variables.tf b/variables.tf
index 3eac082..fe4084a 100644
--- a/variables.tf
+++ b/variables.tf
@@ -351,6 +351,53 @@ variable "branch_protections_v3" {
# ]
}
+variable "branch_protections_v4" {
+ description = "(Optional) A list of v4 branch protections to apply to the repository. Default is {}."
+ type = map(
+ object(
+ {
+ allows_deletions = optional(bool, false)
+ allows_force_pushes = optional(bool, false)
+ blocks_creations = optional(bool, false)
+ enforce_admins = optional(bool, false)
+ push_restrictions = optional(list(string), [])
+ require_conversation_resolution = optional(bool, false)
+ require_signed_commits = optional(bool, false)
+ required_linear_history = optional(bool, false)
+ required_pull_request_reviews = optional(object(
+ {
+ dismiss_stale_reviews = optional(bool, false)
+ dismissal_restrictions = optional(list(string), [])
+ pull_request_bypassers = optional(list(string), [])
+ require_code_owner_reviews = optional(bool, false)
+ required_approving_review_count = optional(number, 0)
+ }
+ ))
+ required_status_checks = optional(object(
+ {
+ strict = optional(bool, false)
+ contexts = optional(list(string), [])
+ }
+ ))
+ }
+ )
+ )
+ default = null
+
+ validation {
+ condition = alltrue(
+ [
+ for cfg in coalesce(var.branch_protections_v4, {}) : try(
+ cfg.required_pull_request_reviews.required_approving_review_count >= 0
+ && cfg.required_pull_request_reviews.required_approving_review_count <= 6,
+ true
+ )
+ ]
+ )
+ error_message = "The value for branch_protections_v4.required_pull_request_reviews.required_approving_review_count must be between 0 and 6, inclusively."
+ }
+}
+
variable "issue_labels_merge_with_github_labels" {
description = "(Optional) Specify if you want to merge and control githubs default set of issue labels."
type = bool
diff --git a/versions.tf b/versions.tf
index 01e52ac..6eaeefd 100644
--- a/versions.tf
+++ b/versions.tf
@@ -3,7 +3,7 @@
# ---------------------------------------------------------------------------------------------------------------------
terraform {
- required_version = "~> 1.0"
+ required_version = "~> 1.3"
# branch_protections_v3 are broken in >= 5.3
required_providers {