Skip to content

Commit 30304ba

Browse files
author
Jordan Caussat
committed
Update project resource: add shared_with_groups option
Fix: error when updating shared groups but not the project Update gitlab_project resource: use new DeleteSharedProjectFromGroup method Update project: rename group_access to group_access_level Update project: remove plan diff for shared_with_group expire_at parameter and add data validation for group_access_level Update resource project: remove shared_with_group.expires_at Update resource project: improve handling of shared_with_groups Update resource project: add shared_with_group option tests
1 parent d1ad087 commit 30304ba

File tree

3 files changed

+319
-4
lines changed

3 files changed

+319
-4
lines changed

gitlab/resource_gitlab_project.go

Lines changed: 165 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,18 @@ 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+
log.Printf("[DEBUG] project shared with group %v", option)
177+
_, err := client.Projects.ShareProjectWithGroup(project.ID, option)
178+
if err != nil {
179+
return err
180+
}
181+
}
182+
}
183+
149184
d.SetId(fmt.Sprintf("%d", project.ID))
150185

151186
return resourceGitlabProjectRead(d, meta)
@@ -211,11 +246,16 @@ func resourceGitlabProjectUpdate(d *schema.ResourceData, meta interface{}) error
211246
options.SnippetsEnabled = gitlab.Bool(d.Get("snippets_enabled").(bool))
212247
}
213248

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

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

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

gitlab/resource_gitlab_project_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 from 1 group
114+
{
115+
Config: testAccGitlabProjectSharedWithGroup(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+
}{{0, "", 30}},
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)