diff --git a/cli/autostart.go b/cli/autostart.go index ddf29191096e4..778937cf3d236 100644 --- a/cli/autostart.go +++ b/cli/autostart.go @@ -46,7 +46,7 @@ func autostartShow() *cobra.Command { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err } @@ -104,7 +104,7 @@ func autostartEnable() *cobra.Command { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err } @@ -147,7 +147,7 @@ func autostartDisable() *cobra.Command { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err } diff --git a/cli/bump.go b/cli/bump.go index f539c8a1eabad..3fea50436fb95 100644 --- a/cli/bump.go +++ b/cli/bump.go @@ -48,7 +48,7 @@ func bump() *cobra.Command { return xerrors.Errorf("get current org: %w", err) } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return xerrors.Errorf("get workspace: %w", err) } diff --git a/cli/delete.go b/cli/delete.go index 42be08965fed4..d1f13ea08c540 100644 --- a/cli/delete.go +++ b/cli/delete.go @@ -34,7 +34,7 @@ func delete() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err } diff --git a/cli/delete_test.go b/cli/delete_test.go index afbeff869b4a4..c8039d454f93e 100644 --- a/cli/delete_test.go +++ b/cli/delete_test.go @@ -1,13 +1,16 @@ package cli_test import ( + "context" "io" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" "github.com/coder/coder/pty/ptytest" ) @@ -38,4 +41,55 @@ func TestDelete(t *testing.T) { pty.ExpectMatch("Cleaning Up") <-doneChan }) + + t.Run("DifferentUser", func(t *testing.T) { + t.Parallel() + adminClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) + adminUser := coderdtest.CreateFirstUser(t, adminClient) + orgID := adminUser.OrganizationID + client := coderdtest.CreateAnotherUser(t, adminClient, orgID) + user, err := client.User(context.Background(), codersdk.Me) + require.NoError(t, err) + + version := coderdtest.CreateTemplateVersion(t, adminClient, orgID, nil) + coderdtest.AwaitTemplateVersionJob(t, adminClient, version.ID) + template := coderdtest.CreateTemplate(t, adminClient, orgID, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, orgID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + cmd, root := clitest.New(t, "delete", user.Username+"/"+workspace.Name, "-y") + clitest.SetupConfig(t, adminClient, root) + doneChan := make(chan struct{}) + pty := ptytest.New(t) + cmd.SetIn(pty.Input()) + cmd.SetOut(pty.Output()) + go func() { + defer close(doneChan) + err := cmd.Execute() + // When running with the race detector on, we sometimes get an EOF. + if err != nil { + assert.ErrorIs(t, err, io.EOF) + } + }() + + pty.ExpectMatch("Cleaning Up") + <-doneChan + + workspace, err = client.Workspace(context.Background(), workspace.ID) + require.ErrorContains(t, err, "was deleted") + }) + + t.Run("InvalidWorkspaceIdentifier", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + cmd, root := clitest.New(t, "delete", "a/b/c", "-y") + clitest.SetupConfig(t, client, root) + doneChan := make(chan struct{}) + go func() { + defer close(doneChan) + err := cmd.Execute() + assert.ErrorContains(t, err, "invalid workspace name: \"a/b/c\"") + }() + <-doneChan + }) } diff --git a/cli/root.go b/cli/root.go index 96aca0c3267e3..ae65115aa12a0 100644 --- a/cli/root.go +++ b/cli/root.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" + "github.com/google/uuid" "github.com/kirsle/configdir" "github.com/mattn/go-isatty" "github.com/spf13/cobra" @@ -176,6 +177,27 @@ func currentOrganization(cmd *cobra.Command, client *codersdk.Client) (codersdk. return orgs[0], nil } +// namedWorkspace fetches and returns a workspace by an identifier, which may be either +// a bare name (for a workspace owned by the current user) or a "user/workspace" combination, +// where user is either a username or UUID. +func namedWorkspace(cmd *cobra.Command, client *codersdk.Client, orgID uuid.UUID, identifier string) (codersdk.Workspace, error) { + parts := strings.Split(identifier, "/") + + var owner, name string + switch len(parts) { + case 1: + owner = codersdk.Me + name = parts[0] + case 2: + owner = parts[0] + name = parts[1] + default: + return codersdk.Workspace{}, xerrors.Errorf("invalid workspace name: %q", identifier) + } + + return client.WorkspaceByOwnerAndName(cmd.Context(), orgID, owner, name) +} + // createConfig consumes the global configuration flag to produce a config root. func createConfig(cmd *cobra.Command) config.Root { globalRoot, err := cmd.Flags().GetString(varGlobalConfig) diff --git a/cli/show.go b/cli/show.go index 2c51de16b619d..e2245922821f0 100644 --- a/cli/show.go +++ b/cli/show.go @@ -5,7 +5,6 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/cli/cliui" - "github.com/coder/coder/codersdk" ) func show() *cobra.Command { @@ -23,7 +22,7 @@ func show() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return xerrors.Errorf("get workspace: %w", err) } diff --git a/cli/ssh.go b/cli/ssh.go index dcdf46a511018..48aaadd798f4e 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -207,7 +207,7 @@ func getWorkspaceAndAgent(cmd *cobra.Command, client *codersdk.Client, orgID uui return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err } } else { - workspace, err = client.WorkspaceByOwnerAndName(cmd.Context(), orgID, userID, workspaceParts[0]) + workspace, err = namedWorkspace(cmd, client, orgID, workspaceParts[0]) if err != nil { return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err } diff --git a/cli/start.go b/cli/start.go index 7e35b22aa915a..2fc142694986d 100644 --- a/cli/start.go +++ b/cli/start.go @@ -32,7 +32,7 @@ func start() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err } diff --git a/cli/state.go b/cli/state.go index ec43a2711571b..8cd7828532476 100644 --- a/cli/state.go +++ b/cli/state.go @@ -35,7 +35,7 @@ func statePull() *cobra.Command { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err } @@ -81,7 +81,7 @@ func statePush() *cobra.Command { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err } diff --git a/cli/stop.go b/cli/stop.go index f2455458bd1f7..05738babebb76 100644 --- a/cli/stop.go +++ b/cli/stop.go @@ -32,7 +32,7 @@ func stop() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err } diff --git a/cli/ttl.go b/cli/ttl.go index 077b561179dcb..4a7e4306ab67b 100644 --- a/cli/ttl.go +++ b/cli/ttl.go @@ -44,7 +44,7 @@ func ttlShow() *cobra.Command { return xerrors.Errorf("get current org: %w", err) } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return xerrors.Errorf("get workspace: %w", err) } @@ -77,7 +77,7 @@ func ttlset() *cobra.Command { return xerrors.Errorf("get current org: %w", err) } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return xerrors.Errorf("get workspace: %w", err) } @@ -125,7 +125,7 @@ func ttlunset() *cobra.Command { return xerrors.Errorf("get current org: %w", err) } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return xerrors.Errorf("get workspace: %w", err) } diff --git a/cli/update.go b/cli/update.go index f444ac441c544..8b4a3e5d9a371 100644 --- a/cli/update.go +++ b/cli/update.go @@ -23,7 +23,7 @@ func update() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0]) + workspace, err := namedWorkspace(cmd, client, organization.ID, args[0]) if err != nil { return err }