diff --git a/docs/resources/project_approval_rule.md b/docs/resources/project_approval_rule.md index 6f8a83a02..71ae3a687 100644 --- a/docs/resources/project_approval_rule.md +++ b/docs/resources/project_approval_rule.md @@ -54,6 +54,14 @@ resource "gitlab_project_approval_rule" "example-three" { approvals_required = 3 user_ids = [for user in data.gitlab_user.users : user.id] } + +# Example using `approval_rule` +resource "gitlab_branch_protection" "any-approver" { + project = 5 + name = "Any name" + rule_type = "any_approver" + approvals_required = 1 +} ``` @@ -70,6 +78,7 @@ resource "gitlab_project_approval_rule" "example-three" { - **group_ids** (Set of Number) A list of group IDs whose members can approve of the merge request. - **id** (String) The ID of this resource. - **protected_branch_ids** (Set of Number) A list of protected branch IDs (not branch names) for which the rule applies. +- **rule_type** (String) String, defaults to 'regular'. The type of rule. `any_approver` is a pre-configured default rule with `approvals_required` at `0`. Valid values are `regular`, `any_approver`. - **user_ids** (Set of Number) A list of specific User IDs to add to the list of approvers. ## Import diff --git a/examples/resources/gitlab_project_approval_rule/resource.tf b/examples/resources/gitlab_project_approval_rule/resource.tf index a36f5783b..22125e47e 100644 --- a/examples/resources/gitlab_project_approval_rule/resource.tf +++ b/examples/resources/gitlab_project_approval_rule/resource.tf @@ -36,3 +36,11 @@ resource "gitlab_project_approval_rule" "example-three" { approvals_required = 3 user_ids = [for user in data.gitlab_user.users : user.id] } + +# Example using `approval_rule` +resource "gitlab_branch_protection" "any-approver" { + project = 5 + name = "Any name" + rule_type = "any_approver" + approvals_required = 1 +} diff --git a/go.mod b/go.mod index 8b2065b58..0a3488d5b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1 github.com/mitchellh/hashstructure v1.1.0 github.com/onsi/gomega v1.18.1 - github.com/xanzy/go-gitlab v0.55.1 + github.com/xanzy/go-gitlab v0.56.0 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect google.golang.org/api v0.34.0 // indirect ) diff --git a/go.sum b/go.sum index b1807843b..1d38bc3c4 100644 --- a/go.sum +++ b/go.sum @@ -344,8 +344,8 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/xanzy/go-gitlab v0.55.1 h1:IgX/DS9buV0AUz8fuJPQkdl0fQGfBiAsAHxpun8sNhg= -github.com/xanzy/go-gitlab v0.55.1/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM= +github.com/xanzy/go-gitlab v0.56.0 h1:/QHBvk3IKVNwvXB/UOWVb5J6VCN6r2bg9/WxjUbFY/0= +github.com/xanzy/go-gitlab v0.56.0/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/provider/resource_gitlab_project_approval_rule.go b/internal/provider/resource_gitlab_project_approval_rule.go index ed85ab9cd..0247b9389 100644 --- a/internal/provider/resource_gitlab_project_approval_rule.go +++ b/internal/provider/resource_gitlab_project_approval_rule.go @@ -3,11 +3,13 @@ package provider import ( "context" "errors" + "fmt" "log" "strconv" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" gitlab "github.com/xanzy/go-gitlab" ) @@ -15,6 +17,10 @@ import ( var errApprovalRuleNotFound = errors.New("approval rule not found") var _ = registerResource("gitlab_project_approval_rule", func() *schema.Resource { + var validRuleTypeValues = []string{ + "regular", + "any_approver", + } return &schema.Resource{ Description: "This resource allows you to create and manage multiple approval rules for your GitLab projects. For further information on approval rules, consult the [gitlab documentation](https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals).\n\n" + "-> This feature requires GitLab Premium.", @@ -43,6 +49,13 @@ var _ = registerResource("gitlab_project_approval_rule", func() *schema.Resource Type: schema.TypeInt, Required: true, }, + "rule_type": { + Description: fmt.Sprintf("String, defaults to 'regular'. The type of rule. `any_approver` is a pre-configured default rule with `approvals_required` at `0`. Valid values are %s.", renderValueListForDocs(validRuleTypeValues)), + Type: schema.TypeString, + Optional: true, + Default: "regular", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice(validRuleTypeValues, false)), + }, "user_ids": { Description: "A list of specific User IDs to add to the list of approvers.", Type: schema.TypeSet, @@ -77,6 +90,10 @@ func resourceGitlabProjectApprovalRuleCreate(ctx context.Context, d *schema.Reso ProtectedBranchIDs: expandProtectedBranchIDs(d.Get("protected_branch_ids")), } + if v, ok := d.GetOk("rule_type"); ok { + options.RuleType = gitlab.String(v.(string)) + } + project := d.Get("project").(string) log.Printf("[DEBUG] Project %s create gitlab project-level rule %+v", project, options) @@ -115,6 +132,7 @@ func resourceGitlabProjectApprovalRuleRead(ctx context.Context, d *schema.Resour d.Set("name", rule.Name) d.Set("approvals_required", rule.ApprovalsRequired) + d.Set("rule_type", rule.RuleType) if err := d.Set("group_ids", flattenApprovalRuleGroupIDs(rule.Groups)); err != nil { return diag.FromErr(err) diff --git a/internal/provider/resource_gitlab_project_approval_rule_test.go b/internal/provider/resource_gitlab_project_approval_rule_test.go index 8849a3f09..3bede1be1 100644 --- a/internal/provider/resource_gitlab_project_approval_rule_test.go +++ b/internal/provider/resource_gitlab_project_approval_rule_test.go @@ -43,12 +43,13 @@ func TestAccGitLabProjectApprovalRule_basic(t *testing.T) { Steps: []resource.TestStep{ // Create rule { - Config: testAccGitlabProjectApprovalRuleConfig(project.ID, 3, projectUsers[0].ID, groups[0].ID, branches[0].ID), + Config: testAccGitlabProjectApprovalRuleConfig(project.ID, 3, "regular", projectUsers[0].ID, groups[0].ID, branches[0].ID), Check: resource.ComposeTestCheckFunc( testAccCheckGitlabProjectApprovalRuleExists("gitlab_project_approval_rule.foo", &projectApprovalRule), testAccCheckGitlabProjectApprovalRuleAttributes(&projectApprovalRule, &testAccGitlabProjectApprovalRuleExpectedAttributes{ Name: "foo", ApprovalsRequired: 3, + RuleType: "regular", EligibleApproverIDs: []int{currentUser.ID, projectUsers[0].ID, group0Users[0].ID}, GroupIDs: []int{groups[0].ID}, ProtectedBranchIDs: []int{branches[0].ID}, @@ -57,12 +58,82 @@ func TestAccGitLabProjectApprovalRule_basic(t *testing.T) { }, // Update rule { - Config: testAccGitlabProjectApprovalRuleConfig(project.ID, 2, projectUsers[1].ID, groups[1].ID, branches[1].ID), + Config: testAccGitlabProjectApprovalRuleConfig(project.ID, 2, "regular", projectUsers[1].ID, groups[1].ID, branches[1].ID), Check: resource.ComposeTestCheckFunc( testAccCheckGitlabProjectApprovalRuleExists("gitlab_project_approval_rule.foo", &projectApprovalRule), testAccCheckGitlabProjectApprovalRuleAttributes(&projectApprovalRule, &testAccGitlabProjectApprovalRuleExpectedAttributes{ Name: "foo", ApprovalsRequired: 2, + RuleType: "regular", + EligibleApproverIDs: []int{currentUser.ID, projectUsers[1].ID, group1Users[0].ID}, + GroupIDs: []int{groups[1].ID}, + ProtectedBranchIDs: []int{branches[1].ID}, + }), + ), + }, + // Verify import + { + ResourceName: "gitlab_project_approval_rule.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGitLabProjectApprovalRule_AnyApprover(t *testing.T) { + // Set up project, groups, users, and branches to use in the test. + + testAccCheck(t) + testAccCheckEE(t) + + // Need to get the current user (usually the admin) because they are automatically added as group members, and we + // will need the user ID for our assertions later. + currentUser := testAccCurrentUser(t) + + project := testAccCreateProject(t) + projectUsers := testAccCreateUsers(t, 2) + branches := testAccCreateProtectedBranches(t, project, 2) + groups := testAccCreateGroups(t, 2) + group0Users := testAccCreateUsers(t, 1) + group1Users := testAccCreateUsers(t, 1) + + testAccAddProjectMembers(t, project.ID, projectUsers) // Users must belong to the project for rules to work. + testAccAddGroupMembers(t, groups[0].ID, group0Users) + testAccAddGroupMembers(t, groups[1].ID, group1Users) + + // Terraform test starts here. + + var projectApprovalRule gitlab.ProjectApprovalRule + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGitlabProjectApprovalRuleDestroy(project.ID), + Steps: []resource.TestStep{ + // Create rule + { + Config: testAccGitlabProjectApprovalRuleConfig(project.ID, 3, "any_approver", projectUsers[0].ID, groups[0].ID, branches[0].ID), + Check: resource.ComposeTestCheckFunc( + testAccCheckGitlabProjectApprovalRuleExists("gitlab_project_approval_rule.foo", &projectApprovalRule), + testAccCheckGitlabProjectApprovalRuleAttributes(&projectApprovalRule, &testAccGitlabProjectApprovalRuleExpectedAttributes{ + Name: "foo", + ApprovalsRequired: 3, + RuleType: "any_approver", + EligibleApproverIDs: []int{currentUser.ID, projectUsers[0].ID, group0Users[0].ID}, + GroupIDs: []int{groups[0].ID}, + ProtectedBranchIDs: []int{branches[0].ID}, + }), + ), + }, + // Update rule + { + Config: testAccGitlabProjectApprovalRuleConfig(project.ID, 2, "any_approver", projectUsers[1].ID, groups[1].ID, branches[1].ID), + Check: resource.ComposeTestCheckFunc( + testAccCheckGitlabProjectApprovalRuleExists("gitlab_project_approval_rule.foo", &projectApprovalRule), + testAccCheckGitlabProjectApprovalRuleAttributes(&projectApprovalRule, &testAccGitlabProjectApprovalRuleExpectedAttributes{ + Name: "foo", + ApprovalsRequired: 2, + RuleType: "any_approver", EligibleApproverIDs: []int{currentUser.ID, projectUsers[1].ID, group1Users[0].ID}, GroupIDs: []int{groups[1].ID}, ProtectedBranchIDs: []int{branches[1].ID}, @@ -82,6 +153,7 @@ func TestAccGitLabProjectApprovalRule_basic(t *testing.T) { type testAccGitlabProjectApprovalRuleExpectedAttributes struct { Name string ApprovalsRequired int + RuleType string EligibleApproverIDs []int GroupIDs []int ProtectedBranchIDs []int @@ -92,6 +164,7 @@ func testAccCheckGitlabProjectApprovalRuleAttributes(got *gitlab.ProjectApproval return InterceptGomegaFailure(func() { Expect(got.Name).To(Equal(want.Name), "name") Expect(got.ApprovalsRequired).To(Equal(want.ApprovalsRequired), "approvals_required") + Expect(got.RuleType).To(Equal(want.RuleType), "rule_type") var approverIDs []int for _, approver := range got.EligibleApprovers { @@ -114,16 +187,17 @@ func testAccCheckGitlabProjectApprovalRuleAttributes(got *gitlab.ProjectApproval } } -func testAccGitlabProjectApprovalRuleConfig(project, approvals, userID, groupID, protectedBranchID int) string { +func testAccGitlabProjectApprovalRuleConfig(project int, approvals int, rule_type string, userID int, groupID int, protectedBranchID int) string { return fmt.Sprintf(` resource "gitlab_project_approval_rule" "foo" { project = %d name = "foo" approvals_required = %d + rule_type = %s user_ids = [%d] group_ids = [%d] protected_branch_ids = [%d] -}`, project, approvals, userID, groupID, protectedBranchID) +}`, project, approvals, rule_type, userID, groupID, protectedBranchID) } func testAccCheckGitlabProjectApprovalRuleExists(n string, projectApprovalRule *gitlab.ProjectApprovalRule) resource.TestCheckFunc {