Skip to content

Commit

Permalink
Feature: branch protection conversation resolution (integrations#904)
Browse files Browse the repository at this point in the history
* feat: add conversation resolution variable on branch protection v3

* chore: update githubv4 package

* feat: add conversation resolution variable on branch protection
  • Loading branch information
josh-barker authored Dec 3, 2021
1 parent 7988ac3 commit d76333c
Show file tree
Hide file tree
Showing 15 changed files with 793 additions and 104 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ website/vendor
# Test exclusions
!command/test-fixtures/**/*.tfstate
!command/test-fixtures/**/.terraform/

# do not commit secrets
.env
80 changes: 46 additions & 34 deletions github/resource_github_branch_protection.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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,
Expand Down Expand Up @@ -140,23 +145,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()
Expand Down Expand Up @@ -226,6 +232,11 @@ func resourceGithubBranchProtectionRead(d *schema.ResourceData, meta interface{}
log.Printf("[WARN] Problem setting '%s' in %s %s branch protection (%s)", PROTECTION_REQUIRES_LINEAR_HISTORY, 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 {
Expand Down Expand Up @@ -260,23 +271,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())
Expand Down
88 changes: 88 additions & 0 deletions github/resource_github_branch_protection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),
Expand Down
6 changes: 6 additions & 0 deletions github/resource_github_branch_protection_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ func resourceGithubBranchProtectionV3() *schema.Resource {
Optional: true,
Default: false,
},
"require_conversation_resolution": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"etag": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -237,6 +242,7 @@ func resourceGithubBranchProtectionV3Read(d *schema.ResourceData, meta interface
d.Set("repository", repoName)
d.Set("branch", branch)
d.Set("enforce_admins", githubProtection.GetEnforceAdmins().Enabled)
d.Set("require_conversation_resolution", githubProtection.GetRequiredConversationResolution().Enabled)

if err := flattenAndSetRequiredStatusChecks(d, githubProtection); err != nil {
return fmt.Errorf("Error setting required_status_checks: %v", err)
Expand Down
76 changes: 76 additions & 0 deletions github/resource_github_branch_protection_v3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,81 @@ func TestAccGithubBranchProtectionV3_defaults(t *testing.T) {
resource.TestCheckResourceAttr(
"github_branch_protection_v3.test", "require_signed_commits", "false",
),
resource.TestCheckResourceAttr(
"github_branch_protection_v3.test", "require_conversation_resolution", "false",
),
resource.TestCheckResourceAttr(
"github_branch_protection_v3.test", "required_status_checks.#", "0",
),
resource.TestCheckResourceAttr(
"github_branch_protection_v3.test", "required_pull_request_reviews.#", "0",
),
resource.TestCheckResourceAttr(
"github_branch_protection_v3.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,
},
},
})
}

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) {
t.Skip("individual account not supported for this operation")
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})

})
}

func TestAccGithubBranchProtectionV3_conversation_resolution(t *testing.T) {

randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

t.Run("configures default settings when empty", func(t *testing.T) {

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_branch_protection_v3" "test" {
repository = github_repository.test.name
branch = "main"
require_conversation_resolution = true
}
`, randomID)

check := resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(
"github_branch_protection_v3.test", "branch", "main",
),
resource.TestCheckResourceAttr(
"github_branch_protection_v3.test", "require_signed_commits", "false",
),
resource.TestCheckResourceAttr(
"github_branch_protection_v3.test", "require_conversation_resolution", "true",
),
resource.TestCheckResourceAttr(
"github_branch_protection_v3.test", "required_status_checks.#", "0",
),
Expand Down Expand Up @@ -75,6 +150,7 @@ func TestAccGithubBranchProtectionV3_defaults(t *testing.T) {

})
}

func TestAccGithubBranchProtectionV3_required_status_checks(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

Expand Down
3 changes: 2 additions & 1 deletion github/resource_github_branch_protection_v3_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (

func buildProtectionRequest(d *schema.ResourceData) (*github.ProtectionRequest, error) {
req := &github.ProtectionRequest{
EnforceAdmins: d.Get("enforce_admins").(bool),
EnforceAdmins: d.Get("enforce_admins").(bool),
RequiredConversationResolution: github.Bool(d.Get("require_conversation_resolution").(bool)),
}

rsc, err := expandRequiredStatusChecks(d)
Expand Down
Loading

0 comments on commit d76333c

Please sign in to comment.