Skip to content

Commit be4666c

Browse files
authored
Merge pull request #62 from jorcau/project_add_shared-with-group_option
Update project resource: add shared_with_group option
2 parents bd44e9e + f26bf6c commit be4666c

File tree

4 files changed

+326
-5
lines changed

4 files changed

+326
-5
lines changed

gitlab/resource_gitlab_project.go

Lines changed: 162 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,28 @@ func resourceGitlabProject() *schema.Resource {
9393
Type: schema.TypeString,
9494
Computed: true,
9595
},
96+
"shared_with_groups": {
97+
Type: schema.TypeList,
98+
Optional: true,
99+
Elem: &schema.Resource{
100+
Schema: map[string]*schema.Schema{
101+
"group_id": {
102+
Type: schema.TypeInt,
103+
Required: true,
104+
},
105+
"group_access_level": {
106+
Type: schema.TypeString,
107+
Required: true,
108+
ValidateFunc: validation.StringInSlice([]string{
109+
"guest", "reporter", "developer", "master"}, false),
110+
},
111+
"group_name": {
112+
Type: schema.TypeString,
113+
Computed: true,
114+
},
115+
},
116+
},
117+
},
96118
},
97119
}
98120
}
@@ -114,6 +136,7 @@ func resourceGitlabProjectSetToState(d *schema.ResourceData, project *gitlab.Pro
114136
d.Set("http_url_to_repo", project.HTTPURLToRepo)
115137
d.Set("web_url", project.WebURL)
116138
d.Set("runners_token", project.RunnersToken)
139+
d.Set("shared_with_groups", flattenSharedWithGroupsOptions(project))
117140
}
118141

119142
func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error {
@@ -146,6 +169,17 @@ func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error
146169
return err
147170
}
148171

172+
if v, ok := d.GetOk("shared_with_groups"); ok {
173+
options := expandSharedWithGroupsOptions(v.([]interface{}))
174+
175+
for _, option := range options {
176+
_, err := client.Projects.ShareProjectWithGroup(project.ID, option)
177+
if err != nil {
178+
return err
179+
}
180+
}
181+
}
182+
149183
d.SetId(fmt.Sprintf("%d", project.ID))
150184

151185
return resourceGitlabProjectRead(d, meta)
@@ -211,11 +245,16 @@ func resourceGitlabProjectUpdate(d *schema.ResourceData, meta interface{}) error
211245
options.SnippetsEnabled = gitlab.Bool(d.Get("snippets_enabled").(bool))
212246
}
213247

214-
log.Printf("[DEBUG] update gitlab project %s", d.Id())
248+
if *options != (gitlab.EditProjectOptions{}) {
249+
log.Printf("[DEBUG] update gitlab project %s", d.Id())
250+
_, _, err := client.Projects.EditProject(d.Id(), options)
251+
if err != nil {
252+
return err
253+
}
254+
}
215255

216-
_, _, err := client.Projects.EditProject(d.Id(), options)
217-
if err != nil {
218-
return err
256+
if d.HasChange("shared_with_groups") {
257+
updateSharedWithGroups(d, meta)
219258
}
220259

221260
return resourceGitlabProjectRead(d, meta)
@@ -258,3 +297,122 @@ func resourceGitlabProjectDelete(d *schema.ResourceData, meta interface{}) error
258297
}
259298
return nil
260299
}
300+
301+
func expandSharedWithGroupsOptions(d []interface{}) []*gitlab.ShareWithGroupOptions {
302+
shareWithGroupOptionsList := []*gitlab.ShareWithGroupOptions{}
303+
304+
for _, config := range d {
305+
data := config.(map[string]interface{})
306+
307+
groupAccess := accessLevelNameToValue[data["group_access_level"].(string)]
308+
309+
shareWithGroupOptions := &gitlab.ShareWithGroupOptions{
310+
GroupID: gitlab.Int(data["group_id"].(int)),
311+
GroupAccess: &groupAccess,
312+
}
313+
314+
shareWithGroupOptionsList = append(shareWithGroupOptionsList,
315+
shareWithGroupOptions)
316+
}
317+
318+
return shareWithGroupOptionsList
319+
}
320+
321+
func flattenSharedWithGroupsOptions(project *gitlab.Project) []interface{} {
322+
sharedWithGroups := project.SharedWithGroups
323+
sharedWithGroupsList := []interface{}{}
324+
325+
for _, option := range sharedWithGroups {
326+
values := map[string]interface{}{
327+
"group_id": option.GroupID,
328+
"group_access_level": accessLevelValueToName[gitlab.AccessLevelValue(
329+
option.GroupAccessLevel)],
330+
"group_name": option.GroupName,
331+
}
332+
333+
sharedWithGroupsList = append(sharedWithGroupsList, values)
334+
}
335+
336+
return sharedWithGroupsList
337+
}
338+
339+
func findGroupProjectSharedWith(target *gitlab.ShareWithGroupOptions,
340+
groups []*gitlab.ShareWithGroupOptions) (*gitlab.ShareWithGroupOptions, int, error) {
341+
for i, group := range groups {
342+
if *group.GroupID == *target.GroupID {
343+
return group, i, nil
344+
}
345+
}
346+
347+
return nil, 0, fmt.Errorf("group not found")
348+
}
349+
350+
func getGroupsProjectSharedWith(project *gitlab.Project) []*gitlab.ShareWithGroupOptions {
351+
sharedGroups := []*gitlab.ShareWithGroupOptions{}
352+
353+
for _, group := range project.SharedWithGroups {
354+
sharedGroups = append(sharedGroups, &gitlab.ShareWithGroupOptions{
355+
GroupID: gitlab.Int(group.GroupID),
356+
GroupAccess: gitlab.AccessLevel(gitlab.AccessLevelValue(
357+
group.GroupAccessLevel)),
358+
})
359+
}
360+
361+
return sharedGroups
362+
}
363+
364+
func updateSharedWithGroups(d *schema.ResourceData, meta interface{}) error {
365+
client := meta.(*gitlab.Client)
366+
367+
var groupsToUnshare []*gitlab.ShareWithGroupOptions
368+
var groupsToShare []*gitlab.ShareWithGroupOptions
369+
370+
// Get target groups from the TF config and current groups from Gitlab server
371+
targetGroups := expandSharedWithGroupsOptions(
372+
d.Get("shared_with_groups").([]interface{}))
373+
project, _, err := client.Projects.GetProject(d.Id())
374+
if err != nil {
375+
return err
376+
}
377+
currentGroups := getGroupsProjectSharedWith(project)
378+
379+
for _, targetGroup := range targetGroups {
380+
currentGroup, index, err := findGroupProjectSharedWith(targetGroup, currentGroups)
381+
382+
// If no corresponding group is found, it must be added
383+
if err != nil {
384+
groupsToShare = append(groupsToShare, targetGroup)
385+
continue
386+
}
387+
388+
// If group is different it must be deleted and added again
389+
if *targetGroup.GroupAccess != *currentGroup.GroupAccess {
390+
groupsToShare = append(groupsToShare, targetGroup)
391+
groupsToUnshare = append(groupsToUnshare, targetGroup)
392+
}
393+
394+
// Remove currentGroup from from list
395+
currentGroups = append(currentGroups[:index], currentGroups[index+1:]...)
396+
}
397+
398+
// All groups still present in currentGroup must be deleted
399+
groupsToUnshare = append(groupsToUnshare, currentGroups...)
400+
401+
// Unshare groups to delete and update
402+
for _, group := range groupsToUnshare {
403+
_, err := client.Projects.DeleteSharedProjectFromGroup(d.Id(), *group.GroupID)
404+
if err != nil {
405+
return err
406+
}
407+
}
408+
409+
// Share groups to add and update
410+
for _, group := range groupsToShare {
411+
_, err := client.Projects.ShareProjectWithGroup(d.Id(), group)
412+
if err != nil {
413+
return err
414+
}
415+
}
416+
417+
return nil
418+
}

gitlab/resource_gitlab_project_test.go

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/hashicorp/terraform/helper/acctest"
88
"github.com/hashicorp/terraform/helper/resource"
99
"github.com/hashicorp/terraform/terraform"
10-
"github.com/xanzy/go-gitlab"
10+
gitlab "github.com/xanzy/go-gitlab"
1111
)
1212

1313
func TestAccGitlabProject_basic(t *testing.T) {
@@ -66,6 +66,72 @@ func TestAccGitlabProject_basic(t *testing.T) {
6666
}),
6767
),
6868
},
69+
//Update the project to share the project with a group
70+
{
71+
Config: testAccGitlabProjectSharedWithGroup(rInt),
72+
Check: resource.ComposeTestCheckFunc(
73+
testAccCheckGitlabProjectExists("gitlab_project.foo", &project),
74+
testAccCheckGitlabProjectAttributes(&project, &testAccGitlabProjectExpectedAttributes{
75+
Name: fmt.Sprintf("foo-%d", rInt),
76+
Path: fmt.Sprintf("foo.%d", rInt),
77+
Description: "Terraform acceptance tests",
78+
IssuesEnabled: true,
79+
MergeRequestsEnabled: true,
80+
WikiEnabled: true,
81+
SnippetsEnabled: true,
82+
Visibility: gitlab.PublicVisibility,
83+
SharedWithGroups: []struct {
84+
GroupID int
85+
GroupName string
86+
GroupAccessLevel int
87+
}{{0, "", 30}},
88+
}),
89+
),
90+
},
91+
//Update the project to share the project with more groups
92+
{
93+
Config: testAccGitlabProjectSharedWithGroup2(rInt),
94+
Check: resource.ComposeTestCheckFunc(
95+
testAccCheckGitlabProjectExists("gitlab_project.foo", &project),
96+
testAccCheckGitlabProjectAttributes(&project, &testAccGitlabProjectExpectedAttributes{
97+
Name: fmt.Sprintf("foo-%d", rInt),
98+
Path: fmt.Sprintf("foo.%d", rInt),
99+
Description: "Terraform acceptance tests",
100+
IssuesEnabled: true,
101+
MergeRequestsEnabled: true,
102+
WikiEnabled: true,
103+
SnippetsEnabled: true,
104+
Visibility: gitlab.PublicVisibility,
105+
SharedWithGroups: []struct {
106+
GroupID int
107+
GroupName string
108+
GroupAccessLevel int
109+
}{{0, "", 10}, {0, "", 30}},
110+
}),
111+
),
112+
},
113+
//Update the project to unshare the project
114+
{
115+
Config: testAccGitlabProjectConfig(rInt),
116+
Check: resource.ComposeTestCheckFunc(
117+
testAccCheckGitlabProjectExists("gitlab_project.foo", &project),
118+
testAccCheckGitlabProjectAttributes(&project, &testAccGitlabProjectExpectedAttributes{
119+
Name: fmt.Sprintf("foo-%d", rInt),
120+
Path: fmt.Sprintf("foo.%d", rInt),
121+
Description: "Terraform acceptance tests",
122+
IssuesEnabled: true,
123+
MergeRequestsEnabled: true,
124+
WikiEnabled: true,
125+
SnippetsEnabled: true,
126+
Visibility: gitlab.PublicVisibility,
127+
SharedWithGroups: []struct {
128+
GroupID int
129+
GroupName string
130+
GroupAccessLevel int
131+
}{},
132+
}),
133+
),
134+
},
69135
},
70136
})
71137
}
@@ -142,6 +208,11 @@ type testAccGitlabProjectExpectedAttributes struct {
142208
WikiEnabled bool
143209
SnippetsEnabled bool
144210
Visibility gitlab.VisibilityValue
211+
SharedWithGroups []struct {
212+
GroupID int
213+
GroupName string
214+
GroupAccessLevel int
215+
}
145216
}
146217

147218
func testAccCheckGitlabProjectAttributes(project *gitlab.Project, want *testAccGitlabProjectExpectedAttributes) resource.TestCheckFunc {
@@ -180,6 +251,12 @@ func testAccCheckGitlabProjectAttributes(project *gitlab.Project, want *testAccG
180251
return fmt.Errorf("got visibility %q; want %q", project.Visibility, want.Visibility)
181252
}
182253

254+
for i, group := range project.SharedWithGroups {
255+
if group.GroupAccessLevel != want.SharedWithGroups[i].GroupAccessLevel {
256+
return fmt.Errorf("got shared with groups access: %d; want %d", group.GroupAccessLevel, want.SharedWithGroups[i].GroupAccessLevel)
257+
}
258+
}
259+
183260
return nil
184261
}
185262
}
@@ -258,3 +335,64 @@ resource "gitlab_project" "foo" {
258335
}
259336
`, rInt, rInt)
260337
}
338+
339+
func testAccGitlabProjectSharedWithGroup(rInt int) string {
340+
return fmt.Sprintf(`
341+
resource "gitlab_project" "foo" {
342+
name = "foo-%d"
343+
path = "foo.%d"
344+
description = "Terraform acceptance tests"
345+
visibility_level = "public"
346+
347+
shared_with_groups = [
348+
{
349+
group_id = "${gitlab_group.foo.id}"
350+
group_access_level = "developer"
351+
},
352+
]
353+
}
354+
355+
resource "gitlab_group" "foo" {
356+
name = "foo-name-%d"
357+
path = "foo-path-%d"
358+
description = "Terraform acceptance tests!"
359+
visibility_level = "public"
360+
}
361+
`, rInt, rInt, rInt, rInt)
362+
}
363+
364+
func testAccGitlabProjectSharedWithGroup2(rInt int) string {
365+
return fmt.Sprintf(`
366+
resource "gitlab_project" "foo" {
367+
name = "foo-%d"
368+
path = "foo.%d"
369+
description = "Terraform acceptance tests"
370+
visibility_level = "public"
371+
372+
shared_with_groups = [
373+
{
374+
group_id = "${gitlab_group.foo.id}"
375+
group_access_level = "guest"
376+
},
377+
{
378+
group_id = "${gitlab_group.foo2.id}"
379+
group_access_level = "developer"
380+
},
381+
]
382+
}
383+
384+
resource "gitlab_group" "foo" {
385+
name = "foo-name-%d"
386+
path = "foo-path-%d"
387+
description = "Terraform acceptance tests!"
388+
visibility_level = "public"
389+
}
390+
391+
resource "gitlab_group" "foo2" {
392+
name = "foo2-name-%d"
393+
path = "foo2-path-%d"
394+
description = "Terraform acceptance tests!"
395+
visibility_level = "public"
396+
}
397+
`, rInt, rInt, rInt, rInt, rInt, rInt)
398+
}

gitlab/util.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ import (
1010
"time"
1111
)
1212

13+
var accessLevelNameToValue = map[string]gitlab.AccessLevelValue{
14+
"guest": gitlab.GuestPermissions,
15+
"reporter": gitlab.ReporterPermissions,
16+
"developer": gitlab.DeveloperPermissions,
17+
"master": gitlab.MasterPermissions,
18+
"owner": gitlab.OwnerPermission,
19+
}
20+
21+
var accessLevelValueToName = map[gitlab.AccessLevelValue]string{
22+
gitlab.GuestPermissions: "guest",
23+
gitlab.ReporterPermissions: "reporter",
24+
gitlab.DeveloperPermissions: "developer",
25+
gitlab.MasterPermissions: "master",
26+
gitlab.OwnerPermission: "owner",
27+
}
28+
1329
// copied from ../github/util.go
1430
func validateValueFunc(values []string) schema.SchemaValidateFunc {
1531
return func(v interface{}, k string) (we []string, errors []error) {

0 commit comments

Comments
 (0)