Skip to content

Commit 1b8e938

Browse files
committed
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

Lines changed: 42 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 227 additions & 0 deletions
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

Lines changed: 82 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy