mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(agent): wire up agentssh server to allow exec into container (#16638)
Builds on top of https://github.com/coder/coder/pull/16623/ and wires up the ReconnectingPTY server. This does nothing to wire up the web terminal yet but the added test demonstrates the functionality working. Other changes: * Refactors and moves the `SystemEnvInfo` interface to the `agent/usershell` package to address follow-up from https://github.com/coder/coder/pull/16623#discussion_r1967580249 * Marks `usershellinfo.Get` as deprecated. Consumers should use the `EnvInfoer` interface instead. --------- Co-authored-by: Mathias Fredriksson <mafredri@gmail.com> Co-authored-by: Danny Kopping <danny@coder.com>
This commit is contained in:
co-authored by
Mathias Fredriksson
Danny Kopping
parent
a3223397cb
commit
172e52317c
+19
-47
@@ -698,45 +698,6 @@ func (s *Server) sftpHandler(logger slog.Logger, session ssh.Session) {
|
||||
_ = session.Exit(1)
|
||||
}
|
||||
|
||||
// EnvInfoer encapsulates external information required by CreateCommand.
|
||||
type EnvInfoer interface {
|
||||
// CurrentUser returns the current user.
|
||||
CurrentUser() (*user.User, error)
|
||||
// Environ returns the environment variables of the current process.
|
||||
Environ() []string
|
||||
// UserHomeDir returns the home directory of the current user.
|
||||
UserHomeDir() (string, error)
|
||||
// UserShell returns the shell of the given user.
|
||||
UserShell(username string) (string, error)
|
||||
}
|
||||
|
||||
type systemEnvInfoer struct{}
|
||||
|
||||
var defaultEnvInfoer EnvInfoer = &systemEnvInfoer{}
|
||||
|
||||
// DefaultEnvInfoer returns a default implementation of
|
||||
// EnvInfoer. This reads information using the default Go
|
||||
// implementations.
|
||||
func DefaultEnvInfoer() EnvInfoer {
|
||||
return defaultEnvInfoer
|
||||
}
|
||||
|
||||
func (systemEnvInfoer) CurrentUser() (*user.User, error) {
|
||||
return user.Current()
|
||||
}
|
||||
|
||||
func (systemEnvInfoer) Environ() []string {
|
||||
return os.Environ()
|
||||
}
|
||||
|
||||
func (systemEnvInfoer) UserHomeDir() (string, error) {
|
||||
return userHomeDir()
|
||||
}
|
||||
|
||||
func (systemEnvInfoer) UserShell(username string) (string, error) {
|
||||
return usershell.Get(username)
|
||||
}
|
||||
|
||||
// CreateCommand processes raw command input with OpenSSH-like behavior.
|
||||
// If the script provided is empty, it will default to the users shell.
|
||||
// This injects environment variables specified by the user at launch too.
|
||||
@@ -744,17 +705,17 @@ func (systemEnvInfoer) UserShell(username string) (string, error) {
|
||||
// alternative implementations for the dependencies of CreateCommand.
|
||||
// This is useful when creating a command to be run in a separate environment
|
||||
// (for example, a Docker container). Pass in nil to use the default.
|
||||
func (s *Server) CreateCommand(ctx context.Context, script string, env []string, deps EnvInfoer) (*pty.Cmd, error) {
|
||||
if deps == nil {
|
||||
deps = DefaultEnvInfoer()
|
||||
func (s *Server) CreateCommand(ctx context.Context, script string, env []string, ei usershell.EnvInfoer) (*pty.Cmd, error) {
|
||||
if ei == nil {
|
||||
ei = &usershell.SystemEnvInfo{}
|
||||
}
|
||||
currentUser, err := deps.CurrentUser()
|
||||
currentUser, err := ei.User()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get current user: %w", err)
|
||||
}
|
||||
username := currentUser.Username
|
||||
|
||||
shell, err := deps.UserShell(username)
|
||||
shell, err := ei.Shell(username)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get user shell: %w", err)
|
||||
}
|
||||
@@ -802,7 +763,18 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string,
|
||||
}
|
||||
}
|
||||
|
||||
cmd := s.Execer.PTYCommandContext(ctx, name, args...)
|
||||
// Modify command prior to execution. This will usually be a no-op, but not
|
||||
// always. For example, to run a command in a Docker container, we need to
|
||||
// modify the command to be `docker exec -it <container> <command>`.
|
||||
modifiedName, modifiedArgs := ei.ModifyCommand(name, args...)
|
||||
// Log if the command was modified.
|
||||
if modifiedName != name && slices.Compare(modifiedArgs, args) != 0 {
|
||||
s.logger.Debug(ctx, "modified command",
|
||||
slog.F("before", append([]string{name}, args...)),
|
||||
slog.F("after", append([]string{modifiedName}, modifiedArgs...)),
|
||||
)
|
||||
}
|
||||
cmd := s.Execer.PTYCommandContext(ctx, modifiedName, modifiedArgs...)
|
||||
cmd.Dir = s.config.WorkingDirectory()
|
||||
|
||||
// If the metadata directory doesn't exist, we run the command
|
||||
@@ -810,13 +782,13 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string,
|
||||
_, err = os.Stat(cmd.Dir)
|
||||
if cmd.Dir == "" || err != nil {
|
||||
// Default to user home if a directory is not set.
|
||||
homedir, err := deps.UserHomeDir()
|
||||
homedir, err := ei.HomeDir()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get home dir: %w", err)
|
||||
}
|
||||
cmd.Dir = homedir
|
||||
}
|
||||
cmd.Env = append(deps.Environ(), env...)
|
||||
cmd.Env = append(ei.Environ(), env...)
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", username))
|
||||
|
||||
// Set SSH connection environment variables (these are also set by OpenSSH
|
||||
|
||||
@@ -124,7 +124,7 @@ type fakeEnvInfoer struct {
|
||||
UserShellFn func(string) (string, error)
|
||||
}
|
||||
|
||||
func (f *fakeEnvInfoer) CurrentUser() (u *user.User, err error) {
|
||||
func (f *fakeEnvInfoer) User() (u *user.User, err error) {
|
||||
return f.CurrentUserFn()
|
||||
}
|
||||
|
||||
@@ -132,14 +132,18 @@ func (f *fakeEnvInfoer) Environ() []string {
|
||||
return f.EnvironFn()
|
||||
}
|
||||
|
||||
func (f *fakeEnvInfoer) UserHomeDir() (string, error) {
|
||||
func (f *fakeEnvInfoer) HomeDir() (string, error) {
|
||||
return f.UserHomeDirFn()
|
||||
}
|
||||
|
||||
func (f *fakeEnvInfoer) UserShell(u string) (string, error) {
|
||||
func (f *fakeEnvInfoer) Shell(u string) (string, error) {
|
||||
return f.UserShellFn(u)
|
||||
}
|
||||
|
||||
func (*fakeEnvInfoer) ModifyCommand(cmd string, args ...string) (string, []string) {
|
||||
return cmd, args
|
||||
}
|
||||
|
||||
func TestNewServer_CloseActiveConnections(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user