From 93e41a0d17e6f7c9048a0c467574bdba8004a639 Mon Sep 17 00:00:00 2001 From: Josh Barker Date: Tue, 14 Sep 2021 16:29:35 +1000 Subject: [PATCH] feat: add conversation resolution variable on branch protection --- github/resource_github_branch_protection.go | 80 ++++++++++------- .../resource_github_branch_protection_test.go | 88 +++++++++++++++++++ github/util_v4_branch_protection.go | 72 ++++++++------- github/util_v4_consts.go | 31 +++---- .../docs/r/branch_protection.html.markdown | 1 + 5 files changed, 190 insertions(+), 82 deletions(-) diff --git a/github/resource_github_branch_protection.go b/github/resource_github_branch_protection.go index 74f16abb55..3f9748523e 100644 --- a/github/resource_github_branch_protection.go +++ b/github/resource_github_branch_protection.go @@ -48,6 +48,11 @@ func resourceGithubBranchProtection() *schema.Resource { Optional: true, Default: false, }, + PROTECTION_REQUIRES_CONVERSATION_RESOLUTION: { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, PROTECTION_REQUIRES_APPROVING_REVIEWS: { Type: schema.TypeList, Optional: true, @@ -136,23 +141,24 @@ func resourceGithubBranchProtectionCreate(d *schema.ResourceData, meta interface return err } input := githubv4.CreateBranchProtectionRuleInput{ - AllowsDeletions: githubv4.NewBoolean(githubv4.Boolean(data.AllowsDeletions)), - AllowsForcePushes: githubv4.NewBoolean(githubv4.Boolean(data.AllowsForcePushes)), - DismissesStaleReviews: githubv4.NewBoolean(githubv4.Boolean(data.DismissesStaleReviews)), - IsAdminEnforced: githubv4.NewBoolean(githubv4.Boolean(data.IsAdminEnforced)), - Pattern: githubv4.String(data.Pattern), - PushActorIDs: githubv4NewIDSlice(githubv4IDSlice(data.PushActorIDs)), - RepositoryID: githubv4.NewID(githubv4.ID(data.RepositoryID)), - RequiredApprovingReviewCount: githubv4.NewInt(githubv4.Int(data.RequiredApprovingReviewCount)), - RequiredStatusCheckContexts: githubv4NewStringSlice(githubv4StringSlice(data.RequiredStatusCheckContexts)), - RequiresApprovingReviews: githubv4.NewBoolean(githubv4.Boolean(data.RequiresApprovingReviews)), - RequiresCodeOwnerReviews: githubv4.NewBoolean(githubv4.Boolean(data.RequiresCodeOwnerReviews)), - RequiresCommitSignatures: githubv4.NewBoolean(githubv4.Boolean(data.RequiresCommitSignatures)), - RequiresStatusChecks: githubv4.NewBoolean(githubv4.Boolean(data.RequiresStatusChecks)), - RequiresStrictStatusChecks: githubv4.NewBoolean(githubv4.Boolean(data.RequiresStrictStatusChecks)), - RestrictsPushes: githubv4.NewBoolean(githubv4.Boolean(data.RestrictsPushes)), - RestrictsReviewDismissals: githubv4.NewBoolean(githubv4.Boolean(data.RestrictsReviewDismissals)), - ReviewDismissalActorIDs: githubv4NewIDSlice(githubv4IDSlice(data.ReviewDismissalActorIDs)), + AllowsDeletions: githubv4.NewBoolean(githubv4.Boolean(data.AllowsDeletions)), + AllowsForcePushes: githubv4.NewBoolean(githubv4.Boolean(data.AllowsForcePushes)), + DismissesStaleReviews: githubv4.NewBoolean(githubv4.Boolean(data.DismissesStaleReviews)), + IsAdminEnforced: githubv4.NewBoolean(githubv4.Boolean(data.IsAdminEnforced)), + Pattern: githubv4.String(data.Pattern), + PushActorIDs: githubv4NewIDSlice(githubv4IDSlice(data.PushActorIDs)), + RepositoryID: githubv4.NewID(githubv4.ID(data.RepositoryID)), + RequiredApprovingReviewCount: githubv4.NewInt(githubv4.Int(data.RequiredApprovingReviewCount)), + RequiredStatusCheckContexts: githubv4NewStringSlice(githubv4StringSlice(data.RequiredStatusCheckContexts)), + RequiresApprovingReviews: githubv4.NewBoolean(githubv4.Boolean(data.RequiresApprovingReviews)), + RequiresCodeOwnerReviews: githubv4.NewBoolean(githubv4.Boolean(data.RequiresCodeOwnerReviews)), + RequiresCommitSignatures: githubv4.NewBoolean(githubv4.Boolean(data.RequiresCommitSignatures)), + RequiresConversationResolution: githubv4.NewBoolean(githubv4.Boolean(data.RequiresConversationResolution)), + RequiresStatusChecks: githubv4.NewBoolean(githubv4.Boolean(data.RequiresStatusChecks)), + RequiresStrictStatusChecks: githubv4.NewBoolean(githubv4.Boolean(data.RequiresStrictStatusChecks)), + RestrictsPushes: githubv4.NewBoolean(githubv4.Boolean(data.RestrictsPushes)), + RestrictsReviewDismissals: githubv4.NewBoolean(githubv4.Boolean(data.RestrictsReviewDismissals)), + ReviewDismissalActorIDs: githubv4NewIDSlice(githubv4IDSlice(data.ReviewDismissalActorIDs)), } ctx := context.Background() @@ -217,6 +223,11 @@ func resourceGithubBranchProtectionRead(d *schema.ResourceData, meta interface{} log.Printf("[WARN] Problem setting '%s' in %s %s branch protection (%s)", PROTECTION_REQUIRES_COMMIT_SIGNATURES, protection.Repository.Name, protection.Pattern, d.Id()) } + err = d.Set(PROTECTION_REQUIRES_CONVERSATION_RESOLUTION, protection.RequiresConversationResolution) + if err != nil { + log.Printf("[WARN] Problem setting '%s' in %s %s branch protection (%s)", PROTECTION_REQUIRES_CONVERSATION_RESOLUTION, protection.Repository.Name, protection.Pattern, d.Id()) + } + approvingReviews := setApprovingReviews(protection) err = d.Set(PROTECTION_REQUIRES_APPROVING_REVIEWS, approvingReviews) if err != nil { @@ -251,23 +262,24 @@ func resourceGithubBranchProtectionUpdate(d *schema.ResourceData, meta interface return err } input := githubv4.UpdateBranchProtectionRuleInput{ - BranchProtectionRuleID: d.Id(), - AllowsDeletions: githubv4.NewBoolean(githubv4.Boolean(data.AllowsDeletions)), - AllowsForcePushes: githubv4.NewBoolean(githubv4.Boolean(data.AllowsForcePushes)), - DismissesStaleReviews: githubv4.NewBoolean(githubv4.Boolean(data.DismissesStaleReviews)), - IsAdminEnforced: githubv4.NewBoolean(githubv4.Boolean(data.IsAdminEnforced)), - Pattern: githubv4.NewString(githubv4.String(data.Pattern)), - PushActorIDs: githubv4NewIDSlice(githubv4IDSlice(data.PushActorIDs)), - RequiredApprovingReviewCount: githubv4.NewInt(githubv4.Int(data.RequiredApprovingReviewCount)), - RequiredStatusCheckContexts: githubv4NewStringSlice(githubv4StringSlice(data.RequiredStatusCheckContexts)), - RequiresApprovingReviews: githubv4.NewBoolean(githubv4.Boolean(data.RequiresApprovingReviews)), - RequiresCodeOwnerReviews: githubv4.NewBoolean(githubv4.Boolean(data.RequiresCodeOwnerReviews)), - RequiresCommitSignatures: githubv4.NewBoolean(githubv4.Boolean(data.RequiresCommitSignatures)), - RequiresStatusChecks: githubv4.NewBoolean(githubv4.Boolean(data.RequiresStatusChecks)), - RequiresStrictStatusChecks: githubv4.NewBoolean(githubv4.Boolean(data.RequiresStrictStatusChecks)), - RestrictsPushes: githubv4.NewBoolean(githubv4.Boolean(data.RestrictsPushes)), - RestrictsReviewDismissals: githubv4.NewBoolean(githubv4.Boolean(data.RestrictsReviewDismissals)), - ReviewDismissalActorIDs: githubv4NewIDSlice(githubv4IDSlice(data.ReviewDismissalActorIDs)), + BranchProtectionRuleID: d.Id(), + AllowsDeletions: githubv4.NewBoolean(githubv4.Boolean(data.AllowsDeletions)), + AllowsForcePushes: githubv4.NewBoolean(githubv4.Boolean(data.AllowsForcePushes)), + DismissesStaleReviews: githubv4.NewBoolean(githubv4.Boolean(data.DismissesStaleReviews)), + IsAdminEnforced: githubv4.NewBoolean(githubv4.Boolean(data.IsAdminEnforced)), + Pattern: githubv4.NewString(githubv4.String(data.Pattern)), + PushActorIDs: githubv4NewIDSlice(githubv4IDSlice(data.PushActorIDs)), + RequiredApprovingReviewCount: githubv4.NewInt(githubv4.Int(data.RequiredApprovingReviewCount)), + RequiredStatusCheckContexts: githubv4NewStringSlice(githubv4StringSlice(data.RequiredStatusCheckContexts)), + RequiresApprovingReviews: githubv4.NewBoolean(githubv4.Boolean(data.RequiresApprovingReviews)), + RequiresCodeOwnerReviews: githubv4.NewBoolean(githubv4.Boolean(data.RequiresCodeOwnerReviews)), + RequiresCommitSignatures: githubv4.NewBoolean(githubv4.Boolean(data.RequiresCommitSignatures)), + RequiresConversationResolution: githubv4.NewBoolean(githubv4.Boolean(data.RequiresConversationResolution)), + RequiresStatusChecks: githubv4.NewBoolean(githubv4.Boolean(data.RequiresStatusChecks)), + RequiresStrictStatusChecks: githubv4.NewBoolean(githubv4.Boolean(data.RequiresStrictStatusChecks)), + RestrictsPushes: githubv4.NewBoolean(githubv4.Boolean(data.RestrictsPushes)), + RestrictsReviewDismissals: githubv4.NewBoolean(githubv4.Boolean(data.RestrictsReviewDismissals)), + ReviewDismissalActorIDs: githubv4NewIDSlice(githubv4IDSlice(data.ReviewDismissalActorIDs)), } ctx := context.WithValue(context.Background(), ctxId, d.Id()) diff --git a/github/resource_github_branch_protection_test.go b/github/resource_github_branch_protection_test.go index 04f9f3c67f..9cfef20810 100644 --- a/github/resource_github_branch_protection_test.go +++ b/github/resource_github_branch_protection_test.go @@ -39,6 +39,94 @@ func TestAccGithubBranchProtection(t *testing.T) { resource.TestCheckResourceAttr( "github_branch_protection.test", "require_signed_commits", "false", ), + resource.TestCheckResourceAttr( + "github_branch_protection.test", "require_conversation_resolution", "false", + ), + resource.TestCheckResourceAttr( + "github_branch_protection.test", "required_status_checks.#", "0", + ), + resource.TestCheckResourceAttr( + "github_branch_protection.test", "required_pull_request_reviews.#", "0", + ), + resource.TestCheckResourceAttr( + "github_branch_protection.test", "push_restrictions.#", "0", + ), + ) + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + { + ResourceName: "github_branch_protection.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: importBranchProtectionByRepoName( + fmt.Sprintf("tf-acc-test-%s", randomID), "main", + ), + }, + { + ResourceName: "github_branch_protection.test", + ImportState: true, + ExpectError: regexp.MustCompile( + `Could not find a branch protection rule with the pattern 'no-such-pattern'\.`, + ), + ImportStateIdFunc: importBranchProtectionByRepoName( + fmt.Sprintf("tf-acc-test-%s", randomID), "no-such-pattern", + ), + }, + }, + }) + } + + t.Run("with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + + t.Run("with an individual account", func(t *testing.T) { + testCase(t, individual) + }) + + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + + }) + + t.Run("configures default settings when conversation resolution is true", func(t *testing.T) { + + config := fmt.Sprintf(` + + resource "github_repository" "test" { + name = "tf-acc-test-%s" + auto_init = true + } + + resource "github_branch_protection" "test" { + + repository_id = github_repository.test.node_id + pattern = "main" + + require_conversation_resolution = true + } + + `, randomID) + + check := resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr( + "github_branch_protection.test", "pattern", "main", + ), + resource.TestCheckResourceAttr( + "github_branch_protection.test", "require_signed_commits", "false", + ), + resource.TestCheckResourceAttr( + "github_branch_protection.test", "require_conversation_resolution", "true", + ), resource.TestCheckResourceAttr( "github_branch_protection.test", "required_status_checks.#", "0", ), diff --git a/github/util_v4_branch_protection.go b/github/util_v4_branch_protection.go index 73bda828fd..f63d1100f0 100644 --- a/github/util_v4_branch_protection.go +++ b/github/util_v4_branch_protection.go @@ -39,42 +39,44 @@ type BranchProtectionRule struct { ReviewDismissalAllowances struct { Nodes []DismissalActorTypes } `graphql:"reviewDismissalAllowances(first: 100)"` - AllowsDeletions githubv4.Boolean - AllowsForcePushes githubv4.Boolean - DismissesStaleReviews githubv4.Boolean - ID githubv4.ID - IsAdminEnforced githubv4.Boolean - Pattern githubv4.String - RequiredApprovingReviewCount githubv4.Int - RequiredStatusCheckContexts []githubv4.String - RequiresApprovingReviews githubv4.Boolean - RequiresCodeOwnerReviews githubv4.Boolean - RequiresCommitSignatures githubv4.Boolean - RequiresStatusChecks githubv4.Boolean - RequiresStrictStatusChecks githubv4.Boolean - RestrictsPushes githubv4.Boolean - RestrictsReviewDismissals githubv4.Boolean + AllowsDeletions githubv4.Boolean + AllowsForcePushes githubv4.Boolean + DismissesStaleReviews githubv4.Boolean + ID githubv4.ID + IsAdminEnforced githubv4.Boolean + Pattern githubv4.String + RequiredApprovingReviewCount githubv4.Int + RequiredStatusCheckContexts []githubv4.String + RequiresApprovingReviews githubv4.Boolean + RequiresCodeOwnerReviews githubv4.Boolean + RequiresCommitSignatures githubv4.Boolean + RequiresConversationResolution githubv4.Boolean + RequiresStatusChecks githubv4.Boolean + RequiresStrictStatusChecks githubv4.Boolean + RestrictsPushes githubv4.Boolean + RestrictsReviewDismissals githubv4.Boolean } type BranchProtectionResourceData struct { - AllowsDeletions bool - AllowsForcePushes bool - BranchProtectionRuleID string - DismissesStaleReviews bool - IsAdminEnforced bool - Pattern string - PushActorIDs []string - RepositoryID string - RequiredApprovingReviewCount int - RequiredStatusCheckContexts []string - RequiresApprovingReviews bool - RequiresCodeOwnerReviews bool - RequiresCommitSignatures bool - RequiresStatusChecks bool - RequiresStrictStatusChecks bool - RestrictsPushes bool - RestrictsReviewDismissals bool - ReviewDismissalActorIDs []string + AllowsDeletions bool + AllowsForcePushes bool + BranchProtectionRuleID string + DismissesStaleReviews bool + IsAdminEnforced bool + Pattern string + PushActorIDs []string + RepositoryID string + RequiredApprovingReviewCount int + RequiredStatusCheckContexts []string + RequiresApprovingReviews bool + RequiresCodeOwnerReviews bool + RequiresCommitSignatures bool + RequiresConversationResolution bool + RequiresStatusChecks bool + RequiresStrictStatusChecks bool + RestrictsPushes bool + RestrictsReviewDismissals bool + ReviewDismissalActorIDs []string } func branchProtectionResourceData(d *schema.ResourceData, meta interface{}) (BranchProtectionResourceData, error) { @@ -108,6 +110,10 @@ func branchProtectionResourceData(d *schema.ResourceData, meta interface{}) (Bra data.RequiresCommitSignatures = v.(bool) } + if v, ok := d.GetOk(PROTECTION_REQUIRES_CONVERSATION_RESOLUTION); ok { + data.RequiresConversationResolution = v.(bool) + } + if v, ok := d.GetOk(PROTECTION_REQUIRES_APPROVING_REVIEWS); ok { vL := v.([]interface{}) if len(vL) > 1 { diff --git a/github/util_v4_consts.go b/github/util_v4_consts.go index fc4a88d8bc..633e158ae3 100644 --- a/github/util_v4_consts.go +++ b/github/util_v4_consts.go @@ -1,21 +1,22 @@ package github const ( - PROTECTION_ALLOWS_DELETIONS = "allows_deletions" - PROTECTION_ALLOWS_FORCE_PUSHES = "allows_force_pushes" - PROTECTION_DISMISSES_STALE_REVIEWS = "dismiss_stale_reviews" - PROTECTION_IS_ADMIN_ENFORCED = "enforce_admins" - PROTECTION_PATTERN = "pattern" - PROTECTION_REQUIRED_APPROVING_REVIEW_COUNT = "required_approving_review_count" - PROTECTION_REQUIRED_STATUS_CHECK_CONTEXTS = "contexts" - PROTECTION_REQUIRES_APPROVING_REVIEWS = "required_pull_request_reviews" - PROTECTION_REQUIRES_CODE_OWNER_REVIEWS = "require_code_owner_reviews" - PROTECTION_REQUIRES_COMMIT_SIGNATURES = "require_signed_commits" - PROTECTION_REQUIRES_STATUS_CHECKS = "required_status_checks" - PROTECTION_REQUIRES_STRICT_STATUS_CHECKS = "strict" - PROTECTION_RESTRICTS_PUSHES = "push_restrictions" - PROTECTION_RESTRICTS_REVIEW_DISMISSALS = "restrict_dismissals" - PROTECTION_RESTRICTS_REVIEW_DISMISSERS = "dismissal_restrictions" + PROTECTION_ALLOWS_DELETIONS = "allows_deletions" + PROTECTION_ALLOWS_FORCE_PUSHES = "allows_force_pushes" + PROTECTION_DISMISSES_STALE_REVIEWS = "dismiss_stale_reviews" + PROTECTION_IS_ADMIN_ENFORCED = "enforce_admins" + PROTECTION_PATTERN = "pattern" + PROTECTION_REQUIRED_APPROVING_REVIEW_COUNT = "required_approving_review_count" + PROTECTION_REQUIRED_STATUS_CHECK_CONTEXTS = "contexts" + PROTECTION_REQUIRES_APPROVING_REVIEWS = "required_pull_request_reviews" + PROTECTION_REQUIRES_CODE_OWNER_REVIEWS = "require_code_owner_reviews" + PROTECTION_REQUIRES_COMMIT_SIGNATURES = "require_signed_commits" + PROTECTION_REQUIRES_CONVERSATION_RESOLUTION = "require_conversation_resolution" + PROTECTION_REQUIRES_STATUS_CHECKS = "required_status_checks" + PROTECTION_REQUIRES_STRICT_STATUS_CHECKS = "strict" + PROTECTION_RESTRICTS_PUSHES = "push_restrictions" + PROTECTION_RESTRICTS_REVIEW_DISMISSALS = "restrict_dismissals" + PROTECTION_RESTRICTS_REVIEW_DISMISSERS = "dismissal_restrictions" REPOSITORY_ID = "repository_id" ) diff --git a/website/docs/r/branch_protection.html.markdown b/website/docs/r/branch_protection.html.markdown index 013425b385..f2223e15fe 100644 --- a/website/docs/r/branch_protection.html.markdown +++ b/website/docs/r/branch_protection.html.markdown @@ -76,6 +76,7 @@ The following arguments are supported: * `pattern` - (Required) Identifies the protection rule pattern. * `enforce_admins` - (Optional) Boolean, setting this to `true` enforces status checks for repository administrators. * `require_signed_commits` - (Optional) Boolean, setting this to `true` requires all commits to be signed with GPG. +* `require_conversation_resolution` - (Optional) Boolean, setting this to `true` requires all conversations on code must be resolved before a pull request can be merged. * `required_status_checks` - (Optional) Enforce restrictions for required status checks. See [Required Status Checks](#required-status-checks) below for details. * `required_pull_request_reviews` - (Optional) Enforce restrictions for pull request reviews. See [Required Pull Request Reviews](#required-pull-request-reviews) below for details. * `push_restrictions` - (Optional) The list of actor IDs that may push to the branch.