Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add coder_workspace_owner datasource #230

Merged
merged 5 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/data-sources/user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "coder_user Data Source - terraform-provider-coder"
subcategory: ""
description: |-
Use this data source to fetch information about the workspace owner.
---

# coder_user (Data Source)

Use this data source to fetch information about the workspace owner.



<!-- schema generated by tfplugindocs -->
## Schema

### Read-Only

- `email` (String) The email address of the user.
- `full_name` (String) The full name of the user.
- `groups` (List of String) The groups of which the user is a member.
- `id` (String) The UUID of the workspace owner.
- `name` (String) The username of the user.
- `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.
- `session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started.
- `ssh_private_key` (String, Sensitive) The user's generated SSH private key.
- `ssh_public_key` (String) The user's generated SSH public key.
14 changes: 7 additions & 7 deletions docs/data-sources/workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ resource "kubernetes_pod" "dev" {
- `access_url` (String) The access URL of the Coder deployment provisioning this workspace.
- `id` (String) UUID of the workspace.
- `name` (String) Name of the workspace.
- `owner` (String) Username of the workspace owner.
- `owner_email` (String) Email address of the workspace owner.
- `owner_groups` (List of String) List of groups the workspace owner belongs to.
- `owner_id` (String) UUID of the workspace owner.
- `owner_name` (String) Name of the workspace owner.
- `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.
- `owner_session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.
- `owner` (String, Deprecated) Username of the workspace owner.
- `owner_email` (String, Deprecated) Email address of the workspace owner.
- `owner_groups` (List of String, Deprecated) List of groups the workspace owner belongs to.
- `owner_id` (String, Deprecated) UUID of the workspace owner.
- `owner_name` (String, Deprecated) Name of the workspace owner.
- `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.
- `owner_session_token` (String, Deprecated) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.
- `start_count` (Number) A computed count based on "transition" state. If "start", count will equal 1.
- `template_id` (String) ID of the workspace's template.
- `template_name` (String) Name of the workspace's template.
Expand Down
13 changes: 7 additions & 6 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ func New() *schema.Provider {
}, nil
},
DataSourcesMap: map[string]*schema.Resource{
"coder_workspace": workspaceDataSource(),
"coder_workspace_tags": workspaceTagDataSource(),
"coder_provisioner": provisionerDataSource(),
"coder_parameter": parameterDataSource(),
"coder_git_auth": gitAuthDataSource(),
"coder_external_auth": externalAuthDataSource(),
"coder_workspace": workspaceDataSource(),
"coder_workspace_tags": workspaceTagDataSource(),
"coder_provisioner": provisionerDataSource(),
"coder_parameter": parameterDataSource(),
"coder_git_auth": gitAuthDataSource(),
"coder_external_auth": externalAuthDataSource(),
"coder_workspace_owner": workspaceOwnerDataSource(),
},
ResourcesMap: map[string]*schema.Resource{
"coder_agent": agentResource(),
Expand Down
7 changes: 7 additions & 0 deletions provider/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,28 +135,33 @@ func workspaceDataSource() *schema.Resource {
Type: schema.TypeString,
Computed: true,
Description: "Username of the workspace owner.",
Deprecated: "Use `coder_workspace_owner.name` instead.",
},
"owner_email": {
Type: schema.TypeString,
Computed: true,
Description: "Email address of the workspace owner.",
Deprecated: "Use `coder_workspace_owner.email` instead.",
},
"owner_id": {
Type: schema.TypeString,
Computed: true,
Description: "UUID of the workspace owner.",
Deprecated: "Use `coder_workspace_owner.id` instead.",
},
"owner_name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the workspace owner.",
Deprecated: "Use `coder_workspace_owner.full_name` instead.",
},
"owner_oidc_access_token": {
Type: schema.TypeString,
Computed: true,
Description: "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.",
Deprecated: "Use `coder_workspace_owner.oidc_access_token` instead.",
},
"owner_groups": {
Type: schema.TypeList,
Expand All @@ -165,6 +170,7 @@ func workspaceDataSource() *schema.Resource {
},
Computed: true,
Description: "List of groups the workspace owner belongs to.",
Deprecated: "Use `coder_workspace_owner.groups` instead.",
},
"id": {
Type: schema.TypeString,
Expand All @@ -180,6 +186,7 @@ func workspaceDataSource() *schema.Resource {
Type: schema.TypeString,
Computed: true,
Description: "Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.",
Deprecated: "Use `coder_workspace_owner.session_token` instead.",
},
"template_id": {
Type: schema.TypeString,
Expand Down
127 changes: 127 additions & 0 deletions provider/workspace_owner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package provider

import (
"context"
"encoding/json"
"os"
"strings"

"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type Role struct {
Name string `json:"name"`
DisplayName string `json:"display-name"`
}

func workspaceOwnerDataSource() *schema.Resource {
return &schema.Resource{
Description: "Use this data source to fetch information about the workspace owner.",
ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
if idStr, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_ID"); ok {
rd.SetId(idStr)
} else {
rd.SetId(uuid.NewString())
}

if username, ok := os.LookupEnv("CODER_WORKSPACE_OWNER"); ok {
_ = rd.Set("name", username)
} else {
_ = rd.Set("name", "default")
}

if fullname, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_NAME"); ok {
_ = rd.Set("full_name", fullname)
} else { // compat: field can be blank, fill in default
_ = rd.Set("full_name", "default")
}

if email, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_EMAIL"); ok {
_ = rd.Set("email", email)
} else {
_ = rd.Set("email", "default@example.com")
}

if sshPubKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY"); ok {
_ = rd.Set("ssh_public_key", sshPubKey)
}

if sshPrivKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY"); ok {
_ = rd.Set("ssh_private_key", sshPrivKey)
}

var groups []string
if groupsRaw, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_GROUPS"); ok {
if err := json.NewDecoder(strings.NewReader(groupsRaw)).Decode(&groups); err != nil {
return diag.Errorf("invalid user groups: %s", err.Error())
}
_ = rd.Set("groups", groups)
}

if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN"); ok {
_ = rd.Set("session_token", tok)
}

if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN"); ok {
_ = rd.Set("oidc_access_token", tok)
}

return nil
},
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
Description: "The UUID of the workspace owner.",
},
"name": {
Type: schema.TypeString,
Computed: true,
Description: "The username of the user.",
},
"full_name": {
Type: schema.TypeString,
Computed: true,
Description: "The full name of the user.",
},
"email": {
Type: schema.TypeString,
Computed: true,
Description: "The email address of the user.",
},
"ssh_public_key": {
Type: schema.TypeString,
Computed: true,
Description: "The user's generated SSH public key.",
},
"ssh_private_key": {
Type: schema.TypeString,
Computed: true,
Description: "The user's generated SSH private key.",
Sensitive: true,
},
"groups": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Computed: true,
Description: "The groups of which the user is a member.",
},
"session_token": {
Type: schema.TypeString,
Computed: true,
Description: "Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started.",
},
"oidc_access_token": {
Type: schema.TypeString,
Computed: true,
Description: "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.",
},
},
}
}
119 changes: 119 additions & 0 deletions provider/workspace_owner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package provider_test

import (
"os"
"testing"

"github.com/coder/terraform-provider-coder/provider"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
testSSHEd25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJeNcdBMtd4Jo9f2W8RZef0ld7Ypye5zTQEf0vUXa/Eq owner123@host456`
// nolint:gosec // This key was generated specifically for this purpose.
testSSHEd25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCXjXHQTLXeCaPX9lvEWXn9JXe2Kcnuc00BH9L1F2vxKgAAAJgp3mfQKd5n
0AAAAAtzc2gtZWQyNTUxOQAAACCXjXHQTLXeCaPX9lvEWXn9JXe2Kcnuc00BH9L1F2vxKg
AAAEBia7mAQFoLBILlvTJroTkOUomzfcPY9ckpViQOjYFkAZeNcdBMtd4Jo9f2W8RZef0l
d7Ypye5zTQEf0vUXa/EqAAAAE3ZzY29kZUAzY2Y4MWY5YmM3MmQBAg==
-----END OPENSSH PRIVATE KEY-----`
)

func TestWorkspaceOwnerDatasource(t *testing.T) {
t.Run("OK", func(t *testing.T) {
t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111")
t.Setenv("CODER_WORKSPACE_OWNER", "owner123")
t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner")
t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com")
t.Setenv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY", testSSHEd25519PublicKey)
t.Setenv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY", testSSHEd25519PrivateKey)
t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`)
t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", `supersecret`)
t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", `alsosupersecret`)

resource.Test(t, resource.TestCase{
Providers: map[string]*schema.Provider{
"coder": provider.New(),
},
IsUnitTest: true,
Steps: []resource.TestStep{{
Config: `
provider "coder" {}
data "coder_workspace_owner" "me" {}
`,
Check: func(s *terraform.State) error {
require.Len(t, s.Modules, 1)
require.Len(t, s.Modules[0].Resources, 1)
resource := s.Modules[0].Resources["data.coder_workspace_owner.me"]
require.NotNil(t, resource)

attrs := resource.Primary.Attributes
assert.Equal(t, "11111111-1111-1111-1111-111111111111", attrs["id"])
assert.Equal(t, "owner123", attrs["name"])
assert.Equal(t, "Mr Owner", attrs["full_name"])
assert.Equal(t, "owner123@example.com", attrs["email"])
assert.Equal(t, testSSHEd25519PublicKey, attrs["ssh_public_key"])
assert.Equal(t, testSSHEd25519PrivateKey, attrs["ssh_private_key"])
assert.Equal(t, `group1`, attrs["groups.0"])
assert.Equal(t, `group2`, attrs["groups.1"])
assert.Equal(t, `supersecret`, attrs["session_token"])
assert.Equal(t, `alsosupersecret`, attrs["oidc_access_token"])
return nil
},
}},
})
})

t.Run("Defaults", func(t *testing.T) {
for _, v := range []string{
"CODER_WORKSPACE_OWNER",
"CODER_WORKSPACE_OWNER_ID",
"CODER_WORKSPACE_OWNER_EMAIL",
"CODER_WORKSPACE_OWNER_NAME",
"CODER_WORKSPACE_OWNER_SESSION_TOKEN",
"CODER_WORKSPACE_OWNER_GROUPS",
"CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN",
"CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY",
"CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY",
} { // https://github.com/golang/go/issues/52817
t.Setenv(v, "")
os.Unsetenv(v)
}

resource.Test(t, resource.TestCase{
Providers: map[string]*schema.Provider{
"coder": provider.New(),
},
IsUnitTest: true,
Steps: []resource.TestStep{{
Config: `
provider "coder" {}
data "coder_workspace_owner" "me" {}
`,
Check: func(s *terraform.State) error {
require.Len(t, s.Modules, 1)
require.Len(t, s.Modules[0].Resources, 1)
resource := s.Modules[0].Resources["data.coder_workspace_owner.me"]
require.NotNil(t, resource)

attrs := resource.Primary.Attributes
assert.NotEmpty(t, attrs["id"])
assert.Equal(t, "default", attrs["name"])
assert.Equal(t, "default", attrs["full_name"])
assert.Equal(t, "default@example.com", attrs["email"])
assert.Empty(t, attrs["ssh_public_key"])
assert.Empty(t, attrs["ssh_private_key"])
assert.Empty(t, attrs["groups.0"])
assert.Empty(t, attrs["session_token"])
assert.Empty(t, attrs["oidc_access_token"])
return nil
},
}},
})
})
}
4 changes: 4 additions & 0 deletions provider/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import (

func TestWorkspace(t *testing.T) {
t.Setenv("CODER_WORKSPACE_OWNER", "owner123")
t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111")
t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner")
t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com")
t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123")
t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`)
t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", "supersecret")
t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID")
t.Setenv("CODER_WORKSPACE_TEMPLATE_NAME", "template123")
t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3")
Expand Down Expand Up @@ -47,13 +49,15 @@ func TestWorkspace(t *testing.T) {
assert.Equal(t, "https://example.com:8080", attribs["access_url"])
assert.Equal(t, "8080", attribs["access_port"])
assert.Equal(t, "owner123", attribs["owner"])
assert.Equal(t, "11111111-1111-1111-1111-111111111111", attribs["owner_id"])
assert.Equal(t, "Mr Owner", attribs["owner_name"])
assert.Equal(t, "owner123@example.com", attribs["owner_email"])
assert.Equal(t, "group1", attribs["owner_groups.0"])
assert.Equal(t, "group2", attribs["owner_groups.1"])
assert.Equal(t, "templateID", attribs["template_id"])
assert.Equal(t, "template123", attribs["template_name"])
assert.Equal(t, "v1.2.3", attribs["template_version"])
assert.Equal(t, "supersecret", attribs["owner_oidc_access_token"])
return nil
},
}},
Expand Down
Loading