Skip to content

Commit 0e44a4a

Browse files
Ramon Rüttimanntimofurrer
authored andcommitted
Implement Gitlab Project Membership Datasource
1 parent 7275d5a commit 0e44a4a

File tree

6 files changed

+348
-4
lines changed

6 files changed

+348
-4
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+
- `id` (String) The ID of this resource.
47+
- `inherited` (Boolean) Return all project members including members through ancestor groups
48+
- `project_id` (Number) The ID of the project.
49+
- `query` (String) A query string to search for members
50+
51+
### Read-Only
52+
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func dataSourceGitlabGroupMembershipRead(ctx context.Context, d *schema.Resource
154154
d.Set("group_id", group.ID)
155155
d.Set("full_path", group.FullPath)
156156

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

159159
var optionsHash strings.Builder
160160
optionsHash.WriteString(strconv.Itoa(group.ID))
@@ -169,7 +169,7 @@ func dataSourceGitlabGroupMembershipRead(ctx context.Context, d *schema.Resource
169169
return nil
170170
}
171171

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

175175
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+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package provider
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
8+
)
9+
10+
func TestAccDataSourceGitlabProjectMembership_basic(t *testing.T) {
11+
testAccCheck(t)
12+
13+
project := testAccCreateProject(t)
14+
users := testAccCreateUsers(t, 1)
15+
testAccAddProjectMembers(t, project.ID, users)
16+
17+
resource.Test(t, resource.TestCase{
18+
PreCheck: func() { testAccPreCheck(t) },
19+
ProviderFactories: providerFactories,
20+
Steps: []resource.TestStep{
21+
{
22+
Config: testAccDataSourceGitlabProjectMembership(project.ID),
23+
Check: resource.ComposeTestCheckFunc(
24+
// Members is 2 because the user owning the token is always added to the project
25+
resource.TestCheckResourceAttr("data.gitlab_project_membership.foo", "members.#", "2"),
26+
resource.TestCheckResourceAttr("data.gitlab_project_membership.foo", "members.1.username", users[0].Username),
27+
resource.TestCheckResourceAttr("data.gitlab_project_membership.foo", "members.1.access_level", "developer"),
28+
),
29+
},
30+
},
31+
})
32+
}
33+
34+
func TestAccDataSourceGitlabProjectMembership_pagination(t *testing.T) {
35+
testAccCheck(t)
36+
37+
userCount := 21
38+
39+
project := testAccCreateProject(t)
40+
users := testAccCreateUsers(t, userCount)
41+
testAccAddProjectMembers(t, project.ID, users)
42+
43+
resource.Test(t, resource.TestCase{
44+
PreCheck: func() { testAccPreCheck(t) },
45+
ProviderFactories: providerFactories,
46+
Steps: []resource.TestStep{
47+
{
48+
Config: testAccDataSourceGitlabProjectMembership(project.ID),
49+
// one more for the user owning the token, which is always added to the project.
50+
Check: resource.TestCheckResourceAttr("data.gitlab_project_membership.foo", "members.#", fmt.Sprintf("%d", userCount+1)),
51+
},
52+
},
53+
})
54+
}
55+
56+
func testAccDataSourceGitlabProjectMembership(projectID int) string {
57+
return fmt.Sprintf(`
58+
data "gitlab_project_membership" "foo" {
59+
project_id = "%d"
60+
}`, projectID)
61+
}

0 commit comments

Comments
 (0)