Skip to content

Commit 1aa9e32

Browse files
authored
feat: add --ssh-host-prefix flag for "coder ssh" (#16088)
This adds a flag matching `--ssh-host-prefix` from `coder config-ssh` to `coder ssh`. By trimming a custom prefix from the argument, we can set up wildcard-based `Host` entries in SSH config for the IDE plugins (and eventually `coder config-ssh`). We also replace `--` in the argument with `/`, so ownership can be specified in wildcard-based SSH hosts like `<owner>--<workspace>`. Replaces #16087. Part of #14986. Related to #16078 and #16080.
1 parent ec6645b commit 1aa9e32

File tree

4 files changed

+89
-1
lines changed

4 files changed

+89
-1
lines changed

cli/ssh.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ var (
6161
func (r *RootCmd) ssh() *serpent.Command {
6262
var (
6363
stdio bool
64+
hostPrefix string
6465
forwardAgent bool
6566
forwardGPG bool
6667
identityAgent string
@@ -195,7 +196,11 @@ func (r *RootCmd) ssh() *serpent.Command {
195196
parsedEnv = append(parsedEnv, [2]string{k, v})
196197
}
197198

198-
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, !disableAutostart, inv.Args[0])
199+
namedWorkspace := strings.TrimPrefix(inv.Args[0], hostPrefix)
200+
// Support "--" as a delimiter between owner and workspace name
201+
namedWorkspace = strings.Replace(namedWorkspace, "--", "/", 1)
202+
203+
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, !disableAutostart, namedWorkspace)
199204
if err != nil {
200205
return err
201206
}
@@ -509,6 +514,12 @@ func (r *RootCmd) ssh() *serpent.Command {
509514
Description: "Specifies whether to emit SSH output over stdin/stdout.",
510515
Value: serpent.BoolOf(&stdio),
511516
},
517+
{
518+
Flag: "ssh-host-prefix",
519+
Env: "CODER_SSH_SSH_HOST_PREFIX",
520+
Description: "Strip this prefix from the provided hostname to determine the workspace name. This is useful when used as part of an OpenSSH proxy command.",
521+
Value: serpent.StringOf(&hostPrefix),
522+
},
512523
{
513524
Flag: "forward-agent",
514525
FlagShorthand: "A",

cli/ssh_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,69 @@ func TestSSH(t *testing.T) {
15681568
})
15691569
}
15701570
})
1571+
1572+
t.Run("SSHHostPrefix", func(t *testing.T) {
1573+
t.Parallel()
1574+
client, workspace, agentToken := setupWorkspaceForAgent(t)
1575+
_, _ = tGoContext(t, func(ctx context.Context) {
1576+
// Run this async so the SSH command has to wait for
1577+
// the build and agent to connect!
1578+
_ = agenttest.New(t, client.URL, agentToken)
1579+
<-ctx.Done()
1580+
})
1581+
1582+
clientOutput, clientInput := io.Pipe()
1583+
serverOutput, serverInput := io.Pipe()
1584+
defer func() {
1585+
for _, c := range []io.Closer{clientOutput, clientInput, serverOutput, serverInput} {
1586+
_ = c.Close()
1587+
}
1588+
}()
1589+
1590+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1591+
defer cancel()
1592+
1593+
user, err := client.User(ctx, codersdk.Me)
1594+
require.NoError(t, err)
1595+
1596+
inv, root := clitest.New(t, "ssh", "--stdio", "--ssh-host-prefix", "coder.dummy.com--", fmt.Sprintf("coder.dummy.com--%s--%s", user.Username, workspace.Name))
1597+
clitest.SetupConfig(t, client, root)
1598+
inv.Stdin = clientOutput
1599+
inv.Stdout = serverInput
1600+
inv.Stderr = io.Discard
1601+
1602+
cmdDone := tGo(t, func() {
1603+
err := inv.WithContext(ctx).Run()
1604+
assert.NoError(t, err)
1605+
})
1606+
1607+
conn, channels, requests, err := ssh.NewClientConn(&stdioConn{
1608+
Reader: serverOutput,
1609+
Writer: clientInput,
1610+
}, "", &ssh.ClientConfig{
1611+
// #nosec
1612+
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
1613+
})
1614+
require.NoError(t, err)
1615+
defer conn.Close()
1616+
1617+
sshClient := ssh.NewClient(conn, channels, requests)
1618+
session, err := sshClient.NewSession()
1619+
require.NoError(t, err)
1620+
defer session.Close()
1621+
1622+
command := "sh -c exit"
1623+
if runtime.GOOS == "windows" {
1624+
command = "cmd.exe /c exit"
1625+
}
1626+
err = session.Run(command)
1627+
require.NoError(t, err)
1628+
err = sshClient.Close()
1629+
require.NoError(t, err)
1630+
_ = clientOutput.Close()
1631+
1632+
<-cmdDone
1633+
})
15711634
}
15721635

15731636
//nolint:paralleltest // This test uses t.Setenv, parent test MUST NOT be parallel.

cli/testdata/coder_ssh_--help.golden

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ OPTIONS:
4545
-R, --remote-forward string-array, $CODER_SSH_REMOTE_FORWARD
4646
Enable remote port forwarding (remote_port:local_address:local_port).
4747

48+
--ssh-host-prefix string, $CODER_SSH_SSH_HOST_PREFIX
49+
Strip this prefix from the provided hostname to determine the
50+
workspace name. This is useful when used as part of an OpenSSH proxy
51+
command.
52+
4853
--stdio bool, $CODER_SSH_STDIO
4954
Specifies whether to emit SSH output over stdin/stdout.
5055

docs/reference/cli/ssh.md

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)