Skip to content

Commit 90f30c5

Browse files
Julien Pivottochriswgerberbtyy77c
authored
Approval (#250)
* Adds a Project Approval Rule resource Co-authored-by: Julien Pivotto <roidelapluie> Co-authored-by: Chris Gerber <[email protected]> Co-authored-by: Emily Ring <[email protected]>
1 parent fa6e4f5 commit 90f30c5

4 files changed

+592
-0
lines changed
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# gitlab\_project\_approval\_rule
2+
3+
This resource allows you to create and manage multiple approval rules for your GitLab
4+
projects. For further information on approval rules, consult the [gitlab
5+
documentation](https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals).
6+
7+
-> This feature requires a GitLab Starter account or above.
8+
9+
## Example Usage
10+
11+
```hcl
12+
resource "gitlab_project_approval_rule" "example-one" {
13+
project = 5
14+
name = "Example Rule 1"
15+
approvals_required = 3
16+
user_ids = [50, 500]
17+
group_ids = [51]
18+
}
19+
20+
resource "gitlab_project_approval_rule" "example-two" {
21+
project = 5
22+
name = "Example Rule 2"
23+
approvals_required = 1
24+
user_ids = []
25+
group_ids = [52]
26+
}
27+
```
28+
29+
## Argument Reference
30+
31+
The following arguments are supported:
32+
33+
* `project` - (Required, string) The name or id of the project to add the approval rules.
34+
35+
* `name` - (Required) The name of the approval rule.
36+
37+
* `approvals_required` - (Required) The number of approvals required for this rule.
38+
39+
* `user_ids` - (Optional) A list of specific User IDs to add to the list of approvers.
40+
41+
* `group_ids` - (Optional) A list of group IDs who's members can approve of the merge request
42+
43+
## Import
44+
45+
GitLab project approval rules can be imported using an id consisting of `project-id:rule-id`, e.g.
46+
47+
```
48+
$ terraform import gitlab_project_approval_rule.example "12345:6"
49+
```

gitlab/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func Provider() terraform.ResourceProvider {
9393
"gitlab_instance_cluster": resourceGitlabInstanceCluster(),
9494
"gitlab_project_mirror": resourceGitlabProjectMirror(),
9595
"gitlab_project_level_mr_approvals": resourceGitlabProjectLevelMRApprovals(),
96+
"gitlab_project_approval_rule": resourceGitlabProjectApprovalRule(),
9697
},
9798
}
9899

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package gitlab
2+
3+
import (
4+
"errors"
5+
"log"
6+
"strconv"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
9+
gitlab "github.com/xanzy/go-gitlab"
10+
)
11+
12+
// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-project-level-rule
13+
var errApprovalRuleNotFound = errors.New("approval rule not found")
14+
15+
func resourceGitlabProjectApprovalRule() *schema.Resource {
16+
return &schema.Resource{
17+
Create: resourceGitlabProjectApprovalRuleCreate,
18+
Read: resourceGitlabProjectApprovalRuleRead,
19+
Update: resourceGitlabProjectApprovalRuleUpdate,
20+
Delete: resourceGitlabProjectApprovalRuleDelete,
21+
Importer: &schema.ResourceImporter{
22+
State: schema.ImportStatePassthrough,
23+
},
24+
Schema: map[string]*schema.Schema{
25+
"project": {
26+
Type: schema.TypeString,
27+
ForceNew: true,
28+
Required: true,
29+
},
30+
"name": {
31+
Type: schema.TypeString,
32+
Required: true,
33+
},
34+
"approvals_required": {
35+
Type: schema.TypeInt,
36+
Required: true,
37+
},
38+
"user_ids": {
39+
Type: schema.TypeSet,
40+
Optional: true,
41+
Elem: &schema.Schema{Type: schema.TypeInt},
42+
Set: schema.HashInt,
43+
},
44+
"group_ids": {
45+
Type: schema.TypeSet,
46+
Optional: true,
47+
Elem: &schema.Schema{Type: schema.TypeInt},
48+
Set: schema.HashInt,
49+
},
50+
},
51+
}
52+
}
53+
54+
func resourceGitlabProjectApprovalRuleCreate(d *schema.ResourceData, meta interface{}) error {
55+
options := gitlab.CreateProjectLevelRuleOptions{
56+
Name: gitlab.String(d.Get("name").(string)),
57+
ApprovalsRequired: gitlab.Int(d.Get("approvals_required").(int)),
58+
UserIDs: expandApproverIds(d.Get("user_ids")),
59+
GroupIDs: expandApproverIds(d.Get("group_ids")),
60+
}
61+
62+
project := d.Get("project").(string)
63+
64+
log.Printf("[DEBUG] Project %s create gitlab project-level rule %+v", project, options)
65+
66+
client := meta.(*gitlab.Client)
67+
68+
rule, _, err := client.Projects.CreateProjectApprovalRule(project, &options)
69+
if err != nil {
70+
return err
71+
}
72+
73+
ruleIDString := strconv.Itoa(rule.ID)
74+
75+
d.SetId(buildTwoPartID(&project, &ruleIDString))
76+
77+
return resourceGitlabProjectApprovalRuleRead(d, meta)
78+
}
79+
80+
func resourceGitlabProjectApprovalRuleRead(d *schema.ResourceData, meta interface{}) error {
81+
log.Printf("[DEBUG] read gitlab project-level rule %s", d.Id())
82+
83+
projectID, _, err := parseTwoPartID(d.Id())
84+
if err != nil {
85+
return err
86+
}
87+
d.Set("project", projectID)
88+
89+
rule, err := getApprovalRuleByID(meta.(*gitlab.Client), d.Id())
90+
if err != nil {
91+
if errors.Is(err, errApprovalRuleNotFound) {
92+
d.SetId("")
93+
return nil
94+
}
95+
return err
96+
}
97+
98+
d.Set("name", rule.Name)
99+
d.Set("approvals_required", rule.ApprovalsRequired)
100+
101+
if err := d.Set("group_ids", flattenApprovalRuleGroupIDs(rule.Groups)); err != nil {
102+
return err
103+
}
104+
105+
if err := d.Set("user_ids", flattenApprovalRuleUserIDs(rule.Users)); err != nil {
106+
return err
107+
}
108+
109+
return nil
110+
}
111+
112+
func resourceGitlabProjectApprovalRuleUpdate(d *schema.ResourceData, meta interface{}) error {
113+
projectID, ruleID, err := parseTwoPartID(d.Id())
114+
if err != nil {
115+
return err
116+
}
117+
118+
ruleIDInt, err := strconv.Atoi(ruleID)
119+
if err != nil {
120+
return err
121+
}
122+
123+
options := gitlab.UpdateProjectLevelRuleOptions{
124+
Name: gitlab.String(d.Get("name").(string)),
125+
ApprovalsRequired: gitlab.Int(d.Get("approvals_required").(int)),
126+
UserIDs: expandApproverIds(d.Get("user_ids")),
127+
GroupIDs: expandApproverIds(d.Get("group_ids")),
128+
}
129+
130+
log.Printf("[DEBUG] Project %s update gitlab project-level approval rule %s", projectID, *options.Name)
131+
132+
client := meta.(*gitlab.Client)
133+
134+
_, _, err = client.Projects.UpdateProjectApprovalRule(projectID, ruleIDInt, &options)
135+
if err != nil {
136+
return err
137+
}
138+
139+
return resourceGitlabProjectApprovalRuleRead(d, meta)
140+
}
141+
142+
func resourceGitlabProjectApprovalRuleDelete(d *schema.ResourceData, meta interface{}) error {
143+
project, ruleID, err := parseTwoPartID(d.Id())
144+
if err != nil {
145+
return err
146+
}
147+
148+
ruleIDInt, err := strconv.Atoi(ruleID)
149+
if err != nil {
150+
return err
151+
}
152+
153+
log.Printf("[DEBUG] Project %s delete gitlab project-level approval rule %d", project, ruleIDInt)
154+
155+
client := meta.(*gitlab.Client)
156+
157+
_, err = client.Projects.DeleteProjectApprovalRule(project, ruleIDInt)
158+
if err != nil {
159+
return err
160+
}
161+
162+
return nil
163+
}
164+
165+
// getApprovalRuleByID checks the list of rules and finds the one that matches our rule ID.
166+
func getApprovalRuleByID(client *gitlab.Client, id string) (*gitlab.ProjectApprovalRule, error) {
167+
projectID, ruleID, err := parseTwoPartID(id)
168+
if err != nil {
169+
return nil, err
170+
}
171+
172+
ruleIDInt, err := strconv.Atoi(ruleID)
173+
if err != nil {
174+
return nil, err
175+
}
176+
177+
log.Printf("[DEBUG] read approval rules for project %s", projectID)
178+
179+
rules, _, err := client.Projects.GetProjectApprovalRules(projectID)
180+
if err != nil {
181+
return nil, err
182+
}
183+
184+
for _, r := range rules {
185+
if r.ID == ruleIDInt {
186+
log.Printf("[DEBUG] found project-level rule %+v", r)
187+
return r, nil
188+
}
189+
}
190+
191+
return nil, errApprovalRuleNotFound
192+
}
193+
194+
// flattenApprovalRuleUserIDs flattens a list of approval user ids into a list
195+
// of ints for storage in state.
196+
func flattenApprovalRuleUserIDs(users []*gitlab.BasicUser) []int {
197+
var userIDs []int
198+
199+
for _, user := range users {
200+
userIDs = append(userIDs, user.ID)
201+
}
202+
203+
return userIDs
204+
}
205+
206+
// flattenApprovalRuleGroupIDs flattens a list of approval group ids into a list
207+
// of ints for storage in state.
208+
func flattenApprovalRuleGroupIDs(groups []*gitlab.Group) []int {
209+
var groupIDs []int
210+
211+
for _, group := range groups {
212+
groupIDs = append(groupIDs, group.ID)
213+
}
214+
215+
return groupIDs
216+
}
217+
218+
// expandApproverIds Expands an interface into a list of ints to read from state.
219+
func expandApproverIds(ids interface{}) []int {
220+
var approverIDs []int
221+
222+
for _, id := range ids.(*schema.Set).List() {
223+
approverIDs = append(approverIDs, id.(int))
224+
}
225+
226+
return approverIDs
227+
}

0 commit comments

Comments
 (0)