Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1b8e938

Browse files
committedMay 23, 2024·
feat: add coder_user datasource
1 parent c683ad5 commit 1b8e938

File tree

4 files changed

+352
-0
lines changed

4 files changed

+352
-0
lines changed
 

‎docs/data-sources/user.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "coder_user Data Source - terraform-provider-coder"
4+
subcategory: ""
5+
description: |-
6+
Use this data source to fetch information about a user.
7+
---
8+
9+
# coder_user (Data Source)
10+
11+
Use this data source to fetch information about a user.
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Read-Only
19+
20+
- `avatar_url` (String) The URL of the user's avatar.
21+
- `created_at` (String) The time the user was created in RFC3339 format.
22+
- `email` (String) The email address of the user.
23+
- `groups` (List of String) The groups of which the user is a member.
24+
- `id` (String) The UUID of the user.
25+
- `last_seen_at` (String) The time the user was last active in RFC3339 format.
26+
- `login_type` (String) The user's login type.
27+
- `name` (String) The full name of the user.
28+
- `organization_ids` (List of String) The organization IDs of which the user is a member.
29+
- `roles` (List of Object) The roles assigned to the user. (see [below for nested schema](#nestedatt--roles))
30+
- `ssh_private_key` (String, Sensitive) The user's generated SSH private key.
31+
- `ssh_public_key` (String) The user's generated SSH public key.
32+
- `status` (String) The status of the user.
33+
- `theme_preference` (String) The user's theme preference.
34+
- `username` (String) The username of the user.
35+
36+
<a id="nestedatt--roles"></a>
37+
### Nested Schema for `roles`
38+
39+
Read-Only:
40+
41+
- `display_name` (String)
42+
- `name` (String)

‎provider/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func New() *schema.Provider {
7474
"coder_parameter": parameterDataSource(),
7575
"coder_git_auth": gitAuthDataSource(),
7676
"coder_external_auth": externalAuthDataSource(),
77+
"coder_user": userDataSource(),
7778
},
7879
ResourcesMap: map[string]*schema.Resource{
7980
"coder_agent": agentResource(),

‎provider/user.go

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
)
12+
13+
type Role struct {
14+
Name string `json:"name"`
15+
DisplayName string `json:"display-name"`
16+
}
17+
18+
func userDataSource() *schema.Resource {
19+
return &schema.Resource{
20+
Description: "Use this data source to fetch information about a user.",
21+
ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
22+
if idStr, ok := os.LookupEnv("CODER_USER_ID"); !ok {
23+
return diag.Errorf("missing user id")
24+
} else {
25+
rd.SetId(idStr)
26+
}
27+
28+
if username, ok := os.LookupEnv("CODER_USER_USERNAME"); !ok {
29+
return diag.Errorf("missing user username")
30+
} else {
31+
_ = rd.Set("username", username)
32+
}
33+
34+
if avatarURL, ok := os.LookupEnv("CODER_USER_AVATAR_URL"); !ok {
35+
return diag.Errorf("missing user avatar_url")
36+
} else {
37+
_ = rd.Set("avatar_url", avatarURL)
38+
}
39+
40+
if fullname, ok := os.LookupEnv("CODER_USER_NAME"); !ok {
41+
_ = rd.Set("name", "default") // compat
42+
} else {
43+
_ = rd.Set("name", fullname)
44+
}
45+
46+
if email, ok := os.LookupEnv("CODER_USER_EMAIL"); !ok {
47+
return diag.Errorf("missing user email")
48+
} else {
49+
_ = rd.Set("email", email)
50+
}
51+
52+
if createdAt, ok := os.LookupEnv("CODER_USER_CREATED_AT"); !ok {
53+
return diag.Errorf("missing user created_at")
54+
} else {
55+
_ = rd.Set("created_at", createdAt)
56+
}
57+
58+
if lastSeenAt, ok := os.LookupEnv("CODER_USER_LAST_SEEN_AT"); !ok {
59+
return diag.Errorf("missing user last_seen_at")
60+
} else {
61+
_ = rd.Set("last_seen_at", lastSeenAt)
62+
}
63+
64+
if status, ok := os.LookupEnv("CODER_USER_STATUS"); !ok {
65+
return diag.Errorf("missing user status")
66+
} else {
67+
_ = rd.Set("status", status)
68+
}
69+
70+
if loginType, ok := os.LookupEnv("CODER_USER_LOGIN_TYPE"); !ok {
71+
return diag.Errorf("missing user login_type")
72+
} else {
73+
_ = rd.Set("login_type", loginType)
74+
}
75+
76+
if themePref, ok := os.LookupEnv("CODER_USER_THEME_PREFERENCE"); !ok {
77+
return diag.Errorf("missing user theme_preference")
78+
} else {
79+
_ = rd.Set("theme_preference", themePref)
80+
}
81+
82+
orgIDsRaw, ok := os.LookupEnv("CODER_USER_ORGANIZATION_IDS")
83+
if !ok {
84+
return diag.Errorf("missing user organization_ids")
85+
}
86+
var orgIDs []string
87+
if err := json.NewDecoder(strings.NewReader(orgIDsRaw)).Decode(&orgIDs); err != nil {
88+
return diag.Errorf("invalid user organization_ids: %s", err.Error())
89+
}
90+
_ = rd.Set("organization_ids", orgIDs)
91+
92+
if sshPubKey, ok := os.LookupEnv("CODER_USER_SSH_PUBLIC_KEY"); !ok {
93+
return diag.Errorf("missing user ssh_public_key")
94+
} else {
95+
_ = rd.Set("ssh_public_key", sshPubKey)
96+
}
97+
98+
if sshPrivKey, ok := os.LookupEnv("CODER_USER_SSH_PRIVATE_KEY"); !ok {
99+
return diag.Errorf("missing user ssh_private_key")
100+
} else {
101+
_ = rd.Set("ssh_private_key", sshPrivKey)
102+
}
103+
104+
groupsRaw, ok := os.LookupEnv("CODER_USER_GROUPS")
105+
if !ok {
106+
return diag.Errorf("missing user groups")
107+
}
108+
var groups []string
109+
if err := json.NewDecoder(strings.NewReader(groupsRaw)).Decode(&groups); err != nil {
110+
return diag.Errorf("invalid user groups: %s", err.Error())
111+
} else {
112+
_ = rd.Set("groups", groups)
113+
}
114+
115+
rolesRaw, ok := os.LookupEnv("CODER_USER_ROLES")
116+
if !ok {
117+
return diag.Errorf("missing user roles")
118+
}
119+
var roles []Role
120+
if err := json.NewDecoder(strings.NewReader(rolesRaw)).Decode(&roles); err != nil {
121+
return diag.Errorf("invalid user roles: %s", err.Error())
122+
} else {
123+
_ = rd.Set("roles", roles)
124+
}
125+
126+
return nil
127+
},
128+
Schema: map[string]*schema.Schema{
129+
"id": {
130+
Type: schema.TypeString,
131+
Computed: true,
132+
Description: "The UUID of the user.",
133+
},
134+
"username": {
135+
Type: schema.TypeString,
136+
Computed: true,
137+
Description: "The username of the user.",
138+
},
139+
"avatar_url": {
140+
Type: schema.TypeString,
141+
Computed: true,
142+
Description: "The URL of the user's avatar.",
143+
},
144+
"name": {
145+
Type: schema.TypeString,
146+
Computed: true,
147+
Description: "The full name of the user.",
148+
},
149+
"email": {
150+
Type: schema.TypeString,
151+
Computed: true,
152+
Description: "The email address of the user.",
153+
},
154+
"created_at": {
155+
Type: schema.TypeString,
156+
Computed: true,
157+
Description: "The time the user was created in RFC3339 format.",
158+
},
159+
"last_seen_at": {
160+
Type: schema.TypeString,
161+
Computed: true,
162+
Description: "The time the user was last active in RFC3339 format.",
163+
},
164+
"status": {
165+
Type: schema.TypeString,
166+
Computed: true,
167+
Description: "The status of the user.",
168+
},
169+
"login_type": {
170+
Type: schema.TypeString,
171+
Computed: true,
172+
Description: "The user's login type.",
173+
},
174+
"theme_preference": {
175+
Type: schema.TypeString,
176+
Computed: true,
177+
Description: "The user's theme preference.",
178+
},
179+
"organization_ids": {
180+
Type: schema.TypeList,
181+
Elem: &schema.Schema{
182+
Type: schema.TypeString,
183+
},
184+
Computed: true,
185+
Description: "The organization IDs of which the user is a member.",
186+
},
187+
"ssh_public_key": {
188+
Type: schema.TypeString,
189+
Computed: true,
190+
Description: "The user's generated SSH public key.",
191+
},
192+
"ssh_private_key": {
193+
Type: schema.TypeString,
194+
Computed: true,
195+
Description: "The user's generated SSH private key.",
196+
Sensitive: true,
197+
},
198+
"groups": {
199+
Type: schema.TypeList,
200+
Elem: &schema.Schema{
201+
Type: schema.TypeString,
202+
},
203+
Computed: true,
204+
Description: "The groups of which the user is a member.",
205+
},
206+
"roles": {
207+
Type: schema.TypeList,
208+
Elem: &schema.Resource{
209+
Schema: map[string]*schema.Schema{
210+
"name": {
211+
Type: schema.TypeString,
212+
Computed: true,
213+
Description: "The internal name of the role.",
214+
},
215+
"display_name": {
216+
Type: schema.TypeString,
217+
Computed: true,
218+
Description: "The display name of the role in the UI.",
219+
},
220+
},
221+
},
222+
Computed: true,
223+
Description: "The roles assigned to the user.",
224+
},
225+
},
226+
}
227+
}

‎provider/user_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package provider_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/coder/terraform-provider-coder/provider"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
const (
15+
testSSHEd25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJeNcdBMtd4Jo9f2W8RZef0ld7Ypye5zTQEf0vUXa/Eq owner123@host456`
16+
// nolint:gosec // This key was generated specifically for this purpose.
17+
testSSHEd25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
18+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
19+
QyNTUxOQAAACCXjXHQTLXeCaPX9lvEWXn9JXe2Kcnuc00BH9L1F2vxKgAAAJgp3mfQKd5n
20+
0AAAAAtzc2gtZWQyNTUxOQAAACCXjXHQTLXeCaPX9lvEWXn9JXe2Kcnuc00BH9L1F2vxKg
21+
AAAEBia7mAQFoLBILlvTJroTkOUomzfcPY9ckpViQOjYFkAZeNcdBMtd4Jo9f2W8RZef0l
22+
d7Ypye5zTQEf0vUXa/EqAAAAE3ZzY29kZUAzY2Y4MWY5YmM3MmQBAg==
23+
-----END OPENSSH PRIVATE KEY-----`
24+
)
25+
26+
func TestUserDatasource(t *testing.T) {
27+
t.Setenv("CODER_USER_ID", "11111111-1111-1111-1111-111111111111")
28+
t.Setenv("CODER_USER_USERNAME", "owner123")
29+
t.Setenv("CODER_USER_AVATAR_URL", "https://example.com/avatar.png")
30+
t.Setenv("CODER_USER_NAME", "Mr Owner")
31+
t.Setenv("CODER_USER_EMAIL", "owner123@example.com")
32+
t.Setenv("CODER_USER_CREATED_AT", "2022-08-15T08:30:10.343828Z")
33+
t.Setenv("CODER_USER_LAST_SEEN_AT", "2024-05-23T13:37:49.193203Z")
34+
t.Setenv("CODER_USER_STATUS", "active")
35+
t.Setenv("CODER_USER_LOGIN_TYPE", "oidc")
36+
t.Setenv("CODER_USER_THEME_PREFERENCE", "hackerGreen")
37+
t.Setenv("CODER_USER_ORGANIZATION_IDS", `["22222222-2222-2222-2222-222222222222","33333333-3333-3333-3333-333333333333"]`)
38+
t.Setenv("CODER_USER_SSH_PUBLIC_KEY", testSSHEd25519PublicKey)
39+
t.Setenv("CODER_USER_SSH_PRIVATE_KEY", testSSHEd25519PrivateKey)
40+
t.Setenv("CODER_USER_GROUPS", `["group1", "group2"]`)
41+
t.Setenv("CODER_USER_ROLES", `[{"name": "template-admin", "display_name": "Template Admin"}]`)
42+
43+
resource.Test(t, resource.TestCase{
44+
Providers: map[string]*schema.Provider{
45+
"coder": provider.New(),
46+
},
47+
IsUnitTest: true,
48+
Steps: []resource.TestStep{{
49+
Config: `
50+
provider "coder" {}
51+
data "coder_user" "me" {}
52+
`,
53+
Check: func(s *terraform.State) error {
54+
require.Len(t, s.Modules, 1)
55+
require.Len(t, s.Modules[0].Resources, 1)
56+
resource := s.Modules[0].Resources["data.coder_user.me"]
57+
require.NotNil(t, resource)
58+
59+
attrs := resource.Primary.Attributes
60+
assert.Equal(t, "11111111-1111-1111-1111-111111111111", attrs["id"])
61+
assert.Equal(t, "owner123", attrs["username"])
62+
assert.Equal(t, "https://example.com/avatar.png", attrs["avatar_url"])
63+
assert.Equal(t, "Mr Owner", attrs["name"])
64+
assert.Equal(t, "owner123@example.com", attrs["email"])
65+
assert.Equal(t, "2022-08-15T08:30:10.343828Z", attrs["created_at"])
66+
assert.Equal(t, "2024-05-23T13:37:49.193203Z", attrs["last_seen_at"])
67+
assert.Equal(t, "active", attrs["status"])
68+
assert.Equal(t, "oidc", attrs["login_type"])
69+
assert.Equal(t, "hackerGreen", attrs["theme_preference"])
70+
assert.Equal(t, "22222222-2222-2222-2222-222222222222", attrs["organization_ids.0"])
71+
assert.Equal(t, "33333333-3333-3333-3333-333333333333", attrs["organization_ids.1"])
72+
assert.Equal(t, testSSHEd25519PublicKey, attrs["ssh_public_key"])
73+
assert.Equal(t, testSSHEd25519PrivateKey, attrs["ssh_private_key"])
74+
assert.Equal(t, `group1`, attrs["groups.0"])
75+
assert.Equal(t, `group2`, attrs["groups.1"])
76+
assert.Equal(t, `template-admin`, attrs["roles.0.name"])
77+
assert.Equal(t, `Template Admin`, attrs["roles.0.display_name"])
78+
return nil
79+
},
80+
}},
81+
})
82+
}

0 commit comments

Comments
 (0)
Please sign in to comment.