Skip to content

Commit 09f3afb

Browse files
committed
lab: Add create and delete mr approval rules
Functionality exists to create and delete merge request approval rules, along with the viewing of merge request approval rules. Add the ability to create and delete merge request approval rules. Signed-off-by: Prarit Bhargava <[email protected]>
1 parent 154952f commit 09f3afb

File tree

3 files changed

+241
-36
lines changed

3 files changed

+241
-36
lines changed

cmd/mr_approval_rules.go

+118-36
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package cmd
22

33
import (
44
"fmt"
5+
"os"
6+
"strconv"
57

68
"github.com/MakeNowJust/heredoc/v2"
79
"github.com/spf13/cobra"
@@ -10,71 +12,151 @@ import (
1012
gitlab "github.com/xanzy/go-gitlab"
1113
)
1214

13-
func mrApprovalRuleShow(approvalState *gitlab.MergeRequestApprovalState) {
14-
for _, rule := range approvalState.Rules {
15-
fmt.Println("Rule:", rule.Name)
16-
if rule.Approved {
17-
fmt.Println(" Approved: Y")
18-
} else {
19-
fmt.Println(" Approved:")
20-
}
15+
func mrApprovalRuleShow(rule *gitlab.MergeRequestApprovalRule) {
16+
fmt.Println("Rule:", rule.Name)
17+
if rule.Approved {
18+
fmt.Println(" Approved: Y")
19+
} else {
20+
fmt.Println(" Approved:")
21+
}
2122

22-
if rule.RuleType == "regular" {
23-
users := ""
24-
for u, user := range rule.EligibleApprovers {
25-
users += fmt.Sprintf("%s", user.Username)
26-
if u != (len(rule.EligibleApprovers) - 1) {
27-
users +=","
28-
}
23+
if rule.RuleType == "regular" {
24+
users := ""
25+
for u, user := range rule.EligibleApprovers {
26+
users += fmt.Sprintf("%s", user.Username)
27+
if u != (len(rule.EligibleApprovers) - 1) {
28+
users += ","
2929
}
30-
fmt.Println(" Approvers:", users)
31-
} else if rule.RuleType == "any_approver" {
32-
fmt.Println(" Approvers: All eligible users")
3330
}
31+
fmt.Println(" Approvers:", users)
32+
} else if rule.RuleType == "any_approver" {
33+
fmt.Println(" Approvers: All eligible users")
34+
}
3435

35-
if rule.ApprovalsRequired > 0 {
36-
fmt.Printf(" Approvals: %d of %d\n", len(rule.ApprovedBy), rule.ApprovalsRequired)
37-
} else {
38-
fmt.Println(" Approvals: Optional")
39-
}
36+
if rule.ApprovalsRequired > 0 {
37+
fmt.Printf(" Approvals: %d of %d\n", len(rule.ApprovedBy), rule.ApprovalsRequired)
38+
} else {
39+
fmt.Println(" Approvals: Optional")
40+
}
4041

41-
if len(rule.ApprovedBy) > 0 {
42-
users := ""
43-
for u, user := range rule.ApprovedBy {
44-
users += fmt.Sprintf("%s", user.Username)
45-
if u != (len(rule.ApprovedBy) - 1) {
46-
users +=","
47-
}
42+
if len(rule.ApprovedBy) > 0 {
43+
users := ""
44+
for u, user := range rule.ApprovedBy {
45+
users += fmt.Sprintf("%s", user.Username)
46+
if u != (len(rule.ApprovedBy) - 1) {
47+
users += ","
4848
}
49-
fmt.Println(" Approved By:", users)
50-
} else {
51-
fmt.Println(" Approved By:")
5249
}
50+
fmt.Println(" Approved By:", users)
51+
} else {
52+
fmt.Println(" Approved By:")
5353
}
5454
}
5555

5656
var mrApprovalRuleCmd = &cobra.Command{
5757
Use: "approval-rule [remote] [<MR id or branch>]",
5858
Aliases: []string{},
5959
Example: heredoc.Doc(`
60-
lab mr approval-rule 1234`),
60+
lab mr approval-rule 1234
61+
lab mr approval-rule 1234 --name "Fancy rule name"
62+
lab mr approval-rule --create --name "Fancy rule name" --user "prarit" --user "zaquestion"
63+
lab mr approval-rule --create --name "Fancy rule name" --user "prarit" --user "zaquestion" --approvals-required 1
64+
lab mr approval-rule --delete "Fancy rule name"`),
6165
PersistentPreRun: labPersistentPreRun,
6266
Run: func(cmd *cobra.Command, args []string) {
6367
rn, id, err := parseArgsWithGitBranchMR(args)
6468
if err != nil {
6569
log.Fatal(err)
6670
}
6771

68-
approvalState, err := lab.GetMRApprovalState(rn, int(id))
72+
create, err := cmd.Flags().GetBool("create")
6973
if err != nil {
7074
log.Fatal(err)
7175
}
7276

77+
if create {
78+
_approvalsRequired, err := cmd.Flags().GetString("approvals-required")
79+
if err != nil {
80+
log.Fatal(err)
81+
}
82+
approvalsRequired, err := strconv.Atoi(_approvalsRequired)
83+
if err != nil {
84+
log.Fatal(err)
85+
}
86+
87+
name, err := cmd.Flags().GetString("name")
88+
if err != nil {
89+
log.Fatal(err)
90+
}
91+
if name == "" {
92+
fmt.Println("The --name option must be used with the --create option")
93+
os.Exit(1)
94+
}
95+
96+
users, err := cmd.Flags().GetStringSlice("user")
97+
if err != nil {
98+
log.Fatal(err)
99+
}
100+
userIDs := getUserIDs(users)
101+
102+
groups, err := cmd.Flags().GetStringSlice("group")
103+
if err != nil {
104+
log.Fatal(err)
105+
}
106+
groupIDs := getUserIDs(groups)
107+
108+
rule, err := lab.CreateMRApprovalRule(rn, approvalsRequired, int(id), name, 0, groupIDs, userIDs)
109+
if err != nil {
110+
log.Fatal(err)
111+
}
112+
113+
mrApprovalRuleShow(rule)
114+
return
115+
}
116+
117+
deleteRule, err := cmd.Flags().GetString("delete")
118+
if err != nil {
119+
log.Fatal(err)
120+
}
121+
if deleteRule != "" {
122+
msg, err := lab.DeleteMRApprovalRule(rn, deleteRule, int(id))
123+
if err != nil {
124+
log.Fatal(err)
125+
}
126+
fmt.Println(msg)
127+
return
128+
}
129+
73130
// default, no options just show the rules
74-
mrApprovalRuleShow(approvalState)
131+
approvalState, err := lab.GetMRApprovalState(rn, int(id))
132+
if err != nil {
133+
log.Fatal(err)
134+
}
135+
136+
name, err := cmd.Flags().GetString("name")
137+
if err != nil {
138+
log.Fatal(err)
139+
}
140+
141+
for _, rule := range approvalState.Rules {
142+
if name != "" {
143+
if rule.Name != name {
144+
continue
145+
}
146+
}
147+
mrApprovalRuleShow(rule)
148+
}
75149
},
76150
}
77151

78152
func init() {
153+
mrApprovalRuleCmd.Flags().BoolP("create", "c", false, "create a new rule. See 'create:' sub-options in help")
154+
mrApprovalRuleCmd.Flags().StringP("delete", "d", "", "delete the named rule")
155+
mrApprovalRuleCmd.Flags().String("approvals-required", "0", "create: number of approvals required")
156+
mrApprovalRuleCmd.Flags().StringP("name", "n", "", "create: rule name (can also be used to display a rule)")
157+
mrApprovalRuleCmd.Flags().String("project-rule-id", "", "create: project rule id for new rule")
158+
mrApprovalRuleCmd.Flags().StringSliceP("user", "u", []string{}, "create: approvers for new rule; can be used multiple times for different users")
159+
mrApprovalRuleCmd.Flags().StringSliceP("group", "g", []string{}, "create: groups for new rule; can be used multiple times for different groups")
160+
79161
mrCmd.AddCommand(mrApprovalRuleCmd)
80162
}

cmd/mr_approval_rules_test.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package cmd
2+
3+
import (
4+
"os/exec"
5+
"strings"
6+
"testing"
7+
8+
"github.com/acarl005/stripansi"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func Test_mrApprovalRule(t *testing.T) {
13+
repo := copyTestRepo(t)
14+
cmd1 := exec.Command(labBinaryPath, "mr", "approval-rule", "1447")
15+
cmd1.Dir = repo
16+
17+
a, err := cmd1.CombinedOutput()
18+
if err != nil {
19+
t.Log(string(a))
20+
t.Error(err)
21+
}
22+
23+
if strings.Contains(stripansi.Strip(string(a)), "Rule: lab mr approval test") {
24+
cleanup := exec.Command(labBinaryPath, "mr", "approval-rule", "1447", "--delete", "lab mr approval test")
25+
cleanup.Dir = repo
26+
b, err := cleanup.CombinedOutput()
27+
if err != nil {
28+
t.Log(string(b))
29+
t.Error(err)
30+
}
31+
}
32+
33+
// Test starts here
34+
cmd2 := exec.Command(labBinaryPath, "mr", "approval-rule", "1447")
35+
cmd2.Dir = repo
36+
37+
b, err := cmd2.CombinedOutput()
38+
if err != nil {
39+
t.Log(string(a))
40+
t.Log(string(b))
41+
t.Error(err)
42+
}
43+
44+
cmd2Output := string(b)
45+
cmd2Output = stripansi.Strip(cmd2Output)
46+
47+
cmd3 := exec.Command(labBinaryPath, "mr", "approval-rule", "1447", "--create", "--name", "lab mr approval test", "--user", "lab-testing")
48+
cmd3.Dir = repo
49+
50+
c, err := cmd3.CombinedOutput()
51+
if err != nil {
52+
t.Log(string(a))
53+
t.Log(string(b))
54+
t.Log(string(c))
55+
t.Error(err)
56+
}
57+
58+
cmd3Output := string(c)
59+
cmd3Output = stripansi.Strip(cmd3Output)
60+
61+
cmd4 := exec.Command(labBinaryPath, "mr", "approval-rule", "1447", "--delete", "lab mr approval test")
62+
cmd4.Dir = repo
63+
64+
d, err := cmd4.CombinedOutput()
65+
if err != nil {
66+
t.Log(string(a))
67+
t.Log(string(b))
68+
t.Log(string(c))
69+
t.Log(string(d))
70+
t.Error(err)
71+
}
72+
73+
cmd4Output := string(d)
74+
cmd4Output = stripansi.Strip(cmd4Output)
75+
76+
require.NotContains(t, cmd2Output, "Rule: lab mr approval test")
77+
require.Contains(t, cmd3Output, "Rule: lab mr approval test")
78+
require.Contains(t, cmd4Output, "Rule test deleted for MR !1447")
79+
}

internal/gitlab/gitlab.go

+44
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,50 @@ func GetMRApprovalState(projID interface{}, id int) (*gitlab.MergeRequestApprova
16101610
return state, err
16111611
}
16121612

1613+
// CreateMRApprovalRule creates a new approval rule for a merge request. Calling this without
1614+
// users or groups set defaults to creating an 'All Eligible Members' rule.
1615+
func CreateMRApprovalRule(projID interface{}, approvalsRequired int, mrID int, name string, projectRuleID int, groups []int, users []int) (*gitlab.MergeRequestApprovalRule, error) {
1616+
1617+
opts := &gitlab.CreateMergeRequestApprovalRuleOptions{
1618+
Name: &name,
1619+
ApprovalsRequired: &approvalsRequired,
1620+
ApprovalProjectRuleID: &projectRuleID,
1621+
UserIDs: &users,
1622+
GroupIDs: &groups,
1623+
}
1624+
1625+
rule, _, err := lab.MergeRequestApprovals.CreateApprovalRule(projID, mrID, opts)
1626+
if err != nil {
1627+
return nil, err
1628+
}
1629+
1630+
return rule, nil
1631+
}
1632+
1633+
// DeleteMRApprovalRule deletes an approval rule for a given MR
1634+
func DeleteMRApprovalRule(projID interface{}, name string, mrID int) (string, error) {
1635+
approvalState, err := GetMRApprovalState(projID, mrID)
1636+
if err != nil {
1637+
return "", err
1638+
}
1639+
1640+
nameID := 0
1641+
for r, rule := range approvalState.Rules {
1642+
if rule.Name == name {
1643+
nameID = rule.ID
1644+
break
1645+
}
1646+
if r == (len(approvalState.Rules) + 1) {
1647+
return "", fmt.Errorf("Could not find '%s' rule for MR !%d", name, mrID)
1648+
}
1649+
}
1650+
_, err = lab.MergeRequestApprovals.DeleteApprovalRule(projID, mrID, nameID)
1651+
if err != nil {
1652+
return "", fmt.Errorf("Rule %s not deleted for MR !%d", name, mrID)
1653+
}
1654+
return fmt.Sprintf("Rule %s deleted for MR !%d", name, mrID), nil
1655+
}
1656+
16131657
// ResolveMRDiscussion resolves a discussion (blocking thread) based on its ID
16141658
func ResolveMRDiscussion(projID interface{}, mrID int, discussionID string, noteID int) (string, error) {
16151659
opts := &gitlab.ResolveMergeRequestDiscussionOptions{

0 commit comments

Comments
 (0)