Skip to content

Commit 348b949

Browse files
authored
Merge pull request #593 from tommyknows/project-membership
Add new data source gitlab_project_membership
2 parents 58e8286 + 4219550 commit 348b949

File tree

6 files changed

+356
-18
lines changed

6 files changed

+356
-18
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "gitlab_project_membership Data Source - terraform-provider-gitlab"
4+
subcategory: ""
5+
description: |-
6+
The gitlab_project_membership data source allows to list and filter all members of a project specified by either its id or full path.
7+
-> Note exactly one of projectid or fullpath must be provided.
8+
Upstream API: GitLab REST API docs https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project
9+
---
10+
11+
# gitlab_project_membership (Data Source)
12+
13+
The `gitlab_project_membership` data source allows to list and filter all members of a project specified by either its id or full path.
14+
15+
-> **Note** exactly one of project_id or full_path must be provided.
16+
17+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project)
18+
19+
## Example Usage
20+
21+
```terraform
22+
# By project's ID
23+
data "gitlab_project_membership" "example" {
24+
project_id = 123
25+
}
26+
27+
# By project's full path
28+
data "gitlab_project_membership" "example" {
29+
full_path = "foo/bar"
30+
}
31+
32+
# Get members of a project including all members
33+
# through ancestor groups
34+
data "gitlab_project_membership" "example" {
35+
project_id = 123
36+
inherited = true
37+
}
38+
```
39+
40+
<!-- schema generated by tfplugindocs -->
41+
## Schema
42+
43+
### Optional
44+
45+
- `full_path` (String) The full path of the project.
46+
- `inherited` (Boolean) Return all project members including members through ancestor groups
47+
- `project_id` (Number) The ID of the project.
48+
- `query` (String) A query string to search for members
49+
50+
### Read-Only
51+
52+
- `id` (String) The ID of this resource.
53+
- `members` (List of Object) The list of project members. (see [below for nested schema](#nestedatt--members))
54+
55+
<a id="nestedatt--members"></a>
56+
### Nested Schema for `members`
57+
58+
Read-Only:
59+
60+
- `access_level` (String)
61+
- `avatar_url` (String)
62+
- `expires_at` (String)
63+
- `id` (Number)
64+
- `name` (String)
65+
- `state` (String)
66+
- `username` (String)
67+
- `web_url` (String)
68+
69+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# By project's ID
2+
data "gitlab_project_membership" "example" {
3+
project_id = 123
4+
}
5+
6+
# By project's full path
7+
data "gitlab_project_membership" "example" {
8+
full_path = "foo/bar"
9+
}
10+
11+
# Get members of a project including all members
12+
# through ancestor groups
13+
data "gitlab_project_membership" "example" {
14+
project_id = 123
15+
inherited = true
16+
}

internal/provider/data_source_gitlab_group_membership.go

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,18 @@ var _ = registerDataSource("gitlab_group_membership", func() *schema.Resource {
2222
ReadContext: dataSourceGitlabGroupMembershipRead,
2323
Schema: map[string]*schema.Schema{
2424
"group_id": {
25-
Description: "The ID of the group.",
26-
Type: schema.TypeInt,
27-
Computed: true,
28-
Optional: true,
29-
ConflictsWith: []string{
30-
"full_path",
31-
},
25+
Description: "The ID of the group.",
26+
Type: schema.TypeInt,
27+
Computed: true,
28+
Optional: true,
29+
ExactlyOneOf: []string{"group_id", "full_path"},
3230
},
3331
"full_path": {
34-
Description: "The full path of the group.",
35-
Type: schema.TypeString,
36-
Computed: true,
37-
Optional: true,
38-
ConflictsWith: []string{
39-
"group_id",
40-
},
32+
Description: "The full path of the group.",
33+
Type: schema.TypeString,
34+
Computed: true,
35+
Optional: true,
36+
ExactlyOneOf: []string{"group_id", "full_path"},
4137
},
4238
"access_level": {
4339
Description: "Only return members with the desired access level. Acceptable values are: `guest`, `reporter`, `developer`, `maintainer`, `owner`.",
@@ -154,7 +150,7 @@ func dataSourceGitlabGroupMembershipRead(ctx context.Context, d *schema.Resource
154150
d.Set("group_id", group.ID)
155151
d.Set("full_path", group.FullPath)
156152

157-
d.Set("members", flattenGitlabMembers(d, allGms)) // lintignore: XR004 // TODO: Resolve this tfproviderlint issue
153+
d.Set("members", flattenGitlabGroupMembers(d, allGms)) // lintignore: XR004 // TODO: Resolve this tfproviderlint issue
158154

159155
var optionsHash strings.Builder
160156
optionsHash.WriteString(strconv.Itoa(group.ID))
@@ -169,7 +165,7 @@ func dataSourceGitlabGroupMembershipRead(ctx context.Context, d *schema.Resource
169165
return nil
170166
}
171167

172-
func flattenGitlabMembers(d *schema.ResourceData, members []*gitlab.GroupMember) []interface{} {
168+
func flattenGitlabGroupMembers(d *schema.ResourceData, members []*gitlab.GroupMember) []interface{} {
173169
membersList := []interface{}{}
174170

175171
var filterAccessLevel gitlab.AccessLevelValue = gitlab.NoPermissions

internal/provider/data_source_gitlab_group_membership_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1212
)
1313

14-
func TestAccDataSourceGitlabMembership_basic(t *testing.T) {
14+
func TestAccDataSourceGitlabGroupMembership_basic(t *testing.T) {
1515
rInt := acctest.RandInt()
1616

1717
resource.ParallelTest(t, resource.TestCase{
@@ -46,7 +46,7 @@ func TestAccDataSourceGitlabMembership_basic(t *testing.T) {
4646
})
4747
}
4848

49-
func TestAccDataSourceGitlabMembership_pagination(t *testing.T) {
49+
func TestAccDataSourceGitlabGroupMembership_pagination(t *testing.T) {
5050
userCount := 21
5151

5252
group := testAccCreateGroups(t, 1)[0]
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
"github.com/xanzy/go-gitlab"
13+
)
14+
15+
var _ = registerDataSource("gitlab_project_membership", func() *schema.Resource {
16+
return &schema.Resource{
17+
Description: `The ` + "`gitlab_project_membership`" + ` data source allows to list and filter all members of a project specified by either its id or full path.
18+
19+
-> **Note** exactly one of project_id or full_path must be provided.
20+
21+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project)`,
22+
ReadContext: dataSourceGitlabProjectMembershipRead,
23+
Schema: map[string]*schema.Schema{
24+
"project_id": {
25+
Description: "The ID of the project.",
26+
Type: schema.TypeInt,
27+
Computed: true,
28+
Optional: true,
29+
ExactlyOneOf: []string{"project_id", "full_path"},
30+
},
31+
"full_path": {
32+
Description: "The full path of the project.",
33+
Type: schema.TypeString,
34+
Computed: true,
35+
Optional: true,
36+
ExactlyOneOf: []string{"project_id", "full_path"},
37+
},
38+
"query": {
39+
Description: "A query string to search for members",
40+
Type: schema.TypeString,
41+
Optional: true,
42+
},
43+
"inherited": {
44+
Description: "Return all project members including members through ancestor groups",
45+
Type: schema.TypeBool,
46+
Optional: true,
47+
},
48+
"members": {
49+
Description: "The list of project members.",
50+
Type: schema.TypeList,
51+
Computed: true,
52+
Elem: &schema.Resource{
53+
Schema: map[string]*schema.Schema{
54+
"id": {
55+
Description: "The unique id assigned to the user by the gitlab server.",
56+
Type: schema.TypeInt,
57+
Computed: true,
58+
},
59+
"username": {
60+
Description: "The username of the user.",
61+
Type: schema.TypeString,
62+
Computed: true,
63+
},
64+
"name": {
65+
Description: "The name of the user.",
66+
Type: schema.TypeString,
67+
Computed: true,
68+
},
69+
"state": {
70+
Description: "Whether the user is active or blocked.",
71+
Type: schema.TypeString,
72+
Computed: true,
73+
},
74+
"avatar_url": {
75+
Description: "The avatar URL of the user.",
76+
Type: schema.TypeString,
77+
Computed: true,
78+
},
79+
"web_url": {
80+
Description: "User's website URL.",
81+
Type: schema.TypeString,
82+
Computed: true,
83+
},
84+
"access_level": {
85+
Description: "The level of access to the group.",
86+
Type: schema.TypeString,
87+
Computed: true,
88+
},
89+
"expires_at": {
90+
Description: "Expiration date for the group membership.",
91+
Type: schema.TypeString,
92+
Computed: true,
93+
},
94+
},
95+
},
96+
},
97+
},
98+
}
99+
})
100+
101+
func dataSourceGitlabProjectMembershipRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
102+
client := meta.(*gitlab.Client)
103+
104+
var project *gitlab.Project
105+
var err error
106+
107+
log.Printf("[INFO] Reading Gitlab project")
108+
109+
var pid interface{}
110+
if v, ok := d.GetOk("project_id"); ok {
111+
pid = v.(int)
112+
} else if v, ok := d.GetOk("full_path"); ok {
113+
pid = v.(string)
114+
} else {
115+
return diag.Errorf("one and only one of project_id or full_path must be set. This is a provider bug, please report upstream at https://github.com/gitlabhq/terraform-provider-gitlab/issues")
116+
}
117+
118+
// Get project to have both, the `project_id` and `full_path` for setting the state.
119+
project, _, err = client.Projects.GetProject(pid, nil)
120+
if err != nil {
121+
return diag.FromErr(err)
122+
}
123+
124+
var query *string
125+
if q, ok := d.GetOk("query"); ok {
126+
s := q.(string)
127+
query = &s
128+
}
129+
130+
log.Printf("[INFO] Reading Gitlab project memberships")
131+
132+
// Get project memberships
133+
listOptions := &gitlab.ListProjectMembersOptions{
134+
Query: query,
135+
ListOptions: gitlab.ListOptions{
136+
PerPage: 20,
137+
Page: 1,
138+
},
139+
}
140+
141+
listMembers := client.ProjectMembers.ListProjectMembers
142+
if inherited, ok := d.GetOk("inherited"); ok && inherited.(bool) {
143+
listMembers = client.ProjectMembers.ListAllProjectMembers
144+
}
145+
146+
var allPMs []*gitlab.ProjectMember
147+
for listOptions.Page != 0 {
148+
pms, resp, err := listMembers(project.ID, listOptions, gitlab.WithContext(ctx))
149+
if err != nil {
150+
return diag.FromErr(err)
151+
}
152+
153+
allPMs = append(allPMs, pms...)
154+
listOptions.Page = resp.NextPage
155+
}
156+
157+
var optionsHash strings.Builder
158+
optionsHash.WriteString(strconv.Itoa(project.ID))
159+
160+
if data, ok := d.GetOk("query"); ok {
161+
optionsHash.WriteString(data.(string))
162+
}
163+
164+
id := schema.HashString(optionsHash.String())
165+
d.SetId(fmt.Sprintf("%d", id))
166+
167+
d.Set("project_id", project.ID)
168+
d.Set("full_path", project.PathWithNamespace)
169+
170+
if err := d.Set("members", flattenGitlabProjectMembers(d, allPMs)); err != nil {
171+
return diag.FromErr(err)
172+
}
173+
174+
return nil
175+
}
176+
177+
func flattenGitlabProjectMembers(d *schema.ResourceData, members []*gitlab.ProjectMember) []interface{} {
178+
membersList := make([]interface{}, 0, len(members))
179+
for _, member := range members {
180+
values := map[string]interface{}{
181+
"id": member.ID,
182+
"username": member.Username,
183+
"name": member.Name,
184+
"state": member.State,
185+
"avatar_url": member.AvatarURL,
186+
"web_url": member.WebURL,
187+
"access_level": accessLevelValueToName[gitlab.AccessLevelValue(member.AccessLevel)],
188+
}
189+
190+
if member.ExpiresAt != nil {
191+
values["expires_at"] = member.ExpiresAt.String()
192+
}
193+
194+
membersList = append(membersList, values)
195+
}
196+
197+
return membersList
198+
}

0 commit comments

Comments
 (0)