Skip to content

Commit 70a0a5a

Browse files
syakir-uzairnickfloydkfcampbell
authored
feat(github_branch_protection_v3): Add support for bypass_pull_request_allowances (#1578)
* feat(github_branch_protection_v3): Add support for bypass_pull_request_allowances * fix: Get bpra * chore: Remove debug * chore: Remove debug * fix: Lint * fix: Test * Test rename for convention --------- Co-authored-by: Nick Floyd <[email protected]> Co-authored-by: Keegan Campbell <[email protected]>
1 parent 0546397 commit 70a0a5a

4 files changed

+197
-10
lines changed

github/resource_github_branch_protection_v3.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,30 @@ func resourceGithubBranchProtectionV3() *schema.Resource {
122122
Description: "Require 'x' number of approvals to satisfy branch protection requirements. If this is specified it must be a number between 0-6.",
123123
ValidateFunc: validation.IntBetween(0, 6),
124124
},
125+
"bypass_pull_request_allowances": {
126+
Type: schema.TypeList,
127+
Optional: true,
128+
MaxItems: 1,
129+
Elem: &schema.Resource{
130+
Schema: map[string]*schema.Schema{
131+
"users": {
132+
Type: schema.TypeSet,
133+
Optional: true,
134+
Elem: &schema.Schema{Type: schema.TypeString},
135+
},
136+
"teams": {
137+
Type: schema.TypeSet,
138+
Optional: true,
139+
Elem: &schema.Schema{Type: schema.TypeString},
140+
},
141+
"apps": {
142+
Type: schema.TypeSet,
143+
Optional: true,
144+
Elem: &schema.Schema{Type: schema.TypeString},
145+
},
146+
},
147+
},
148+
},
125149
},
126150
},
127151
},

github/resource_github_branch_protection_v3_test.go

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,10 @@ func TestAccGithubBranchProtectionV3_required_status_checks(t *testing.T) {
211211
})
212212
}
213213
func TestAccGithubBranchProtectionV3_required_pull_request_reviews(t *testing.T) {
214+
214215
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
215216

216217
t.Run("configures required pull request reviews", func(t *testing.T) {
217-
218218
config := fmt.Sprintf(`
219219
220220
resource "github_repository" "test" {
@@ -228,8 +228,8 @@ func TestAccGithubBranchProtectionV3_required_pull_request_reviews(t *testing.T)
228228
branch = "main"
229229
230230
required_pull_request_reviews {
231-
dismiss_stale_reviews = true
232-
require_code_owner_reviews = true
231+
dismiss_stale_reviews = true
232+
require_code_owner_reviews = true
233233
}
234234
235235
}
@@ -279,6 +279,80 @@ func TestAccGithubBranchProtectionV3_required_pull_request_reviews(t *testing.T)
279279
})
280280
}
281281

282+
func TestAccGithubBranchProtectionV3RequiredPullRequestReviewsBypassAllowances(t *testing.T) {
283+
284+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
285+
286+
t.Run("configures required pull request reviews with bypass allowances", func(t *testing.T) {
287+
config := fmt.Sprintf(`
288+
289+
resource "github_repository" "test" {
290+
name = "tf-acc-test-%s"
291+
auto_init = true
292+
}
293+
294+
resource "github_team" "test" {
295+
name = "tf-acc-test-%[1]s"
296+
}
297+
298+
resource "github_team_repository" "test" {
299+
team_id = github_team.test.id
300+
repository = github_repository.test.name
301+
permission = "admin"
302+
}
303+
304+
resource "github_branch_protection_v3" "test" {
305+
repository = github_repository.test.name
306+
branch = "main"
307+
308+
required_pull_request_reviews {
309+
bypass_pull_request_allowances {
310+
teams = [github_team.test.slug]
311+
}
312+
}
313+
314+
depends_on = [github_team_repository.test]
315+
}
316+
317+
`, randomID)
318+
319+
check := resource.ComposeAggregateTestCheckFunc(
320+
resource.TestCheckResourceAttr(
321+
"github_branch_protection_v3.test", "required_pull_request_reviews.#", "1",
322+
),
323+
resource.TestCheckResourceAttr(
324+
"github_branch_protection_v3.test", "required_pull_request_reviews.0.bypass_pull_request_allowances.#", "1",
325+
),
326+
)
327+
328+
testCase := func(t *testing.T, mode string) {
329+
resource.Test(t, resource.TestCase{
330+
PreCheck: func() { skipUnlessMode(t, mode) },
331+
Providers: testAccProviders,
332+
Steps: []resource.TestStep{
333+
{
334+
Config: config,
335+
Check: check,
336+
},
337+
},
338+
})
339+
}
340+
341+
t.Run("with an anonymous account", func(t *testing.T) {
342+
t.Skip("anonymous account not supported for this operation")
343+
})
344+
345+
t.Run("with an individual account", func(t *testing.T) {
346+
t.Skip("individual account not supported for this operation")
347+
})
348+
349+
t.Run("with an organization account", func(t *testing.T) {
350+
testCase(t, organization)
351+
})
352+
353+
})
354+
}
355+
282356
func TestAccGithubBranchProtectionV3_branch_push_restrictions(t *testing.T) {
283357

284358
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
@@ -294,7 +368,7 @@ func TestAccGithubBranchProtectionV3_branch_push_restrictions(t *testing.T) {
294368
resource "github_team" "test" {
295369
name = "tf-acc-test-%[1]s"
296370
}
297-
371+
298372
resource "github_team_repository" "test" {
299373
team_id = "${github_team.test.id}"
300374
repository = "${github_repository.test.name}"
@@ -307,9 +381,9 @@ func TestAccGithubBranchProtectionV3_branch_push_restrictions(t *testing.T) {
307381
branch = "main"
308382
309383
restrictions {
310-
teams = ["${github_team.test.slug}"]
384+
teams = ["${github_team.test.slug}"]
311385
}
312-
386+
313387
}
314388
`, randomID)
315389

github/resource_github_branch_protection_v3_utils.go

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"github.com/google/go-github/v50/github"
8-
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
97
"log"
108
"strconv"
119
"strings"
10+
11+
"github.com/google/go-github/v50/github"
12+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
1213
)
1314

1415
func buildProtectionRequest(d *schema.ResourceData) (*github.ProtectionRequest, error) {
@@ -122,6 +123,40 @@ func requireSignedCommitsUpdate(d *schema.ResourceData, meta interface{}) (err e
122123
return err
123124
}
124125

126+
func flattenBypassPullRequestAllowances(bpra *github.BypassPullRequestAllowances) []interface{} {
127+
if bpra == nil {
128+
return nil
129+
}
130+
users := make([]interface{}, 0, len(bpra.Users))
131+
for _, u := range bpra.Users {
132+
if u.Login != nil {
133+
users = append(users, *u.Login)
134+
}
135+
}
136+
137+
teams := make([]interface{}, 0, len(bpra.Teams))
138+
for _, t := range bpra.Teams {
139+
if t.Slug != nil {
140+
teams = append(teams, *t.Slug)
141+
}
142+
}
143+
144+
apps := make([]interface{}, 0, len(bpra.Apps))
145+
for _, t := range bpra.Apps {
146+
if t.Slug != nil {
147+
apps = append(apps, *t.Slug)
148+
}
149+
}
150+
151+
return []interface{}{
152+
map[string]interface{}{
153+
"users": schema.NewSet(schema.HashString, users),
154+
"teams": schema.NewSet(schema.HashString, teams),
155+
"apps": schema.NewSet(schema.HashString, apps),
156+
},
157+
}
158+
}
159+
125160
func flattenAndSetRequiredPullRequestReviews(d *schema.ResourceData, protection *github.Protection) error {
126161
rprr := protection.GetRequiredPullRequestReviews()
127162
if rprr != nil {
@@ -143,13 +178,16 @@ func flattenAndSetRequiredPullRequestReviews(d *schema.ResourceData, protection
143178
}
144179
}
145180

181+
bpra := flattenBypassPullRequestAllowances(rprr.GetBypassPullRequestAllowances())
182+
146183
return d.Set("required_pull_request_reviews", []interface{}{
147184
map[string]interface{}{
148185
"dismiss_stale_reviews": rprr.DismissStaleReviews,
149186
"dismissal_users": schema.NewSet(schema.HashString, users),
150187
"dismissal_teams": schema.NewSet(schema.HashString, teams),
151188
"require_code_owner_reviews": rprr.RequireCodeOwnerReviews,
152189
"required_approving_review_count": rprr.RequiredApprovingReviewCount,
190+
"bypass_pull_request_allowances": bpra,
153191
},
154192
})
155193
}
@@ -292,10 +330,16 @@ func expandRequiredPullRequestReviews(d *schema.ResourceData) (*github.PullReque
292330
drr.Teams = &teams
293331
}
294332

333+
bpra, err := expandBypassPullRequestAllowances(m)
334+
if err != nil {
335+
return nil, err
336+
}
337+
295338
rprr.DismissalRestrictionsRequest = drr
296339
rprr.DismissStaleReviews = m["dismiss_stale_reviews"].(bool)
297340
rprr.RequireCodeOwnerReviews = m["require_code_owner_reviews"].(bool)
298341
rprr.RequiredApprovingReviewCount = m["required_approving_review_count"].(int)
342+
rprr.BypassPullRequestAllowancesRequest = bpra
299343
}
300344

301345
return rprr, nil
@@ -336,6 +380,36 @@ func expandRestrictions(d *schema.ResourceData) (*github.BranchRestrictionsReque
336380
return nil, nil
337381
}
338382

383+
func expandBypassPullRequestAllowances(m map[string]interface{}) (*github.BypassPullRequestAllowancesRequest, error) {
384+
if m["bypass_pull_request_allowances"] == nil {
385+
return nil, nil
386+
}
387+
388+
vL := m["bypass_pull_request_allowances"].([]interface{})
389+
if len(vL) > 1 {
390+
return nil, errors.New("cannot specify bypass_pull_request_allowances more than one time")
391+
}
392+
393+
var bpra *github.BypassPullRequestAllowancesRequest
394+
395+
for _, v := range vL {
396+
if v == nil {
397+
return nil, errors.New("invalid bypass_pull_request_allowances")
398+
}
399+
bpra = new(github.BypassPullRequestAllowancesRequest)
400+
m := v.(map[string]interface{})
401+
402+
users := expandNestedSet(m, "users")
403+
bpra.Users = users
404+
teams := expandNestedSet(m, "teams")
405+
bpra.Teams = teams
406+
apps := expandNestedSet(m, "apps")
407+
bpra.Apps = apps
408+
}
409+
410+
return bpra, nil
411+
}
412+
339413
func checkBranchRestrictionsUsers(actual *github.BranchRestrictions, expected *github.BranchRestrictionsRequest) error {
340414
if expected == nil {
341415
return nil

website/docs/r/branch_protection_v3.html.markdown

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: |-
55
Protects a GitHub branch using the v3 / REST implementation. The `github_branch_protection` resource has moved to the GraphQL API, while this resource will continue to leverage the REST API
66
---
77

8-
# github\_branch\_protection\_v3
8+
# github_branch_protection_v3
99

1010
Protects a GitHub branch.
1111

@@ -29,7 +29,7 @@ resource "github_branch_protection_v3" "example" {
2929

3030
```hcl
3131
# Protect the main branch of the foo repository. Additionally, require that
32-
# the "ci/check" check ran by the Github Actions app is passing and only allow
32+
# the "ci/check" check ran by the Github Actions app is passing and only allow
3333
# the engineers team merge to the branch.
3434
3535
resource "github_branch_protection_v3" "example" {
@@ -48,6 +48,12 @@ resource "github_branch_protection_v3" "example" {
4848
dismiss_stale_reviews = true
4949
dismissal_users = ["foo-user"]
5050
dismissal_teams = [github_team.example.slug]
51+
52+
bypass_pull_request_allowances {
53+
users = ["foo-user"]
54+
teams = [github_team.example.slug]
55+
apps = ["foo-app"]
56+
}
5157
}
5258
5359
restrictions {
@@ -103,6 +109,7 @@ The following arguments are supported:
103109
Always use `slug` of the team, **not** its name. Each team already **has** to have access to the repository.
104110
* `require_code_owner_reviews`: (Optional) Require an approved review in pull requests including files with a designated code owner. Defaults to `false`.
105111
* `required_approving_review_count`: (Optional) Require x number of approvals to satisfy branch protection requirements. If this is specified it must be a number between 0-6. This requirement matches GitHub's API, see the upstream [documentation](https://developer.github.com/v3/repos/branches/#parameters-1) for more information.
112+
* `bypass_pull_request_allowances`: (Optional) Allow specific users, teams, or apps to bypass pull request requirements. See [Bypass Pull Request Allowances](#bypass-pull-request-allowances) below for details.
106113

107114
### Restrictions
108115

@@ -115,6 +122,14 @@ The following arguments are supported:
115122

116123
`restrictions` is only available for organization-owned repositories.
117124

125+
### Bypass Pull Request Allowances
126+
127+
`bypass_pull_request_allowances` supports the following arguments:
128+
129+
- `users`: (Optional) The list of user logins allowed to bypass pull request requirements.
130+
- `teams`: (Optional) The list of team slugs allowed to bypass pull request requirements.
131+
- `apps`: (Optional) The list of app slugs allowed to bypass pull request requirements.
132+
118133
## Import
119134

120135
GitHub Branch Protection can be imported using an ID made up of `repository:branch`, e.g.

0 commit comments

Comments
 (0)