Skip to content

Commit 582d636

Browse files
authored
feat: support fully-qualified workspace names in CLI (#2036)
1 parent fc38b61 commit 582d636

12 files changed

+91
-16
lines changed

cli/autostart.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func autostartShow() *cobra.Command {
4646
return err
4747
}
4848

49-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
49+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
5050
if err != nil {
5151
return err
5252
}
@@ -104,7 +104,7 @@ func autostartEnable() *cobra.Command {
104104
return err
105105
}
106106

107-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
107+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
108108
if err != nil {
109109
return err
110110
}
@@ -147,7 +147,7 @@ func autostartDisable() *cobra.Command {
147147
return err
148148
}
149149

150-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
150+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
151151
if err != nil {
152152
return err
153153
}

cli/bump.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func bump() *cobra.Command {
4848
return xerrors.Errorf("get current org: %w", err)
4949
}
5050

51-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
51+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
5252
if err != nil {
5353
return xerrors.Errorf("get workspace: %w", err)
5454
}

cli/delete.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func delete() *cobra.Command {
3434
if err != nil {
3535
return err
3636
}
37-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
37+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
3838
if err != nil {
3939
return err
4040
}

cli/delete_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package cli_test
22

33
import (
4+
"context"
45
"io"
56
"testing"
67

78
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
810

911
"github.com/coder/coder/cli/clitest"
1012
"github.com/coder/coder/coderd/coderdtest"
13+
"github.com/coder/coder/codersdk"
1114
"github.com/coder/coder/pty/ptytest"
1215
)
1316

@@ -38,4 +41,55 @@ func TestDelete(t *testing.T) {
3841
pty.ExpectMatch("Cleaning Up")
3942
<-doneChan
4043
})
44+
45+
t.Run("DifferentUser", func(t *testing.T) {
46+
t.Parallel()
47+
adminClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
48+
adminUser := coderdtest.CreateFirstUser(t, adminClient)
49+
orgID := adminUser.OrganizationID
50+
client := coderdtest.CreateAnotherUser(t, adminClient, orgID)
51+
user, err := client.User(context.Background(), codersdk.Me)
52+
require.NoError(t, err)
53+
54+
version := coderdtest.CreateTemplateVersion(t, adminClient, orgID, nil)
55+
coderdtest.AwaitTemplateVersionJob(t, adminClient, version.ID)
56+
template := coderdtest.CreateTemplate(t, adminClient, orgID, version.ID)
57+
workspace := coderdtest.CreateWorkspace(t, client, orgID, template.ID)
58+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
59+
60+
cmd, root := clitest.New(t, "delete", user.Username+"/"+workspace.Name, "-y")
61+
clitest.SetupConfig(t, adminClient, root)
62+
doneChan := make(chan struct{})
63+
pty := ptytest.New(t)
64+
cmd.SetIn(pty.Input())
65+
cmd.SetOut(pty.Output())
66+
go func() {
67+
defer close(doneChan)
68+
err := cmd.Execute()
69+
// When running with the race detector on, we sometimes get an EOF.
70+
if err != nil {
71+
assert.ErrorIs(t, err, io.EOF)
72+
}
73+
}()
74+
75+
pty.ExpectMatch("Cleaning Up")
76+
<-doneChan
77+
78+
workspace, err = client.Workspace(context.Background(), workspace.ID)
79+
require.ErrorContains(t, err, "was deleted")
80+
})
81+
82+
t.Run("InvalidWorkspaceIdentifier", func(t *testing.T) {
83+
t.Parallel()
84+
client := coderdtest.New(t, nil)
85+
cmd, root := clitest.New(t, "delete", "a/b/c", "-y")
86+
clitest.SetupConfig(t, client, root)
87+
doneChan := make(chan struct{})
88+
go func() {
89+
defer close(doneChan)
90+
err := cmd.Execute()
91+
assert.ErrorContains(t, err, "invalid workspace name: \"a/b/c\"")
92+
}()
93+
<-doneChan
94+
})
4195
}

cli/root.go

+22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"golang.org/x/xerrors"
1111

12+
"github.com/google/uuid"
1213
"github.com/kirsle/configdir"
1314
"github.com/mattn/go-isatty"
1415
"github.com/spf13/cobra"
@@ -176,6 +177,27 @@ func currentOrganization(cmd *cobra.Command, client *codersdk.Client) (codersdk.
176177
return orgs[0], nil
177178
}
178179

180+
// namedWorkspace fetches and returns a workspace by an identifier, which may be either
181+
// a bare name (for a workspace owned by the current user) or a "user/workspace" combination,
182+
// where user is either a username or UUID.
183+
func namedWorkspace(cmd *cobra.Command, client *codersdk.Client, orgID uuid.UUID, identifier string) (codersdk.Workspace, error) {
184+
parts := strings.Split(identifier, "/")
185+
186+
var owner, name string
187+
switch len(parts) {
188+
case 1:
189+
owner = codersdk.Me
190+
name = parts[0]
191+
case 2:
192+
owner = parts[0]
193+
name = parts[1]
194+
default:
195+
return codersdk.Workspace{}, xerrors.Errorf("invalid workspace name: %q", identifier)
196+
}
197+
198+
return client.WorkspaceByOwnerAndName(cmd.Context(), orgID, owner, name)
199+
}
200+
179201
// createConfig consumes the global configuration flag to produce a config root.
180202
func createConfig(cmd *cobra.Command) config.Root {
181203
globalRoot, err := cmd.Flags().GetString(varGlobalConfig)

cli/show.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"golang.org/x/xerrors"
66

77
"github.com/coder/coder/cli/cliui"
8-
"github.com/coder/coder/codersdk"
98
)
109

1110
func show() *cobra.Command {
@@ -23,7 +22,7 @@ func show() *cobra.Command {
2322
if err != nil {
2423
return err
2524
}
26-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
25+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
2726
if err != nil {
2827
return xerrors.Errorf("get workspace: %w", err)
2928
}

cli/ssh.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func getWorkspaceAndAgent(cmd *cobra.Command, client *codersdk.Client, orgID uui
207207
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err
208208
}
209209
} else {
210-
workspace, err = client.WorkspaceByOwnerAndName(cmd.Context(), orgID, userID, workspaceParts[0])
210+
workspace, err = namedWorkspace(cmd, client, orgID, workspaceParts[0])
211211
if err != nil {
212212
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err
213213
}

cli/start.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func start() *cobra.Command {
3232
if err != nil {
3333
return err
3434
}
35-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
35+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
3636
if err != nil {
3737
return err
3838
}

cli/state.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func statePull() *cobra.Command {
3535
return err
3636
}
3737

38-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
38+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
3939
if err != nil {
4040
return err
4141
}
@@ -81,7 +81,7 @@ func statePush() *cobra.Command {
8181
return err
8282
}
8383

84-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
84+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
8585
if err != nil {
8686
return err
8787
}

cli/stop.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func stop() *cobra.Command {
3232
if err != nil {
3333
return err
3434
}
35-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
35+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
3636
if err != nil {
3737
return err
3838
}

cli/ttl.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func ttlShow() *cobra.Command {
4444
return xerrors.Errorf("get current org: %w", err)
4545
}
4646

47-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
47+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
4848
if err != nil {
4949
return xerrors.Errorf("get workspace: %w", err)
5050
}
@@ -77,7 +77,7 @@ func ttlset() *cobra.Command {
7777
return xerrors.Errorf("get current org: %w", err)
7878
}
7979

80-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
80+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
8181
if err != nil {
8282
return xerrors.Errorf("get workspace: %w", err)
8383
}
@@ -125,7 +125,7 @@ func ttlunset() *cobra.Command {
125125
return xerrors.Errorf("get current org: %w", err)
126126
}
127127

128-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
128+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
129129
if err != nil {
130130
return xerrors.Errorf("get workspace: %w", err)
131131
}

cli/update.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func update() *cobra.Command {
2323
if err != nil {
2424
return err
2525
}
26-
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
26+
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
2727
if err != nil {
2828
return err
2929
}

0 commit comments

Comments
 (0)