Skip to content

Commit eb43b9f

Browse files
authored
feat: add coder_workspace_owner datasource (#230)
- Adds a coder_workspace_owner data source populated from fields of coder_workspace prefix with `owner_`. - Adds `coder_workspace_owner.ssh_{public,private}_key`. - Deprecates same fields of coder_workspace.
1 parent 7e5a28b commit eb43b9f

File tree

7 files changed

+299
-13
lines changed

7 files changed

+299
-13
lines changed

docs/data-sources/workspace.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ resource "kubernetes_pod" "dev" {
3030
- `access_url` (String) The access URL of the Coder deployment provisioning this workspace.
3131
- `id` (String) UUID of the workspace.
3232
- `name` (String) Name of the workspace.
33-
- `owner` (String) Username of the workspace owner.
34-
- `owner_email` (String) Email address of the workspace owner.
35-
- `owner_groups` (List of String) List of groups the workspace owner belongs to.
36-
- `owner_id` (String) UUID of the workspace owner.
37-
- `owner_name` (String) Name of the workspace owner.
38-
- `owner_oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.
39-
- `owner_session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.
33+
- `owner` (String, Deprecated) Username of the workspace owner.
34+
- `owner_email` (String, Deprecated) Email address of the workspace owner.
35+
- `owner_groups` (List of String, Deprecated) List of groups the workspace owner belongs to.
36+
- `owner_id` (String, Deprecated) UUID of the workspace owner.
37+
- `owner_name` (String, Deprecated) Name of the workspace owner.
38+
- `owner_oidc_access_token` (String, Deprecated) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.
39+
- `owner_session_token` (String, Deprecated) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.
4040
- `start_count` (Number) A computed count based on "transition" state. If "start", count will equal 1.
4141
- `template_id` (String) ID of the workspace's template.
4242
- `template_name` (String) Name of the workspace's template.

docs/data-sources/workspace_owner.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "coder_workspace_owner Data Source - terraform-provider-coder"
4+
subcategory: ""
5+
description: |-
6+
Use this data source to fetch information about the workspace owner.
7+
---
8+
9+
# coder_workspace_owner (Data Source)
10+
11+
Use this data source to fetch information about the workspace owner.
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Read-Only
19+
20+
- `email` (String) The email address of the user.
21+
- `full_name` (String) The full name of the user.
22+
- `groups` (List of String) The groups of which the user is a member.
23+
- `id` (String) The UUID of the workspace owner.
24+
- `name` (String) The username of the user.
25+
- `oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.
26+
- `session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started.
27+
- `ssh_private_key` (String, Sensitive) The user's generated SSH private key.
28+
- `ssh_public_key` (String) The user's generated SSH public key.

provider/provider.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,13 @@ func New() *schema.Provider {
6868
}, nil
6969
},
7070
DataSourcesMap: map[string]*schema.Resource{
71-
"coder_workspace": workspaceDataSource(),
72-
"coder_workspace_tags": workspaceTagDataSource(),
73-
"coder_provisioner": provisionerDataSource(),
74-
"coder_parameter": parameterDataSource(),
75-
"coder_git_auth": gitAuthDataSource(),
76-
"coder_external_auth": externalAuthDataSource(),
71+
"coder_workspace": workspaceDataSource(),
72+
"coder_workspace_tags": workspaceTagDataSource(),
73+
"coder_provisioner": provisionerDataSource(),
74+
"coder_parameter": parameterDataSource(),
75+
"coder_git_auth": gitAuthDataSource(),
76+
"coder_external_auth": externalAuthDataSource(),
77+
"coder_workspace_owner": workspaceOwnerDataSource(),
7778
},
7879
ResourcesMap: map[string]*schema.Resource{
7980
"coder_agent": agentResource(),

provider/workspace.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,28 +135,33 @@ func workspaceDataSource() *schema.Resource {
135135
Type: schema.TypeString,
136136
Computed: true,
137137
Description: "Username of the workspace owner.",
138+
Deprecated: "Use `coder_workspace_owner.name` instead.",
138139
},
139140
"owner_email": {
140141
Type: schema.TypeString,
141142
Computed: true,
142143
Description: "Email address of the workspace owner.",
144+
Deprecated: "Use `coder_workspace_owner.email` instead.",
143145
},
144146
"owner_id": {
145147
Type: schema.TypeString,
146148
Computed: true,
147149
Description: "UUID of the workspace owner.",
150+
Deprecated: "Use `coder_workspace_owner.id` instead.",
148151
},
149152
"owner_name": {
150153
Type: schema.TypeString,
151154
Computed: true,
152155
Description: "Name of the workspace owner.",
156+
Deprecated: "Use `coder_workspace_owner.full_name` instead.",
153157
},
154158
"owner_oidc_access_token": {
155159
Type: schema.TypeString,
156160
Computed: true,
157161
Description: "A valid OpenID Connect access token of the workspace owner. " +
158162
"This is only available if the workspace owner authenticated with OpenID Connect. " +
159163
"If a valid token cannot be obtained, this value will be an empty string.",
164+
Deprecated: "Use `coder_workspace_owner.oidc_access_token` instead.",
160165
},
161166
"owner_groups": {
162167
Type: schema.TypeList,
@@ -165,6 +170,7 @@ func workspaceDataSource() *schema.Resource {
165170
},
166171
Computed: true,
167172
Description: "List of groups the workspace owner belongs to.",
173+
Deprecated: "Use `coder_workspace_owner.groups` instead.",
168174
},
169175
"id": {
170176
Type: schema.TypeString,
@@ -180,6 +186,7 @@ func workspaceDataSource() *schema.Resource {
180186
Type: schema.TypeString,
181187
Computed: true,
182188
Description: "Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.",
189+
Deprecated: "Use `coder_workspace_owner.session_token` instead.",
183190
},
184191
"template_id": {
185192
Type: schema.TypeString,

provider/workspace_owner.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"strings"
8+
9+
"github.com/google/uuid"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
)
13+
14+
type Role struct {
15+
Name string `json:"name"`
16+
DisplayName string `json:"display-name"`
17+
}
18+
19+
func workspaceOwnerDataSource() *schema.Resource {
20+
return &schema.Resource{
21+
Description: "Use this data source to fetch information about the workspace owner.",
22+
ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
23+
if idStr, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_ID"); ok {
24+
rd.SetId(idStr)
25+
} else {
26+
rd.SetId(uuid.NewString())
27+
}
28+
29+
if username, ok := os.LookupEnv("CODER_WORKSPACE_OWNER"); ok {
30+
_ = rd.Set("name", username)
31+
} else {
32+
_ = rd.Set("name", "default")
33+
}
34+
35+
if fullname, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_NAME"); ok {
36+
_ = rd.Set("full_name", fullname)
37+
} else { // compat: field can be blank, fill in default
38+
_ = rd.Set("full_name", "default")
39+
}
40+
41+
if email, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_EMAIL"); ok {
42+
_ = rd.Set("email", email)
43+
} else {
44+
_ = rd.Set("email", "default@example.com")
45+
}
46+
47+
if sshPubKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY"); ok {
48+
_ = rd.Set("ssh_public_key", sshPubKey)
49+
}
50+
51+
if sshPrivKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY"); ok {
52+
_ = rd.Set("ssh_private_key", sshPrivKey)
53+
}
54+
55+
var groups []string
56+
if groupsRaw, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_GROUPS"); ok {
57+
if err := json.NewDecoder(strings.NewReader(groupsRaw)).Decode(&groups); err != nil {
58+
return diag.Errorf("invalid user groups: %s", err.Error())
59+
}
60+
_ = rd.Set("groups", groups)
61+
}
62+
63+
if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN"); ok {
64+
_ = rd.Set("session_token", tok)
65+
}
66+
67+
if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN"); ok {
68+
_ = rd.Set("oidc_access_token", tok)
69+
}
70+
71+
return nil
72+
},
73+
Schema: map[string]*schema.Schema{
74+
"id": {
75+
Type: schema.TypeString,
76+
Computed: true,
77+
Description: "The UUID of the workspace owner.",
78+
},
79+
"name": {
80+
Type: schema.TypeString,
81+
Computed: true,
82+
Description: "The username of the user.",
83+
},
84+
"full_name": {
85+
Type: schema.TypeString,
86+
Computed: true,
87+
Description: "The full name of the user.",
88+
},
89+
"email": {
90+
Type: schema.TypeString,
91+
Computed: true,
92+
Description: "The email address of the user.",
93+
},
94+
"ssh_public_key": {
95+
Type: schema.TypeString,
96+
Computed: true,
97+
Description: "The user's generated SSH public key.",
98+
},
99+
"ssh_private_key": {
100+
Type: schema.TypeString,
101+
Computed: true,
102+
Description: "The user's generated SSH private key.",
103+
Sensitive: true,
104+
},
105+
"groups": {
106+
Type: schema.TypeList,
107+
Elem: &schema.Schema{
108+
Type: schema.TypeString,
109+
},
110+
Computed: true,
111+
Description: "The groups of which the user is a member.",
112+
},
113+
"session_token": {
114+
Type: schema.TypeString,
115+
Computed: true,
116+
Description: "Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started.",
117+
},
118+
"oidc_access_token": {
119+
Type: schema.TypeString,
120+
Computed: true,
121+
Description: "A valid OpenID Connect access token of the workspace owner. " +
122+
"This is only available if the workspace owner authenticated with OpenID Connect. " +
123+
"If a valid token cannot be obtained, this value will be an empty string.",
124+
},
125+
},
126+
}
127+
}

provider/workspace_owner_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package provider_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/coder/terraform-provider-coder/provider"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
const (
16+
testSSHEd25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJeNcdBMtd4Jo9f2W8RZef0ld7Ypye5zTQEf0vUXa/Eq owner123@host456`
17+
// nolint:gosec // This key was generated specifically for this purpose.
18+
testSSHEd25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
19+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
20+
QyNTUxOQAAACCXjXHQTLXeCaPX9lvEWXn9JXe2Kcnuc00BH9L1F2vxKgAAAJgp3mfQKd5n
21+
0AAAAAtzc2gtZWQyNTUxOQAAACCXjXHQTLXeCaPX9lvEWXn9JXe2Kcnuc00BH9L1F2vxKg
22+
AAAEBia7mAQFoLBILlvTJroTkOUomzfcPY9ckpViQOjYFkAZeNcdBMtd4Jo9f2W8RZef0l
23+
d7Ypye5zTQEf0vUXa/EqAAAAE3ZzY29kZUAzY2Y4MWY5YmM3MmQBAg==
24+
-----END OPENSSH PRIVATE KEY-----`
25+
)
26+
27+
func TestWorkspaceOwnerDatasource(t *testing.T) {
28+
t.Run("OK", func(t *testing.T) {
29+
t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111")
30+
t.Setenv("CODER_WORKSPACE_OWNER", "owner123")
31+
t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner")
32+
t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com")
33+
t.Setenv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY", testSSHEd25519PublicKey)
34+
t.Setenv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY", testSSHEd25519PrivateKey)
35+
t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`)
36+
t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", `supersecret`)
37+
t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", `alsosupersecret`)
38+
39+
resource.Test(t, resource.TestCase{
40+
Providers: map[string]*schema.Provider{
41+
"coder": provider.New(),
42+
},
43+
IsUnitTest: true,
44+
Steps: []resource.TestStep{{
45+
Config: `
46+
provider "coder" {}
47+
data "coder_workspace_owner" "me" {}
48+
`,
49+
Check: func(s *terraform.State) error {
50+
require.Len(t, s.Modules, 1)
51+
require.Len(t, s.Modules[0].Resources, 1)
52+
resource := s.Modules[0].Resources["data.coder_workspace_owner.me"]
53+
require.NotNil(t, resource)
54+
55+
attrs := resource.Primary.Attributes
56+
assert.Equal(t, "11111111-1111-1111-1111-111111111111", attrs["id"])
57+
assert.Equal(t, "owner123", attrs["name"])
58+
assert.Equal(t, "Mr Owner", attrs["full_name"])
59+
assert.Equal(t, "owner123@example.com", attrs["email"])
60+
assert.Equal(t, testSSHEd25519PublicKey, attrs["ssh_public_key"])
61+
assert.Equal(t, testSSHEd25519PrivateKey, attrs["ssh_private_key"])
62+
assert.Equal(t, `group1`, attrs["groups.0"])
63+
assert.Equal(t, `group2`, attrs["groups.1"])
64+
assert.Equal(t, `supersecret`, attrs["session_token"])
65+
assert.Equal(t, `alsosupersecret`, attrs["oidc_access_token"])
66+
return nil
67+
},
68+
}},
69+
})
70+
})
71+
72+
t.Run("Defaults", func(t *testing.T) {
73+
for _, v := range []string{
74+
"CODER_WORKSPACE_OWNER",
75+
"CODER_WORKSPACE_OWNER_ID",
76+
"CODER_WORKSPACE_OWNER_EMAIL",
77+
"CODER_WORKSPACE_OWNER_NAME",
78+
"CODER_WORKSPACE_OWNER_SESSION_TOKEN",
79+
"CODER_WORKSPACE_OWNER_GROUPS",
80+
"CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN",
81+
"CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY",
82+
"CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY",
83+
} { // https://github.com/golang/go/issues/52817
84+
t.Setenv(v, "")
85+
os.Unsetenv(v)
86+
}
87+
88+
resource.Test(t, resource.TestCase{
89+
Providers: map[string]*schema.Provider{
90+
"coder": provider.New(),
91+
},
92+
IsUnitTest: true,
93+
Steps: []resource.TestStep{{
94+
Config: `
95+
provider "coder" {}
96+
data "coder_workspace_owner" "me" {}
97+
`,
98+
Check: func(s *terraform.State) error {
99+
require.Len(t, s.Modules, 1)
100+
require.Len(t, s.Modules[0].Resources, 1)
101+
resource := s.Modules[0].Resources["data.coder_workspace_owner.me"]
102+
require.NotNil(t, resource)
103+
104+
attrs := resource.Primary.Attributes
105+
assert.NotEmpty(t, attrs["id"])
106+
assert.Equal(t, "default", attrs["name"])
107+
assert.Equal(t, "default", attrs["full_name"])
108+
assert.Equal(t, "default@example.com", attrs["email"])
109+
assert.Empty(t, attrs["ssh_public_key"])
110+
assert.Empty(t, attrs["ssh_private_key"])
111+
assert.Empty(t, attrs["groups.0"])
112+
assert.Empty(t, attrs["session_token"])
113+
assert.Empty(t, attrs["oidc_access_token"])
114+
return nil
115+
},
116+
}},
117+
})
118+
})
119+
}

provider/workspace_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ import (
1414

1515
func TestWorkspace(t *testing.T) {
1616
t.Setenv("CODER_WORKSPACE_OWNER", "owner123")
17+
t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111")
1718
t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner")
1819
t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com")
1920
t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123")
2021
t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`)
22+
t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", "supersecret")
2123
t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID")
2224
t.Setenv("CODER_WORKSPACE_TEMPLATE_NAME", "template123")
2325
t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3")
@@ -47,13 +49,15 @@ func TestWorkspace(t *testing.T) {
4749
assert.Equal(t, "https://example.com:8080", attribs["access_url"])
4850
assert.Equal(t, "8080", attribs["access_port"])
4951
assert.Equal(t, "owner123", attribs["owner"])
52+
assert.Equal(t, "11111111-1111-1111-1111-111111111111", attribs["owner_id"])
5053
assert.Equal(t, "Mr Owner", attribs["owner_name"])
5154
assert.Equal(t, "owner123@example.com", attribs["owner_email"])
5255
assert.Equal(t, "group1", attribs["owner_groups.0"])
5356
assert.Equal(t, "group2", attribs["owner_groups.1"])
5457
assert.Equal(t, "templateID", attribs["template_id"])
5558
assert.Equal(t, "template123", attribs["template_name"])
5659
assert.Equal(t, "v1.2.3", attribs["template_version"])
60+
assert.Equal(t, "supersecret", attribs["owner_oidc_access_token"])
5761
return nil
5862
},
5963
}},

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