func (*NativeExecHandler) ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error { createOpts := dockertypes.ExecConfig{ Cmd: cmd, AttachStdin: stdin != nil, AttachStdout: stdout != nil, AttachStderr: stderr != nil, Tty: tty, } execObj, err := client.CreateExec(container.ID, createOpts) if err != nil { return fmt.Errorf("failed to exec in container - Exec setup failed - %v", err) } // Have to start this before the call to client.StartExec because client.StartExec is a blocking // call :-( Otherwise, resize events don't get processed and the terminal never resizes. kubecontainer.HandleResizing(resize, func(size term.Size) { client.ResizeExecTTY(execObj.ID, int(size.Height), int(size.Width)) }) startOpts := dockertypes.ExecStartCheck{Detach: false, Tty: tty} streamOpts := StreamOptions{ InputStream: stdin, OutputStream: stdout, ErrorStream: stderr, RawTerminal: tty, } err = client.StartExec(execObj.ID, startOpts, streamOpts) if err != nil { return err } ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() count := 0 for { inspect, err2 := client.InspectExec(execObj.ID) if err2 != nil { return err2 } if !inspect.Running { if inspect.ExitCode != 0 { err = &dockerExitError{inspect} } break } count++ if count == 5 { glog.Errorf("Exec session %s in container %s terminated but process still running!", execObj.ID, container.ID) break } <-ticker.C } return err }
// TODO should we support nsenter in a container, running with elevated privs and --pid=host? func (*NsenterExecHandler) ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error { nsenter, err := exec.LookPath("nsenter") if err != nil { return fmt.Errorf("exec unavailable - unable to locate nsenter") } containerPid := container.State.Pid // TODO what if the container doesn't have `env`??? args := []string{"-t", fmt.Sprintf("%d", containerPid), "-m", "-i", "-u", "-n", "-p", "--", "env", "-i"} args = append(args, fmt.Sprintf("HOSTNAME=%s", container.Config.Hostname)) args = append(args, container.Config.Env...) args = append(args, cmd...) command := exec.Command(nsenter, args...) if tty { p, err := kubecontainer.StartPty(command) if err != nil { return err } defer p.Close() // make sure to close the stdout stream defer stdout.Close() kubecontainer.HandleResizing(resize, func(size term.Size) { term.SetSize(p.Fd(), size) }) if stdin != nil { go io.Copy(p, stdin) } if stdout != nil { go io.Copy(stdout, p) } return command.Wait() } else { if stdin != nil { // Use an os.Pipe here as it returns true *os.File objects. // This way, if you run 'kubectl exec <pod> -i bash' (no tty) and type 'exit', // the call below to command.Run() can unblock because its Stdin is the read half // of the pipe. r, w, err := os.Pipe() if err != nil { return err } go io.Copy(w, stdin) command.Stdin = r } if stdout != nil { command.Stdout = stdout } if stderr != nil { command.Stderr = stderr } return command.Run() } }