Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/resources/project_approval_rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
```

<!-- schema generated by tfplugindocs -->
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions examples/resources/gitlab_project_approval_rule/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
18 changes: 18 additions & 0 deletions internal/provider/resource_gitlab_project_approval_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ 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"
)

// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-project-level-rule
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.",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
82 changes: 78 additions & 4 deletions internal/provider/resource_gitlab_project_approval_rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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},
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down