Skip to content

Commit 8be3fde

Browse files
authored
Merge pull request #1118 from PatrickRice-KSC/add_current_user_data_source
Add current_user data source
2 parents dbc86a6 + c46b93a commit 8be3fde

File tree

8 files changed

+325
-0
lines changed

8 files changed

+325
-0
lines changed

docs/data-sources/current_user.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "gitlab_current_user Data Source - terraform-provider-gitlab"
4+
subcategory: ""
5+
description: |-
6+
The gitlab_current_user data source allows details of the current user (determined by token provider attribute) to be retrieved.
7+
Upstream API: GitLab GraphQL API docs https://docs.gitlab.com/ee/api/graphql/reference/index.html#querycurrentuser
8+
---
9+
10+
# gitlab_current_user (Data Source)
11+
12+
The `gitlab_current_user` data source allows details of the current user (determined by `token` provider attribute) to be retrieved.
13+
14+
**Upstream API**: [GitLab GraphQL API docs](https://docs.gitlab.com/ee/api/graphql/reference/index.html#querycurrentuser)
15+
16+
## Example Usage
17+
18+
```terraform
19+
data "gitlab_current_user" "example" {}
20+
```
21+
22+
<!-- schema generated by tfplugindocs -->
23+
## Schema
24+
25+
### Read-Only
26+
27+
- `bot` (Boolean) Indicates if the user is a bot.
28+
- `global_id` (String) Global ID of the user. This is in the form of a GraphQL globally unique ID.
29+
- `global_namespace_id` (String) Personal namespace of the user. This is in the form of a GraphQL globally unique ID.
30+
- `group_count` (Number) Group count for the user.
31+
- `id` (String) ID of the user.
32+
- `name` (String) Human-readable name of the user. Returns **** if the user is a project bot and the requester does not have permission to view the project.
33+
- `namespace_id` (String) Personal namespace of the user.
34+
- `public_email` (String) User’s public email.
35+
- `username` (String) Username of the user. Unique within this instance of GitLab.
36+
37+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data "gitlab_current_user" "example" {}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/xanzy/go-gitlab"
11+
)
12+
13+
var _ = registerDataSource("gitlab_current_user", func() *schema.Resource {
14+
return &schema.Resource{
15+
Description: `The ` + "`gitlab_current_user`" + ` data source allows details of the current user (determined by ` + "`token`" + ` provider attribute) to be retrieved.
16+
17+
**Upstream API**: [GitLab GraphQL API docs](https://docs.gitlab.com/ee/api/graphql/reference/index.html#querycurrentuser)`,
18+
19+
ReadContext: dataSourceGitlabCurrentUserRead,
20+
Schema: map[string]*schema.Schema{
21+
"id": {
22+
Description: "ID of the user.",
23+
Type: schema.TypeString,
24+
Computed: true,
25+
},
26+
"global_id": {
27+
Description: "Global ID of the user. This is in the form of a GraphQL globally unique ID.",
28+
Type: schema.TypeString,
29+
Computed: true,
30+
},
31+
"username": {
32+
Description: "Username of the user. Unique within this instance of GitLab.",
33+
Type: schema.TypeString,
34+
Computed: true,
35+
},
36+
"name": {
37+
Description: "Human-readable name of the user. Returns **** if the user is a project bot and the requester does not have permission to view the project.",
38+
Type: schema.TypeString,
39+
Computed: true,
40+
},
41+
"bot": {
42+
Description: "Indicates if the user is a bot.",
43+
Type: schema.TypeBool,
44+
Computed: true,
45+
},
46+
"group_count": {
47+
Description: "Group count for the user.",
48+
Type: schema.TypeInt,
49+
Computed: true,
50+
},
51+
"namespace_id": {
52+
Description: "Personal namespace of the user.",
53+
Type: schema.TypeString,
54+
Computed: true,
55+
},
56+
"global_namespace_id": {
57+
Description: "Personal namespace of the user. This is in the form of a GraphQL globally unique ID.",
58+
Type: schema.TypeString,
59+
Computed: true,
60+
},
61+
"public_email": {
62+
Description: "User’s public email.",
63+
Type: schema.TypeString,
64+
Computed: true,
65+
},
66+
},
67+
}
68+
})
69+
70+
func dataSourceGitlabCurrentUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
71+
client := meta.(*gitlab.Client)
72+
73+
query := GraphQLQuery{
74+
`query {currentUser {name, bot, groupCount, id, namespace{id}, publicEmail, username}}`,
75+
}
76+
log.Printf("[DEBUG] executing GraphQL Query %s to retrieve current user", query.Query)
77+
78+
var response CurrentUserResponse
79+
if _, err := SendGraphQLRequest(ctx, client, query, &response); err != nil {
80+
return diag.FromErr(err)
81+
}
82+
83+
userID, err := extractIIDFromGlobalID(response.Data.CurrentUser.ID)
84+
if err != nil {
85+
return diag.FromErr(err)
86+
}
87+
88+
namespaceID, err := extractIIDFromGlobalID(response.Data.CurrentUser.Namespace.ID)
89+
if err != nil {
90+
return diag.FromErr(err)
91+
}
92+
93+
d.SetId(fmt.Sprintf("%d", userID))
94+
d.Set("global_id", response.Data.CurrentUser.ID)
95+
d.Set("username", response.Data.CurrentUser.Username)
96+
d.Set("name", response.Data.CurrentUser.Name)
97+
d.Set("bot", response.Data.CurrentUser.Bot)
98+
d.Set("group_count", response.Data.CurrentUser.GroupCount)
99+
d.Set("namespace_id", fmt.Sprintf("%d", namespaceID))
100+
d.Set("global_namespace_id", response.Data.CurrentUser.Namespace.ID)
101+
d.Set("public_email", response.Data.CurrentUser.PublicEmail)
102+
103+
return nil
104+
}
105+
106+
// Struct representing current user based on the input API token
107+
type CurrentUserResponse struct {
108+
Data struct {
109+
CurrentUser GraphQLUser `json:"currentUser"`
110+
} `json:"data"`
111+
}
112+
113+
type GraphQLUser struct {
114+
Name string `json:"name"`
115+
Bot bool `json:"bot"`
116+
GroupCount int `json:"groupCount"`
117+
ID string `json:"id"` // This is purposefully a string, as in some APIs it comes back as a globally unique ID
118+
Namespace struct {
119+
ID string `json:"id"`
120+
} `json:"namespace"`
121+
PublicEmail string `json:"publicEmail"`
122+
Username string `json:"username"`
123+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//go:build acceptance
2+
// +build acceptance
3+
4+
package provider
5+
6+
import (
7+
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
10+
"github.com/xanzy/go-gitlab"
11+
)
12+
13+
func TestAccDataSourceGitlabCurrentUser_basic(t *testing.T) {
14+
//The root user has no public email by default, set the public email so it shows up properly.
15+
_, _, _ = testGitlabClient.Users.ModifyUser(1, &gitlab.ModifyUserOptions{
16+
// The public email MUST match an email on record for the user, or it gets a bad request.
17+
PublicEmail: gitlab.String("[email protected]"),
18+
})
19+
20+
t.Cleanup(func() {
21+
_, _, _ = testGitlabClient.Users.ModifyUser(1, &gitlab.ModifyUserOptions{
22+
//Set back to the empty state on test completion.
23+
PublicEmail: gitlab.String(""),
24+
})
25+
})
26+
27+
resource.Test(t, resource.TestCase{
28+
ProviderFactories: providerFactories,
29+
Steps: []resource.TestStep{
30+
{
31+
Config: `data "gitlab_current_user" "this" {}`,
32+
Check: resource.ComposeTestCheckFunc(
33+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "id", "1"),
34+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "global_id", "gid://gitlab/User/1"),
35+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "name", "Administrator"),
36+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "username", "root"),
37+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "bot", "false"),
38+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "group_count", "2"),
39+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "namespace_id", "1"),
40+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "global_namespace_id", "gid://gitlab/Namespaces::UserNamespace/1"),
41+
resource.TestCheckResourceAttr("data.gitlab_current_user.this", "public_email", "[email protected]"),
42+
),
43+
},
44+
},
45+
})
46+
}

internal/provider/graphql_helper.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
6+
"github.com/xanzy/go-gitlab"
7+
)
8+
9+
// Helper method for modifying client requests appropriately for sending a GraphQL call instead of a REST call.
10+
func SendGraphQLRequest(ctx context.Context, client *gitlab.Client, query GraphQLQuery, response interface{}) (interface{}, error) {
11+
request, err := client.NewRequest("POST", "", query, nil)
12+
if err != nil {
13+
return nil, err
14+
}
15+
// Overwrite the path of the existing request, as otherwise the go-gitlab client appends /api/v4 instead.
16+
request.URL.Path = "/api/graphql"
17+
if _, err = client.Do(request, response); err != nil {
18+
return nil, err
19+
}
20+
return response, nil
21+
}
22+
23+
// Represents a GraphQL call to the API. All GraphQL calls are a string passed to the "query" parameter, so they should be included here.
24+
type GraphQLQuery struct {
25+
Query string `json:"query"`
26+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//go:build acceptance
2+
// +build acceptance
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
"log"
9+
"testing"
10+
)
11+
12+
func TestAcc_GraphQL_basic(t *testing.T) {
13+
14+
query := GraphQLQuery{
15+
`query {currentUser {name, bot, gitpodEnabled, groupCount, id, namespace{id}, publicEmail, username}}`,
16+
}
17+
18+
var response CurrentUserResponse
19+
_, err := SendGraphQLRequest(context.Background(), testGitlabClient, query, &response)
20+
if err != nil {
21+
log.Println(err)
22+
t.Fail()
23+
}
24+
25+
if response.Data.CurrentUser.Name != "Administrator" {
26+
t.Fail()
27+
}
28+
}

internal/provider/util.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ import (
1515
"github.com/xanzy/go-gitlab"
1616
)
1717

18+
// extractIIDFromGlobalID extracts the internal model ID from a global GraphQL ID.
19+
//
20+
// e.g. 'gid://gitlab/User/1' -> 1 or 'gid://gitlab/Project/42' -> 42
21+
//
22+
// see https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#global-ids
23+
func extractIIDFromGlobalID(globalID string) (int, error) {
24+
parts := strings.Split(globalID, "/")
25+
iid, err := strconv.Atoi(parts[len(parts)-1])
26+
if err != nil {
27+
return 0, fmt.Errorf("unable to extract iid from global id %q. Was looking for an integer after the last slash (/).", globalID)
28+
}
29+
return iid, nil
30+
}
31+
1832
func renderValueListForDocs(values []string) string {
1933
inlineCodeValues := make([]string, 0, len(values))
2034
for _, v := range values {

internal/provider/util_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,56 @@ import (
66
gitlab "github.com/xanzy/go-gitlab"
77
)
88

9+
func TestGitlab_extractIIDFromGlobalID(t *testing.T) {
10+
cases := []struct {
11+
GlobalID string
12+
IID int
13+
}{
14+
{
15+
GlobalID: "gid://gitlab/User/1",
16+
IID: 1,
17+
},
18+
{
19+
GlobalID: "gid://gitlab/Namespaces::UserNamespace/1000",
20+
IID: 1000,
21+
},
22+
}
23+
24+
for _, tc := range cases {
25+
iid, err := extractIIDFromGlobalID(tc.GlobalID)
26+
if err != nil {
27+
t.Fatalf("expected valid global id, got %q: %v", tc.GlobalID, err)
28+
}
29+
30+
if iid != tc.IID {
31+
t.Fatalf("got %v expected %v", iid, tc.IID)
32+
}
33+
}
34+
}
35+
36+
func TestGitlab_extractIIDFromGlobalID_invalidGlobalID(t *testing.T) {
37+
cases := []struct {
38+
GlobalID string
39+
}{
40+
{
41+
GlobalID: "",
42+
},
43+
{
44+
GlobalID: "gid://gitlab/User/",
45+
},
46+
{
47+
GlobalID: "gid://gitlab/Namespaces::UserNamespace",
48+
},
49+
}
50+
51+
for _, tc := range cases {
52+
iid, err := extractIIDFromGlobalID(tc.GlobalID)
53+
if err == nil {
54+
t.Fatalf("expected invalid global id, got id %q instead from global id %q", iid, tc.GlobalID)
55+
}
56+
}
57+
}
58+
959
func TestGitlab_visbilityHelpers(t *testing.T) {
1060
cases := []struct {
1161
String string

0 commit comments

Comments
 (0)