Skip to content

Commit 690e91c

Browse files
committed
refactor: move SystemEnvInfo to usershell
1 parent 36707f9 commit 690e91c

File tree

8 files changed

+89
-74
lines changed

8 files changed

+89
-74
lines changed

agent/agentcontainers/containers_dockercli.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"context"
77
"encoding/json"
88
"fmt"
9-
"os"
109
"os/user"
1110
"slices"
1211
"sort"
@@ -15,12 +14,18 @@ import (
1514
"time"
1615

1716
"github.com/coder/coder/v2/agent/agentexec"
17+
"github.com/coder/coder/v2/agent/usershell"
1818
"github.com/coder/coder/v2/codersdk"
1919

2020
"golang.org/x/exp/maps"
2121
"golang.org/x/xerrors"
2222
)
2323

24+
const (
25+
RuntimeSystem int = iota
26+
RuntimeDocker
27+
)
28+
2429
// DockerCLILister is a ContainerLister that lists containers using the docker CLI
2530
type DockerCLILister struct {
2631
execer agentexec.Execer
@@ -37,6 +42,7 @@ func NewDocker(execer agentexec.Execer) Lister {
3742
// DockerEnvInfoer is an implementation of agentssh.EnvInfoer that returns
3843
// information about a container.
3944
type DockerEnvInfoer struct {
45+
usershell.SystemEnvInfo
4046
container string
4147
user *user.User
4248
userShell string
@@ -128,19 +134,6 @@ func (dei *DockerEnvInfoer) CurrentUser() (*user.User, error) {
128134
return &u, nil
129135
}
130136

131-
func (*DockerEnvInfoer) Environ() []string {
132-
// Return a clone of the environment so that the caller can't modify it
133-
return os.Environ()
134-
}
135-
136-
func (*DockerEnvInfoer) UserHomeDir() (string, error) {
137-
// We default the working directory of the command to the user's home
138-
// directory. Since this came from inside the container, we cannot guarantee
139-
// that this exists on the host. Return the "real" home directory of the user
140-
// instead.
141-
return os.UserHomeDir()
142-
}
143-
144137
func (dei *DockerEnvInfoer) UserShell(string) (string, error) {
145138
return dei.userShell, nil
146139
}

agent/agentssh/agentssh.go

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -698,69 +698,24 @@ func (s *Server) sftpHandler(logger slog.Logger, session ssh.Session) {
698698
_ = session.Exit(1)
699699
}
700700

701-
// EnvInfoer encapsulates external information required by CreateCommand.
702-
type EnvInfoer interface {
703-
// CurrentUser returns the current user.
704-
CurrentUser() (*user.User, error)
705-
// Environ returns the environment variables of the current process.
706-
Environ() []string
707-
// UserHomeDir returns the home directory of the current user.
708-
UserHomeDir() (string, error)
709-
// UserShell returns the shell of the given user.
710-
UserShell(username string) (string, error)
711-
// ModifyCommand modifies the command and arguments before execution.
712-
ModifyCommand(name string, args ...string) (string, []string)
713-
}
714-
715-
type systemEnvInfoer struct{}
716-
717-
var defaultEnvInfoer EnvInfoer = &systemEnvInfoer{}
718-
719-
// DefaultEnvInfoer returns a default implementation of
720-
// EnvInfoer. This reads information using the default Go
721-
// implementations.
722-
func DefaultEnvInfoer() EnvInfoer {
723-
return defaultEnvInfoer
724-
}
725-
726-
func (systemEnvInfoer) CurrentUser() (*user.User, error) {
727-
return user.Current()
728-
}
729-
730-
func (systemEnvInfoer) Environ() []string {
731-
return os.Environ()
732-
}
733-
734-
func (systemEnvInfoer) UserHomeDir() (string, error) {
735-
return userHomeDir()
736-
}
737-
738-
func (systemEnvInfoer) UserShell(username string) (string, error) {
739-
return usershell.Get(username)
740-
}
741-
742-
func (systemEnvInfoer) ModifyCommand(name string, args ...string) (string, []string) {
743-
return name, args
744-
}
745-
746701
// CreateCommand processes raw command input with OpenSSH-like behavior.
747702
// If the script provided is empty, it will default to the users shell.
748703
// This injects environment variables specified by the user at launch too.
749704
// The final argument is an interface that allows the caller to provide
750705
// alternative implementations for the dependencies of CreateCommand.
751706
// This is useful when creating a command to be run in a separate environment
752707
// (for example, a Docker container). Pass in nil to use the default.
753-
func (s *Server) CreateCommand(ctx context.Context, script string, env []string, deps EnvInfoer) (*pty.Cmd, error) {
754-
if deps == nil {
755-
deps = DefaultEnvInfoer()
708+
func (s *Server) CreateCommand(ctx context.Context, script string, env []string, ei usershell.EnvInfoer) (*pty.Cmd, error) {
709+
if ei == nil {
710+
ei = &usershell.SystemEnvInfo{}
756711
}
757-
currentUser, err := deps.CurrentUser()
712+
currentUser, err := ei.CurrentUser()
758713
if err != nil {
759714
return nil, xerrors.Errorf("get current user: %w", err)
760715
}
761716
username := currentUser.Username
762717

763-
shell, err := deps.UserShell(username)
718+
shell, err := ei.UserShell(username)
764719
if err != nil {
765720
return nil, xerrors.Errorf("get user shell: %w", err)
766721
}
@@ -809,7 +764,7 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string,
809764
}
810765

811766
// Modify command prior to execution. This will usually be a no-op, but not always.
812-
modifiedName, modifiedArgs := deps.ModifyCommand(name, args...)
767+
modifiedName, modifiedArgs := ei.ModifyCommand(name, args...)
813768
s.logger.Info(ctx, "modified command",
814769
slog.F("before", append([]string{name}, args...)),
815770
slog.F("after", append([]string{modifiedName}, modifiedArgs...)),
@@ -822,13 +777,13 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string,
822777
_, err = os.Stat(cmd.Dir)
823778
if cmd.Dir == "" || err != nil {
824779
// Default to user home if a directory is not set.
825-
homedir, err := deps.UserHomeDir()
780+
homedir, err := ei.UserHomeDir()
826781
if err != nil {
827782
return nil, xerrors.Errorf("get home dir: %w", err)
828783
}
829784
cmd.Dir = homedir
830785
}
831-
cmd.Env = append(deps.Environ(), env...)
786+
cmd.Env = append(ei.Environ(), env...)
832787
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", username))
833788

834789
// Set SSH connection environment variables (these are also set by OpenSSH

agent/reconnectingpty/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"cdr.dev/slog"
1717
"github.com/coder/coder/v2/agent/agentcontainers"
1818
"github.com/coder/coder/v2/agent/agentssh"
19+
"github.com/coder/coder/v2/agent/usershell"
1920
"github.com/coder/coder/v2/codersdk/workspacesdk"
2021
)
2122

@@ -159,7 +160,7 @@ func (s *Server) handleConn(ctx context.Context, logger slog.Logger, conn net.Co
159160
}
160161
}()
161162

162-
var ei agentssh.EnvInfoer
163+
var ei usershell.EnvInfoer
163164
if msg.Container != "" {
164165
dei, err := agentcontainers.EnvInfo(ctx, s.commandCreator.Execer, msg.Container, msg.ContainerUser)
165166
if err != nil {

agent/usershell/usershell.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package usershell
2+
3+
import (
4+
"os"
5+
"os/user"
6+
7+
"golang.org/x/xerrors"
8+
)
9+
10+
// HomeDir returns the home directory of the current user, giving
11+
// priority to the $HOME environment variable.
12+
func HomeDir() (string, error) {
13+
// First we check the environment.
14+
homedir, err := os.UserHomeDir()
15+
if err == nil {
16+
return homedir, nil
17+
}
18+
19+
// As a fallback, we try the user information.
20+
u, err := user.Current()
21+
if err != nil {
22+
return "", xerrors.Errorf("current user: %w", err)
23+
}
24+
return u.HomeDir, nil
25+
}
26+
27+
// EnvInfoer encapsulates external information about the environment.
28+
type EnvInfoer interface {
29+
// CurrentUser returns the current user.
30+
CurrentUser() (*user.User, error)
31+
// Environ returns the environment variables of the current process.
32+
Environ() []string
33+
// UserHomeDir returns the home directory of the current user.
34+
UserHomeDir() (string, error)
35+
// UserShell returns the shell of the given user.
36+
UserShell(username string) (string, error)
37+
// ModifyCommand modifies the command and arguments before execution based on
38+
// the environment. This is useful for executing a command inside a container.
39+
// In the default case, the command and arguments are returned unchanged.
40+
ModifyCommand(name string, args ...string) (string, []string)
41+
}
42+
43+
// SystemEnvInfo encapsulates the information about the environment
44+
// just using the default Go implementations.
45+
type SystemEnvInfo struct{}
46+
47+
func (SystemEnvInfo) CurrentUser() (*user.User, error) {
48+
return user.Current()
49+
}
50+
51+
func (SystemEnvInfo) Environ() []string {
52+
return os.Environ()
53+
}
54+
55+
func (SystemEnvInfo) UserHomeDir() (string, error) {
56+
return HomeDir()
57+
}
58+
59+
func (SystemEnvInfo) UserShell(username string) (string, error) {
60+
return Get(username)
61+
}
62+
63+
func (SystemEnvInfo) ModifyCommand(name string, args ...string) (string, []string) {
64+
return name, args
65+
}

agent/usershell/usershell_darwin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
)
1111

1212
// Get returns the $SHELL environment variable.
13+
// Deprecated: use SystemEnvInfo.UserShell instead.
1314
func Get(username string) (string, error) {
1415
// This command will output "UserShell: /bin/zsh" if successful, we
1516
// can ignore the error since we have fallback behavior.

agent/usershell/usershell_test.go renamed to agent/usershell/usershell_internal_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
package usershell_test
1+
package usershell
22

33
import (
44
"os/user"
55
"runtime"
66
"testing"
77

88
"github.com/stretchr/testify/require"
9-
10-
"github.com/coder/coder/v2/agent/usershell"
119
)
1210

1311
//nolint:paralleltest,tparallel // This test sets an environment variable.
@@ -20,7 +18,7 @@ func TestGet(t *testing.T) {
2018
t.Setenv("SHELL", "/bin/sh")
2119

2220
t.Run("NonExistentUser", func(t *testing.T) {
23-
shell, err := usershell.Get("notauser")
21+
shell, err := Get("notauser")
2422
require.NoError(t, err)
2523
require.Equal(t, "/bin/sh", shell)
2624
})
@@ -31,14 +29,14 @@ func TestGet(t *testing.T) {
3129
t.Setenv("SHELL", "")
3230

3331
t.Run("NotFound", func(t *testing.T) {
34-
_, err := usershell.Get("notauser")
32+
_, err := Get("notauser")
3533
require.Error(t, err)
3634
})
3735

3836
t.Run("User", func(t *testing.T) {
3937
u, err := user.Current()
4038
require.NoError(t, err)
41-
shell, err := usershell.Get(u.Username)
39+
shell, err := Get(u.Username)
4240
require.NoError(t, err)
4341
require.NotEmpty(t, shell)
4442
})

agent/usershell/usershell_other.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
)
1212

1313
// Get returns the /etc/passwd entry for the username provided.
14+
// Deprecated: use SystemEnvInfo.UserShell instead.
1415
func Get(username string) (string, error) {
1516
contents, err := os.ReadFile("/etc/passwd")
1617
if err != nil {

agent/usershell/usershell_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package usershell
33
import "os/exec"
44

55
// Get returns the command prompt binary name.
6+
// Deprecated: use SystemEnvInfo.UserShell instead.
67
func Get(username string) (string, error) {
78
_, err := exec.LookPath("pwsh.exe")
89
if err == nil {

0 commit comments

Comments
 (0)