Skip to content

Commit e99d184

Browse files
committed
feat: Add --env flag for "coder ssh"
This allows environment variables to be set on the SSH session. Example: coder ssh myworkspace --env VAR1=val1,VAR2=val2
1 parent b85d5d8 commit e99d184

File tree

3 files changed

+76
-9
lines changed

3 files changed

+76
-9
lines changed

cli/ssh.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func (r *RootCmd) ssh() *serpent.Command {
5656
noWait bool
5757
logDirPath string
5858
remoteForwards []string
59+
env []string
5960
disableAutostart bool
6061
)
6162
client := new(codersdk.Client)
@@ -145,16 +146,23 @@ func (r *RootCmd) ssh() *serpent.Command {
145146
stack := newCloserStack(ctx, logger)
146147
defer stack.close(nil)
147148

148-
if len(remoteForwards) > 0 {
149-
for _, remoteForward := range remoteForwards {
150-
isValid := validateRemoteForward(remoteForward)
151-
if !isValid {
152-
return xerrors.Errorf(`invalid format of remote-forward, expected: remote_port:local_address:local_port`)
153-
}
154-
if isValid && stdio {
155-
return xerrors.Errorf(`remote-forward can't be enabled in the stdio mode`)
156-
}
149+
for _, remoteForward := range remoteForwards {
150+
isValid := validateRemoteForward(remoteForward)
151+
if !isValid {
152+
return xerrors.Errorf(`invalid format of remote-forward, expected: remote_port:local_address:local_port`)
153+
}
154+
if isValid && stdio {
155+
return xerrors.Errorf(`remote-forward can't be enabled in the stdio mode`)
156+
}
157+
}
158+
159+
var parsedEnv [][2]string
160+
for _, e := range env {
161+
k, v, ok := strings.Cut(e, "=")
162+
if !ok {
163+
return xerrors.Errorf("invalid environment variable setting %q", e)
157164
}
165+
parsedEnv = append(parsedEnv, [2]string{k, v})
158166
}
159167

160168
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, !disableAutostart, inv.Args[0])
@@ -369,6 +377,12 @@ func (r *RootCmd) ssh() *serpent.Command {
369377
}()
370378
}
371379

380+
for _, kv := range parsedEnv {
381+
if err := sshSession.Setenv(kv[0], kv[1]); err != nil {
382+
return xerrors.Errorf("setenv: %w", err)
383+
}
384+
}
385+
372386
err = sshSession.RequestPty("xterm-256color", 128, 128, gossh.TerminalModes{})
373387
if err != nil {
374388
return xerrors.Errorf("request pty: %w", err)
@@ -477,6 +491,13 @@ func (r *RootCmd) ssh() *serpent.Command {
477491
FlagShorthand: "R",
478492
Value: serpent.StringArrayOf(&remoteForwards),
479493
},
494+
{
495+
Flag: "env",
496+
Description: "Set environment variable(s) for session (key1=value1,key2=value2,...).",
497+
Env: "CODER_SSH_ENV",
498+
FlagShorthand: "e",
499+
Value: serpent.StringArrayOf(&env),
500+
},
480501
sshDisableAutostartOption(serpent.BoolOf(&disableAutostart)),
481502
}
482503
return cmd

cli/ssh_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,49 @@ func TestSSH(t *testing.T) {
968968
<-cmdDone
969969
})
970970

971+
t.Run("Env", func(t *testing.T) {
972+
if runtime.GOOS == "windows" {
973+
t.Skip("Test not supported on windows")
974+
}
975+
976+
t.Parallel()
977+
978+
client, workspace, agentToken := setupWorkspaceForAgent(t)
979+
_ = agenttest.New(t, client.URL, agentToken)
980+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
981+
982+
inv, root := clitest.New(t,
983+
"ssh",
984+
workspace.Name,
985+
"--env",
986+
"foo=bar,baz=qux",
987+
)
988+
clitest.SetupConfig(t, client, root)
989+
990+
pty := ptytest.New(t).Attach(inv)
991+
inv.Stderr = pty.Output()
992+
993+
// Wait super long so this doesn't flake on -race test.
994+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitSuperLong)
995+
defer cancel()
996+
997+
w := clitest.StartWithWaiter(t, inv.WithContext(ctx))
998+
defer w.Wait() // We don't care about any exit error (exit code 255: SSH connection ended unexpectedly).
999+
1000+
// Since something was output, it should be safe to write input.
1001+
// This could show a prompt or "running startup scripts", so it's
1002+
// not indicative of the SSH connection being ready.
1003+
_ = pty.Peek(ctx, 1)
1004+
1005+
// Ensure the SSH connection is ready by testing the shell
1006+
// input/output.
1007+
pty.WriteLine("echo $foo $baz")
1008+
pty.ExpectMatchContext(ctx, "bar qux")
1009+
1010+
// And we're done.
1011+
pty.WriteLine("exit")
1012+
})
1013+
9711014
t.Run("RemoteForwardUnixSocket", func(t *testing.T) {
9721015
if runtime.GOOS == "windows" {
9731016
t.Skip("Test not supported on windows")

cli/testdata/coder_ssh_--help.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ OPTIONS:
99
--disable-autostart bool, $CODER_SSH_DISABLE_AUTOSTART (default: false)
1010
Disable starting the workspace automatically when connecting via SSH.
1111

12+
-e, --env string-array, $CODER_SSH_ENV
13+
Set environment variable(s) for session (key1=value1,key2=value2,...).
14+
1215
-A, --forward-agent bool, $CODER_SSH_FORWARD_AGENT
1316
Specifies whether to forward the SSH agent specified in
1417
$SSH_AUTH_SOCK.

0 commit comments

Comments
 (0)