Skip to content

Commit b9f3fe4

Browse files
authored
fix: Start login shells on macOS and Linux (#2437)
This appends `-l` to the shell command on macOS and Linux. It also adds environment variable expansion to allow for chaining from `coder_agent.env`.
1 parent be02d87 commit b9f3fe4

File tree

3 files changed

+41
-17
lines changed

3 files changed

+41
-17
lines changed

agent/agent.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,11 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
328328
command := rawCommand
329329
if len(command) == 0 {
330330
command = shell
331+
if runtime.GOOS != "windows" {
332+
// On Linux and macOS, we should start a login
333+
// shell to consume juicy environment variables!
334+
command += " -l"
335+
}
331336
}
332337

333338
// OpenSSH executes all commands with the users current shell.
@@ -348,7 +353,6 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
348353
return nil, xerrors.Errorf("getting os executable: %w", err)
349354
}
350355
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", username))
351-
cmd.Env = append(cmd.Env, fmt.Sprintf(`PATH=%s%c%s`, os.Getenv("PATH"), filepath.ListSeparator, filepath.Dir(executablePath)))
352356
// Git on Windows resolves with UNIX-style paths.
353357
// If using backslashes, it's unable to find the executable.
354358
unixExecutablePath := strings.ReplaceAll(executablePath, "\\", "/")
@@ -363,7 +367,10 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
363367
// Load environment variables passed via the agent.
364368
// These should override all variables we manually specify.
365369
for key, value := range metadata.EnvironmentVariables {
366-
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
370+
// Expanding environment variables allows for customization
371+
// of the $PATH, among other variables. Customers can prepand
372+
// or append to the $PATH, so allowing expand is required!
373+
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, os.ExpandEnv(value)))
367374
}
368375

369376
// Agent-level environment variables should take over all!

agent/agent_test.go

+22-15
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,6 @@ func TestAgent(t *testing.T) {
6868
require.True(t, strings.HasSuffix(strings.TrimSpace(string(output)), "gitssh --"))
6969
})
7070

71-
t.Run("PATHHasCoder", func(t *testing.T) {
72-
t.Parallel()
73-
session := setupSSHSession(t, agent.Metadata{})
74-
command := "sh -c 'echo $PATH'"
75-
if runtime.GOOS == "windows" {
76-
command = "cmd.exe /c echo %PATH%"
77-
}
78-
output, err := session.Output(command)
79-
require.NoError(t, err)
80-
ex, err := os.Executable()
81-
t.Log(ex)
82-
require.NoError(t, err)
83-
require.True(t, strings.Contains(strings.TrimSpace(string(output)), filepath.Dir(ex)))
84-
})
85-
8671
t.Run("SessionTTY", func(t *testing.T) {
8772
t.Parallel()
8873
if runtime.GOOS == "windows" {
@@ -182,6 +167,28 @@ func TestAgent(t *testing.T) {
182167
require.Equal(t, value, strings.TrimSpace(string(output)))
183168
})
184169

170+
t.Run("EnvironmentVariableExpansion", func(t *testing.T) {
171+
t.Parallel()
172+
key := "EXAMPLE"
173+
session := setupSSHSession(t, agent.Metadata{
174+
EnvironmentVariables: map[string]string{
175+
key: "$SOMETHINGNOTSET",
176+
},
177+
})
178+
command := "sh -c 'echo $" + key + "'"
179+
if runtime.GOOS == "windows" {
180+
command = "cmd.exe /c echo %" + key + "%"
181+
}
182+
output, err := session.Output(command)
183+
require.NoError(t, err)
184+
expect := ""
185+
if runtime.GOOS == "windows" {
186+
expect = "%EXAMPLE%"
187+
}
188+
// Output should be empty, because the variable is not set!
189+
require.Equal(t, expect, strings.TrimSpace(string(output)))
190+
})
191+
185192
t.Run("StartupScript", func(t *testing.T) {
186193
t.Parallel()
187194
tempPath := filepath.Join(os.TempDir(), "content.txt")

cli/agent.go

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
67
_ "net/http/pprof" //nolint: gosec
78
"net/url"
@@ -138,6 +139,15 @@ func workspaceAgent() *cobra.Command {
138139
}
139140
}
140141

142+
executablePath, err := os.Executable()
143+
if err != nil {
144+
return xerrors.Errorf("getting os executable: %w", err)
145+
}
146+
err = os.Setenv("PATH", fmt.Sprintf("%s%c%s", os.Getenv("PATH"), filepath.ListSeparator, filepath.Dir(executablePath)))
147+
if err != nil {
148+
return xerrors.Errorf("add executable to $PATH: %w", err)
149+
}
150+
141151
closer := agent.New(client.ListenWorkspaceAgent, &agent.Options{
142152
Logger: logger,
143153
EnvironmentVariables: map[string]string{

0 commit comments

Comments
 (0)