Skip to content

feat: add coder_workspace_owner datasource #230

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

Merged
merged 5 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
compat
  • Loading branch information
johnstcn committed May 24, 2024
commit 1fc56bb32672fc7f70a33a573b16e00daf3f470f
1 change: 1 addition & 0 deletions docs/data-sources/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
70 changes: 48 additions & 22 deletions provider/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
Expand Down Expand Up @@ -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.",
},
},
}
}
103 changes: 72 additions & 31 deletions provider/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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", "[email protected]")
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", "[email protected]")
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, "[email protected]", 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", "[email protected]")
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, "[email protected]", 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, "[email protected]", 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
},
}},
})
})
}
Loading