@@ -29,6 +29,7 @@ import (
2929
3030 "cdr.dev/slog"
3131
32+ "github.com/coder/coder/v2/agent/agentcontainers"
3233 "github.com/coder/coder/v2/agent/agentexec"
3334 "github.com/coder/coder/v2/agent/agentrsa"
3435 "github.com/coder/coder/v2/agent/usershell"
@@ -60,6 +61,14 @@ const (
6061 // MagicSessionTypeEnvironmentVariable is used to track the purpose behind an SSH connection.
6162 // This is stripped from any commands being executed, and is counted towards connection stats.
6263 MagicSessionTypeEnvironmentVariable = "CODER_SSH_SESSION_TYPE"
64+ // ContainerEnvironmentVariable is used to specify the target container for an SSH connection.
65+ // This is stripped from any commands being executed.
66+ // Only available if CODER_AGENT_DEVCONTAINERS_ENABLE=true.
67+ ContainerEnvironmentVariable = "CODER_CONTAINER"
68+ // ContainerUserEnvironmentVariable is used to specify the container user for
69+ // an SSH connection.
70+ // Only available if CODER_AGENT_DEVCONTAINERS_ENABLE=true.
71+ ContainerUserEnvironmentVariable = "CODER_CONTAINER_USER"
6372)
6473
6574// MagicSessionType enums.
@@ -104,6 +113,9 @@ type Config struct {
104113 BlockFileTransfer bool
105114 // ReportConnection.
106115 ReportConnection reportConnectionFunc
116+ // Experimental: allow connecting to running containers if
117+ // CODER_AGENT_DEVCONTAINERS_ENABLE=true.
118+ ExperimentalDevContainersEnabled bool
107119}
108120
109121type Server struct {
@@ -324,6 +336,22 @@ func (s *sessionCloseTracker) Close() error {
324336 return s .Session .Close ()
325337}
326338
339+ func extractContainerInfo (env []string ) (container , containerUser string , filteredEnv []string ) {
340+ for _ , kv := range env {
341+ if strings .HasPrefix (kv , ContainerEnvironmentVariable + "=" ) {
342+ container = strings .TrimPrefix (kv , ContainerEnvironmentVariable + "=" )
343+ }
344+
345+ if strings .HasPrefix (kv , ContainerUserEnvironmentVariable + "=" ) {
346+ containerUser = strings .TrimPrefix (kv , ContainerUserEnvironmentVariable + "=" )
347+ }
348+ }
349+
350+ return container , containerUser , slices .DeleteFunc (env , func (kv string ) bool {
351+ return strings .HasPrefix (kv , ContainerEnvironmentVariable + "=" ) || strings .HasPrefix (kv , ContainerUserEnvironmentVariable + "=" )
352+ })
353+ }
354+
327355func (s * Server ) sessionHandler (session ssh.Session ) {
328356 ctx := session .Context ()
329357 id := uuid .New ()
@@ -353,6 +381,7 @@ func (s *Server) sessionHandler(session ssh.Session) {
353381 defer s .trackSession (session , false )
354382
355383 reportSession := true
384+
356385 switch magicType {
357386 case MagicSessionTypeVSCode :
358387 s .connCountVSCode .Add (1 )
@@ -395,9 +424,22 @@ func (s *Server) sessionHandler(session ssh.Session) {
395424 return
396425 }
397426
427+ container , containerUser , env := extractContainerInfo (env )
428+ if container != "" {
429+ s .logger .Debug (ctx , "container info" ,
430+ slog .F ("container" , container ),
431+ slog .F ("container_user" , containerUser ),
432+ )
433+ }
434+
398435 switch ss := session .Subsystem (); ss {
399436 case "" :
400437 case "sftp" :
438+ if s .config .ExperimentalDevContainersEnabled && container != "" {
439+ closeCause ("sftp not yet supported with containers" )
440+ _ = session .Exit (1 )
441+ return
442+ }
401443 err := s .sftpHandler (logger , session )
402444 if err != nil {
403445 closeCause (err .Error ())
@@ -422,7 +464,7 @@ func (s *Server) sessionHandler(session ssh.Session) {
422464 env = append (env , fmt .Sprintf ("DISPLAY=localhost:%d.%d" , display , x11 .ScreenNumber ))
423465 }
424466
425- err := s .sessionStart (logger , session , env , magicType )
467+ err := s .sessionStart (logger , session , env , magicType , container , containerUser )
426468 var exitError * exec.ExitError
427469 if xerrors .As (err , & exitError ) {
428470 code := exitError .ExitCode ()
@@ -495,30 +537,34 @@ func (s *Server) fileTransferBlocked(session ssh.Session) bool {
495537 return false
496538}
497539
498- func (s * Server ) sessionStart (logger slog.Logger , session ssh.Session , env []string , magicType MagicSessionType ) (retErr error ) {
540+ func (s * Server ) sessionStart (logger slog.Logger , session ssh.Session , env []string , magicType MagicSessionType , container , containerUser string ) (retErr error ) {
499541 ctx := session .Context ()
500542
501543 magicTypeLabel := magicTypeMetricLabel (magicType )
502544 sshPty , windowSize , isPty := session .Pty ()
545+ ptyLabel := "no"
546+ if isPty {
547+ ptyLabel = "yes"
548+ }
503549
504- cmd , err := s .CreateCommand (ctx , session .RawCommand (), env , nil )
505- if err != nil {
506- ptyLabel := "no"
507- if isPty {
508- ptyLabel = "yes"
550+ var ei usershell.EnvInfoer
551+ var err error
552+ if s .config .ExperimentalDevContainersEnabled && container != "" {
553+ ei , err = agentcontainers .EnvInfo (ctx , s .Execer , container , containerUser )
554+ if err != nil {
555+ s .metrics .sessionErrors .WithLabelValues (magicTypeLabel , ptyLabel , "container_env_info" ).Add (1 )
556+ return err
509557 }
558+ }
559+ cmd , err := s .CreateCommand (ctx , session .RawCommand (), env , ei )
560+ if err != nil {
510561 s .metrics .sessionErrors .WithLabelValues (magicTypeLabel , ptyLabel , "create_command" ).Add (1 )
511562 return err
512563 }
513564
514565 if ssh .AgentRequested (session ) {
515566 l , err := ssh .NewAgentListener ()
516567 if err != nil {
517- ptyLabel := "no"
518- if isPty {
519- ptyLabel = "yes"
520- }
521-
522568 s .metrics .sessionErrors .WithLabelValues (magicTypeLabel , ptyLabel , "listener" ).Add (1 )
523569 return xerrors .Errorf ("new agent listener: %w" , err )
524570 }
0 commit comments