Merge branch '17170-container-shell'
[arvados.git] / lib / crunchrun / container_gateway.go
index 0d869ca7fc750f4969c5f423245dc1c860124b7d..1116c4bb1285d11ae39b4e194f21df6fd01c5d24 100644 (file)
@@ -126,8 +126,9 @@ func (gw *Gateway) Start() error {
        return nil
 }
 
-// handleSSH connects to an SSH server that runs commands as root in
-// the container. The tunnel itself can only be created by an
+// handleSSH connects to an SSH server that allows the caller to run
+// interactive commands as root (or any other desired user) inside the
+// container. The tunnel itself can only be created by an
 // authenticated caller, so the SSH server itself is wide open (any
 // password or key will be accepted).
 //
@@ -140,10 +141,12 @@ func (gw *Gateway) Start() error {
 // hmac(AuthSecret,certfingerprint) (this prevents other containers
 // and shell nodes from connecting directly)
 //
-// Optional header:
+// Optional headers:
 //
-// X-Arvados-Detach-Keys: argument to "docker attach --detach-keys",
+// X-Arvados-Detach-Keys: argument to "docker exec --detach-keys",
 // e.g., "ctrl-p,ctrl-q"
+// X-Arvados-Login-Username: argument to "docker exec --user": account
+// used to run command(s) inside the container.
 func (gw *Gateway) handleSSH(w http.ResponseWriter, req *http.Request) {
        // In future we'll handle browser traffic too, but for now the
        // only traffic we expect is an SSH tunnel from
@@ -194,7 +197,7 @@ func (gw *Gateway) handleSSH(w http.ResponseWriter, req *http.Request) {
        go ssh.DiscardRequests(reqs)
        for newch := range newchans {
                if newch.ChannelType() != "session" {
-                       newch.Reject(ssh.UnknownChannelType, "unknown channel type")
+                       newch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unsupported channel type %q", newch.ChannelType()))
                        continue
                }
                ch, reqs, err := newch.Accept()
@@ -256,15 +259,18 @@ func (gw *Gateway) handleSSH(w http.ResponseWriter, req *http.Request) {
                                                }
                                                cmd.Env = append(os.Environ(), termEnv...)
                                                err := cmd.Run()
-                                               errClose := ch.CloseWrite()
                                                var resp struct {
                                                        Status uint32
                                                }
-                                               if err, ok := err.(*exec.ExitError); ok {
-                                                       if status, ok := err.Sys().(syscall.WaitStatus); ok {
+                                               if exiterr, ok := err.(*exec.ExitError); ok {
+                                                       if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
                                                                resp.Status = uint32(status.ExitStatus())
                                                        }
+                                               } else if err != nil {
+                                                       // Propagate errors like `exec: "docker": executable file not found in $PATH`
+                                                       fmt.Fprintln(ch.Stderr(), err)
                                                }
+                                               errClose := ch.CloseWrite()
                                                if resp.Status == 0 && (err != nil || errClose != nil) {
                                                        resp.Status = 1
                                                }