From 28bab5a548fbdc020a965de140c49a3806ff8d9f Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 23 May 2024 21:40:03 +0000 Subject: [PATCH 1/5] feat: add coder_user datasource --- docs/data-sources/user.md | 26 +++++++++ provider/provider.go | 1 + provider/user.go | 113 ++++++++++++++++++++++++++++++++++++++ provider/user_test.go | 65 ++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 docs/data-sources/user.md create mode 100644 provider/user.go create mode 100644 provider/user_test.go diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md new file mode 100644 index 00000000..b02077ac --- /dev/null +++ b/docs/data-sources/user.md @@ -0,0 +1,26 @@ +--- +# 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 a user. +--- + +# coder_user (Data Source) + +Use this data source to fetch information about a user. + + + + +## 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 user. +- `name` (String) The username of the user. +- `ssh_private_key` (String, Sensitive) The user's generated SSH private key. +- `ssh_public_key` (String) The user's generated SSH public key. diff --git a/provider/provider.go b/provider/provider.go index 4ea75ba8..2137dc75 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -74,6 +74,7 @@ func New() *schema.Provider { "coder_parameter": parameterDataSource(), "coder_git_auth": gitAuthDataSource(), "coder_external_auth": externalAuthDataSource(), + "coder_user": userDataSource(), }, ResourcesMap: map[string]*schema.Resource{ "coder_agent": agentResource(), diff --git a/provider/user.go b/provider/user.go new file mode 100644 index 00000000..317739cf --- /dev/null +++ b/provider/user.go @@ -0,0 +1,113 @@ +package provider + +import ( + "context" + "encoding/json" + "os" + "strings" + + "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 userDataSource() *schema.Resource { + return &schema.Resource{ + Description: "Use this data source to fetch information about a user.", + ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { + if idStr, ok := os.LookupEnv("CODER_USER_ID"); !ok { + return diag.Errorf("missing user id") + } else { + rd.SetId(idStr) + } + + if username, ok := os.LookupEnv("CODER_USER_NAME"); !ok { + return diag.Errorf("missing user username") + } else { + _ = rd.Set("name", username) + } + + if fullname, ok := os.LookupEnv("CODER_USER_FULL_NAME"); !ok { + _ = rd.Set("name", "default") // compat + } else { + _ = rd.Set("full_name", fullname) + } + + if email, ok := os.LookupEnv("CODER_USER_EMAIL"); !ok { + return diag.Errorf("missing user email") + } else { + _ = rd.Set("email", email) + } + + if sshPubKey, ok := os.LookupEnv("CODER_USER_SSH_PUBLIC_KEY"); !ok { + return diag.Errorf("missing user ssh_public_key") + } else { + _ = rd.Set("ssh_public_key", sshPubKey) + } + + if sshPrivKey, ok := os.LookupEnv("CODER_USER_SSH_PRIVATE_KEY"); !ok { + return diag.Errorf("missing user ssh_private_key") + } else { + _ = rd.Set("ssh_private_key", sshPrivKey) + } + + groupsRaw, ok := os.LookupEnv("CODER_USER_GROUPS") + if !ok { + return diag.Errorf("missing user groups") + } + var groups []string + if err := json.NewDecoder(strings.NewReader(groupsRaw)).Decode(&groups); err != nil { + return diag.Errorf("invalid user groups: %s", err.Error()) + } else { + _ = rd.Set("groups", groups) + } + + return nil + }, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the user.", + }, + "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.", + }, + }, + } +} diff --git a/provider/user_test.go b/provider/user_test.go new file mode 100644 index 00000000..8aeb2187 --- /dev/null +++ b/provider/user_test.go @@ -0,0 +1,65 @@ +package provider_test + +import ( + "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 TestUserDatasource(t *testing.T) { + t.Setenv("CODER_USER_ID", "11111111-1111-1111-1111-111111111111") + t.Setenv("CODER_USER_NAME", "owner123") + t.Setenv("CODER_USER_AVATAR_URL", "https://example.com/avatar.png") + t.Setenv("CODER_USER_FULL_NAME", "Mr Owner") + t.Setenv("CODER_USER_EMAIL", "owner123@example.com") + t.Setenv("CODER_USER_SSH_PUBLIC_KEY", testSSHEd25519PublicKey) + t.Setenv("CODER_USER_SSH_PRIVATE_KEY", testSSHEd25519PrivateKey) + t.Setenv("CODER_USER_GROUPS", `["group1", "group2"]`) + + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + data "coder_user" "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_user.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"]) + return nil + }, + }}, + }) +} From 1fc56bb32672fc7f70a33a573b16e00daf3f470f Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 24 May 2024 08:50:01 +0000 Subject: [PATCH 2/5] compat --- docs/data-sources/user.md | 1 + provider/user.go | 70 ++++++++++++++++++-------- provider/user_test.go | 103 ++++++++++++++++++++++++++------------ 3 files changed, 121 insertions(+), 53 deletions(-) diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md index b02077ac..78cf06f4 100644 --- a/docs/data-sources/user.md +++ b/docs/data-sources/user.md @@ -22,5 +22,6 @@ Use this data source to fetch information about a user. - `groups` (List of String) The groups of which the user is a member. - `id` (String) The UUID of the user. - `name` (String) The username of the user. +- `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. diff --git a/provider/user.go b/provider/user.go index 317739cf..d9e761f8 100644 --- a/provider/user.go +++ b/provider/user.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -20,50 +21,70 @@ func userDataSource() *schema.Resource { Description: "Use this data source to fetch information about a user.", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { if idStr, ok := os.LookupEnv("CODER_USER_ID"); !ok { - return diag.Errorf("missing user id") + rd.SetId(uuid.NewString()) } else { rd.SetId(idStr) } - if username, ok := os.LookupEnv("CODER_USER_NAME"); !ok { - return diag.Errorf("missing user username") - } else { + if username, ok := os.LookupEnv("CODER_USER_NAME"); ok { _ = rd.Set("name", username) + } else if altUsername, ok := os.LookupEnv("CODER_WORKSPACE_OWNER"); ok { + _ = rd.Set("name", altUsername) + } else { + return diag.Errorf("missing user name") } - if fullname, ok := os.LookupEnv("CODER_USER_FULL_NAME"); !ok { - _ = rd.Set("name", "default") // compat - } else { + if fullname, ok := os.LookupEnv("CODER_USER_FULL_NAME"); ok { _ = rd.Set("full_name", fullname) + } else if altFullname, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_NAME"); ok { + // Compatibility: read from CODER_WORKSPACE_OWNER_NAME + _ = rd.Set("full_name", altFullname) + } else { // fallback + return diag.Errorf("missing user full_name") } - if email, ok := os.LookupEnv("CODER_USER_EMAIL"); !ok { - return diag.Errorf("missing user email") - } else { + if email, ok := os.LookupEnv("CODER_USER_EMAIL"); ok { _ = rd.Set("email", email) + } else if altEmail, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_EMAIL"); ok { + _ = rd.Set("email", altEmail) + } else { + return diag.Errorf("missing user email") } - if sshPubKey, ok := os.LookupEnv("CODER_USER_SSH_PUBLIC_KEY"); !ok { - return diag.Errorf("missing user ssh_public_key") - } else { + if sshPubKey, ok := os.LookupEnv("CODER_USER_SSH_PUBLIC_KEY"); ok { _ = rd.Set("ssh_public_key", sshPubKey) + } else { + // Compat: do not error + _ = rd.Set("ssh_public_key", "missing") } - if sshPrivKey, ok := os.LookupEnv("CODER_USER_SSH_PRIVATE_KEY"); !ok { - return diag.Errorf("missing user ssh_private_key") - } else { + if sshPrivKey, ok := os.LookupEnv("CODER_USER_SSH_PRIVATE_KEY"); ok { _ = rd.Set("ssh_private_key", sshPrivKey) + } else { + // Compat: do not error + _ = rd.Set("ssh_private_key", "missing") } - groupsRaw, ok := os.LookupEnv("CODER_USER_GROUPS") - if !ok { + var groups []string + if groupsRaw, ok := os.LookupEnv("CODER_USER_GROUPS"); ok { + if err := json.NewDecoder(strings.NewReader(groupsRaw)).Decode(&groups); err != nil { + return diag.Errorf("invalid user groups: %s", err.Error()) + } + } else if altGroupsRaw, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_GROUPS"); ok { + if err := json.NewDecoder(strings.NewReader(altGroupsRaw)).Decode(&groups); err != nil { + return diag.Errorf("invalid workspace owner groups: %s", err.Error()) + } + } else { return diag.Errorf("missing user groups") } - var groups []string - 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_USER_SESSION_TOKEN"); ok { + _ = rd.Set("session_token", tok) + } else if altTok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN"); ok { + _ = rd.Set("session_token", altTok) } else { - _ = rd.Set("groups", groups) + return diag.Errorf("missing user session_token") } return nil @@ -108,6 +129,11 @@ func userDataSource() *schema.Resource { 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.", + }, }, } } diff --git a/provider/user_test.go b/provider/user_test.go index 8aeb2187..91a5e084 100644 --- a/provider/user_test.go +++ b/provider/user_test.go @@ -24,42 +24,83 @@ const ( ) func TestUserDatasource(t *testing.T) { - t.Setenv("CODER_USER_ID", "11111111-1111-1111-1111-111111111111") - t.Setenv("CODER_USER_NAME", "owner123") - t.Setenv("CODER_USER_AVATAR_URL", "https://example.com/avatar.png") - t.Setenv("CODER_USER_FULL_NAME", "Mr Owner") - t.Setenv("CODER_USER_EMAIL", "owner123@example.com") - t.Setenv("CODER_USER_SSH_PUBLIC_KEY", testSSHEd25519PublicKey) - t.Setenv("CODER_USER_SSH_PRIVATE_KEY", testSSHEd25519PrivateKey) - t.Setenv("CODER_USER_GROUPS", `["group1", "group2"]`) + t.Run("OK", func(t *testing.T) { + t.Setenv("CODER_USER_ID", "11111111-1111-1111-1111-111111111111") + t.Setenv("CODER_USER_NAME", "owner123") + t.Setenv("CODER_USER_FULL_NAME", "Mr Owner") + t.Setenv("CODER_USER_EMAIL", "owner123@example.com") + t.Setenv("CODER_USER_SSH_PUBLIC_KEY", testSSHEd25519PublicKey) + t.Setenv("CODER_USER_SSH_PRIVATE_KEY", testSSHEd25519PrivateKey) + t.Setenv("CODER_USER_GROUPS", `["group1", "group2"]`) + t.Setenv("CODER_USER_SESSION_TOKEN", `supersecret`) - resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, - Steps: []resource.TestStep{{ - Config: ` + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` provider "coder" {} data "coder_user" "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_user.me"] - require.NotNil(t, resource) + 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_user.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"]) + return nil + }, + }}, + }) + }) + + t.Run("Compat", func(t *testing.T) { + 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_GROUPS", `["group1", "group2"]`) + t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", `supersecret`) - 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"]) - return nil + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), }, - }}, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + data "coder_user" "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_user.me"] + require.NotNil(t, resource) + + attrs := resource.Primary.Attributes + assert.NotEmpty(t, 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, "missing", attrs["ssh_public_key"]) + assert.Equal(t, "missing", attrs["ssh_private_key"]) + assert.Equal(t, `group1`, attrs["groups.0"]) + assert.Equal(t, `group2`, attrs["groups.1"]) + return nil + }, + }}, + }) }) } From 748a714ff5331e232ba4dd339fda4bda401c30c9 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 24 May 2024 13:25:56 +0000 Subject: [PATCH 3/5] rename to coder_workspace_owner --- docs/data-sources/user.md | 7 +- docs/data-sources/workspace.md | 14 ++-- provider/provider.go | 2 +- provider/workspace.go | 7 ++ provider/{user.go => workspace_owner.go} | 70 ++++++++----------- .../{user_test.go => workspace_owner_test.go} | 57 +++++++++------ provider/workspace_test.go | 4 ++ 7 files changed, 87 insertions(+), 74 deletions(-) rename provider/{user.go => workspace_owner.go} (55%) rename provider/{user_test.go => workspace_owner_test.go} (69%) diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md index 78cf06f4..abf717cc 100644 --- a/docs/data-sources/user.md +++ b/docs/data-sources/user.md @@ -3,12 +3,12 @@ page_title: "coder_user Data Source - terraform-provider-coder" subcategory: "" description: |- - Use this data source to fetch information about a user. + Use this data source to fetch information about the workspace owner. --- # coder_user (Data Source) -Use this data source to fetch information about a user. +Use this data source to fetch information about the workspace owner. @@ -20,8 +20,9 @@ Use this data source to fetch information about a user. - `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 user. +- `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. diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index 2ed7c63d..2a813722 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -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. diff --git a/provider/provider.go b/provider/provider.go index 2137dc75..e59238f2 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -74,7 +74,7 @@ func New() *schema.Provider { "coder_parameter": parameterDataSource(), "coder_git_auth": gitAuthDataSource(), "coder_external_auth": externalAuthDataSource(), - "coder_user": userDataSource(), + "coder_user": workspaceOwnerDataSource(), }, ResourcesMap: map[string]*schema.Resource{ "coder_agent": agentResource(), diff --git a/provider/workspace.go b/provider/workspace.go index b8ff1684..098d64cc 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -135,21 +135,25 @@ 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, @@ -157,6 +161,7 @@ func workspaceDataSource() *schema.Resource { 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, @@ -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, @@ -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, diff --git a/provider/user.go b/provider/workspace_owner.go similarity index 55% rename from provider/user.go rename to provider/workspace_owner.go index d9e761f8..5721b5c5 100644 --- a/provider/user.go +++ b/provider/workspace_owner.go @@ -16,75 +16,56 @@ type Role struct { DisplayName string `json:"display-name"` } -func userDataSource() *schema.Resource { +func workspaceOwnerDataSource() *schema.Resource { return &schema.Resource{ - Description: "Use this data source to fetch information about a user.", + 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_USER_ID"); !ok { - rd.SetId(uuid.NewString()) - } else { + if idStr, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_ID"); ok { rd.SetId(idStr) + } else { + rd.SetId(uuid.NewString()) } - if username, ok := os.LookupEnv("CODER_USER_NAME"); ok { + if username, ok := os.LookupEnv("CODER_WORKSPACE_OWNER"); ok { _ = rd.Set("name", username) - } else if altUsername, ok := os.LookupEnv("CODER_WORKSPACE_OWNER"); ok { - _ = rd.Set("name", altUsername) } else { - return diag.Errorf("missing user name") + _ = rd.Set("name", "default") } - if fullname, ok := os.LookupEnv("CODER_USER_FULL_NAME"); ok { + if fullname, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_NAME"); ok { _ = rd.Set("full_name", fullname) - } else if altFullname, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_NAME"); ok { - // Compatibility: read from CODER_WORKSPACE_OWNER_NAME - _ = rd.Set("full_name", altFullname) - } else { // fallback - return diag.Errorf("missing user full_name") + } else { // compat: field can be blank, fill in default + _ = rd.Set("full_name", "default") } - if email, ok := os.LookupEnv("CODER_USER_EMAIL"); ok { + if email, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_EMAIL"); ok { _ = rd.Set("email", email) - } else if altEmail, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_EMAIL"); ok { - _ = rd.Set("email", altEmail) } else { - return diag.Errorf("missing user email") + _ = rd.Set("email", "default@example.com") } - if sshPubKey, ok := os.LookupEnv("CODER_USER_SSH_PUBLIC_KEY"); ok { + if sshPubKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY"); ok { _ = rd.Set("ssh_public_key", sshPubKey) - } else { - // Compat: do not error - _ = rd.Set("ssh_public_key", "missing") } - if sshPrivKey, ok := os.LookupEnv("CODER_USER_SSH_PRIVATE_KEY"); ok { + if sshPrivKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY"); ok { _ = rd.Set("ssh_private_key", sshPrivKey) - } else { - // Compat: do not error - _ = rd.Set("ssh_private_key", "missing") } var groups []string - if groupsRaw, ok := os.LookupEnv("CODER_USER_GROUPS"); ok { + 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()) } - } else if altGroupsRaw, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_GROUPS"); ok { - if err := json.NewDecoder(strings.NewReader(altGroupsRaw)).Decode(&groups); err != nil { - return diag.Errorf("invalid workspace owner groups: %s", err.Error()) - } - } else { - return diag.Errorf("missing user groups") + _ = rd.Set("groups", groups) } - _ = rd.Set("groups", groups) - if tok, ok := os.LookupEnv("CODER_USER_SESSION_TOKEN"); ok { + if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN"); ok { _ = rd.Set("session_token", tok) - } else if altTok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN"); ok { - _ = rd.Set("session_token", altTok) - } else { - return diag.Errorf("missing user session_token") + } + + if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN"); ok { + _ = rd.Set("oidc_access_token", tok) } return nil @@ -93,7 +74,7 @@ func userDataSource() *schema.Resource { "id": { Type: schema.TypeString, Computed: true, - Description: "The UUID of the user.", + Description: "The UUID of the workspace owner.", }, "name": { Type: schema.TypeString, @@ -134,6 +115,13 @@ func userDataSource() *schema.Resource { 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.", + }, }, } } diff --git a/provider/user_test.go b/provider/workspace_owner_test.go similarity index 69% rename from provider/user_test.go rename to provider/workspace_owner_test.go index 91a5e084..b15c8efb 100644 --- a/provider/user_test.go +++ b/provider/workspace_owner_test.go @@ -1,6 +1,7 @@ package provider_test import ( + "os" "testing" "github.com/coder/terraform-provider-coder/provider" @@ -23,16 +24,17 @@ const ( -----END OPENSSH PRIVATE KEY-----` ) -func TestUserDatasource(t *testing.T) { +func TestWorkspaceOwnerDatasource(t *testing.T) { t.Run("OK", func(t *testing.T) { - t.Setenv("CODER_USER_ID", "11111111-1111-1111-1111-111111111111") - t.Setenv("CODER_USER_NAME", "owner123") - t.Setenv("CODER_USER_FULL_NAME", "Mr Owner") - t.Setenv("CODER_USER_EMAIL", "owner123@example.com") - t.Setenv("CODER_USER_SSH_PUBLIC_KEY", testSSHEd25519PublicKey) - t.Setenv("CODER_USER_SSH_PRIVATE_KEY", testSSHEd25519PrivateKey) - t.Setenv("CODER_USER_GROUPS", `["group1", "group2"]`) - t.Setenv("CODER_USER_SESSION_TOKEN", `supersecret`) + 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{ @@ -60,18 +62,28 @@ func TestUserDatasource(t *testing.T) { 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("Compat", func(t *testing.T) { - 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_GROUPS", `["group1", "group2"]`) - t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", `supersecret`) + 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{ @@ -91,13 +103,14 @@ func TestUserDatasource(t *testing.T) { attrs := resource.Primary.Attributes assert.NotEmpty(t, 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, "missing", attrs["ssh_public_key"]) - assert.Equal(t, "missing", attrs["ssh_private_key"]) - assert.Equal(t, `group1`, attrs["groups.0"]) - assert.Equal(t, `group2`, attrs["groups.1"]) + 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 }, }}, diff --git a/provider/workspace_test.go b/provider/workspace_test.go index d5866af5..d285b30c 100644 --- a/provider/workspace_test.go +++ b/provider/workspace_test.go @@ -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") @@ -47,6 +49,7 @@ 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"]) @@ -54,6 +57,7 @@ func TestWorkspace(t *testing.T) { 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 }, }}, From 8696892af909bf7f4b83b201f077221854c4235a Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 24 May 2024 13:41:05 +0000 Subject: [PATCH 4/5] fixup! rename to coder_workspace_owner --- provider/provider.go | 14 +++++++------- provider/workspace_owner_test.go | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/provider/provider.go b/provider/provider.go index e59238f2..c1991a26 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -68,13 +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_user": workspaceOwnerDataSource(), + "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(), diff --git a/provider/workspace_owner_test.go b/provider/workspace_owner_test.go index b15c8efb..90839cfc 100644 --- a/provider/workspace_owner_test.go +++ b/provider/workspace_owner_test.go @@ -44,12 +44,12 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { Steps: []resource.TestStep{{ Config: ` provider "coder" {} - data "coder_user" "me" {} + 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_user.me"] + resource := s.Modules[0].Resources["data.coder_workspace_owner.me"] require.NotNil(t, resource) attrs := resource.Primary.Attributes @@ -93,12 +93,12 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { Steps: []resource.TestStep{{ Config: ` provider "coder" {} - data "coder_user" "me" {} + 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_user.me"] + resource := s.Modules[0].Resources["data.coder_workspace_owner.me"] require.NotNil(t, resource) attrs := resource.Primary.Attributes From 04de18d0b6dc68f06b2cd9512ea91af5800bdea2 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 24 May 2024 13:44:44 +0000 Subject: [PATCH 5/5] make gen --- docs/data-sources/{user.md => workspace_owner.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename docs/data-sources/{user.md => workspace_owner.md} (90%) diff --git a/docs/data-sources/user.md b/docs/data-sources/workspace_owner.md similarity index 90% rename from docs/data-sources/user.md rename to docs/data-sources/workspace_owner.md index abf717cc..646b1340 100644 --- a/docs/data-sources/user.md +++ b/docs/data-sources/workspace_owner.md @@ -1,12 +1,12 @@ --- # generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "coder_user Data Source - terraform-provider-coder" +page_title: "coder_workspace_owner Data Source - terraform-provider-coder" subcategory: "" description: |- Use this data source to fetch information about the workspace owner. --- -# coder_user (Data Source) +# coder_workspace_owner (Data Source) Use this data source to fetch information about the workspace owner. 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