Skip to content

Commit 0154e6c

Browse files
committed
fix: handle vscodessh style workspace names in coder ssh
1 parent ac74c65 commit 0154e6c

File tree

2 files changed

+74
-5
lines changed

2 files changed

+74
-5
lines changed

cli/ssh.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"os"
1414
"os/exec"
1515
"path/filepath"
16+
"regexp"
1617
"slices"
1718
"strconv"
1819
"strings"
@@ -57,6 +58,7 @@ var (
5758
autostopNotifyCountdown = []time.Duration{30 * time.Minute}
5859
// gracefulShutdownTimeout is the timeout, per item in the stack of things to close
5960
gracefulShutdownTimeout = 2 * time.Second
61+
workspaceNameRe = regexp.MustCompile(`[/.]+|--`)
6062
)
6163

6264
func (r *RootCmd) ssh() *serpent.Command {
@@ -200,10 +202,9 @@ func (r *RootCmd) ssh() *serpent.Command {
200202
parsedEnv = append(parsedEnv, [2]string{k, v})
201203
}
202204

203-
namedWorkspace := strings.TrimPrefix(inv.Args[0], hostPrefix)
204-
// Support "--" as a delimiter between owner and workspace name
205-
namedWorkspace = strings.Replace(namedWorkspace, "--", "/", 1)
206-
205+
workspaceInput := strings.TrimPrefix(inv.Args[0], hostPrefix)
206+
// convert workspace name format into owner/workspace.agent
207+
namedWorkspace := normalizeWorkspaceInput(workspaceInput)
207208
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, !disableAutostart, namedWorkspace)
208209
if err != nil {
209210
return err
@@ -1413,3 +1414,28 @@ func collectNetworkStats(ctx context.Context, agentConn *workspacesdk.AgentConn,
14131414
DownloadBytesSec: int64(downloadSecs),
14141415
}, nil
14151416
}
1417+
1418+
// Converts workspace name input to owner/workspace.agent format
1419+
// Possible valid input formats:
1420+
// workspace
1421+
// owner/workspace
1422+
// owner--workspace
1423+
// owner/workspace--agent
1424+
// owner/workspace.agent
1425+
// owner--workspace--agent
1426+
// owner--workspace.agent
1427+
func normalizeWorkspaceInput(input string) string {
1428+
// Split on "/", "--", and "."
1429+
parts := workspaceNameRe.Split(input, -1)
1430+
1431+
switch len(parts) {
1432+
case 1:
1433+
return input // "workspace"
1434+
case 2:
1435+
return fmt.Sprintf("%s/%s", parts[0], parts[1]) // "owner/workspace"
1436+
case 3:
1437+
return fmt.Sprintf("%s/%s.%s", parts[0], parts[1], parts[2]) // "owner/workspace.agent"
1438+
default:
1439+
return input // Fallback
1440+
}
1441+
}

cli/ssh_test.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ func setupWorkspaceForAgent(t *testing.T, mutations ...func([]*proto.Agent) []*p
6363
client, store := coderdtest.NewWithDatabase(t, nil)
6464
client.SetLogger(testutil.Logger(t).Named("client"))
6565
first := coderdtest.CreateFirstUser(t, client)
66-
userClient, user := coderdtest.CreateAnotherUser(t, client, first.OrganizationID)
66+
userClient, user := coderdtest.CreateAnotherUserMutators(t, client, first.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
67+
r.Username = "myuser"
68+
})
6769
r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{
70+
Name: "myworkspace",
6871
OrganizationID: first.OrganizationID,
6972
OwnerID: user.ID,
7073
}).WithAgent(mutations...).Do()
@@ -98,6 +101,46 @@ func TestSSH(t *testing.T) {
98101
pty.WriteLine("exit")
99102
<-cmdDone
100103
})
104+
t.Run("WorkspaceNameInput", func(t *testing.T) {
105+
t.Parallel()
106+
107+
cases := []string{
108+
"myworkspace",
109+
"myuser/myworkspace",
110+
"myuser--myworkspace",
111+
"myuser/myworkspace--dev",
112+
"myuser/myworkspace.dev",
113+
"myuser--myworkspace--dev",
114+
"myuser--myworkspace.dev",
115+
}
116+
117+
for _, tc := range cases {
118+
t.Run(tc, func(t *testing.T) {
119+
t.Parallel()
120+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
121+
defer cancel()
122+
123+
client, workspace, agentToken := setupWorkspaceForAgent(t)
124+
125+
inv, root := clitest.New(t, "ssh", tc)
126+
clitest.SetupConfig(t, client, root)
127+
pty := ptytest.New(t).Attach(inv)
128+
129+
cmdDone := tGo(t, func() {
130+
err := inv.WithContext(ctx).Run()
131+
assert.NoError(t, err)
132+
})
133+
pty.ExpectMatch("Waiting")
134+
135+
_ = agenttest.New(t, client.URL, agentToken)
136+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
137+
138+
// Shells on Mac, Windows, and Linux all exit shells with the "exit" command.
139+
pty.WriteLine("exit")
140+
<-cmdDone
141+
})
142+
}
143+
})
101144
t.Run("StartStoppedWorkspace", func(t *testing.T) {
102145
t.Parallel()
103146

0 commit comments

Comments
 (0)
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