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/docs/data-sources/workspace_owner.md b/docs/data-sources/workspace_owner.md new file mode 100644 index 00000000..646b1340 --- /dev/null +++ b/docs/data-sources/workspace_owner.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coder_workspace_owner Data Source - terraform-provider-coder" +subcategory: "" +description: |- + Use this data source to fetch information about the workspace owner. +--- + +# coder_workspace_owner (Data Source) + +Use this data source to fetch information about the workspace owner. + + + + +## 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. diff --git a/provider/provider.go b/provider/provider.go index 4ea75ba8..c1991a26 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -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(), 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/workspace_owner.go b/provider/workspace_owner.go new file mode 100644 index 00000000..5721b5c5 --- /dev/null +++ b/provider/workspace_owner.go @@ -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.", + }, + }, + } +} diff --git a/provider/workspace_owner_test.go b/provider/workspace_owner_test.go new file mode 100644 index 00000000..90839cfc --- /dev/null +++ b/provider/workspace_owner_test.go @@ -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 + }, + }}, + }) + }) +} 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 }, }},
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: