exec.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package container
  2. import (
  3. "fmt"
  4. "io"
  5. "golang.org/x/net/context"
  6. "github.com/Sirupsen/logrus"
  7. "github.com/docker/docker/api/client"
  8. "github.com/docker/docker/cli"
  9. "github.com/docker/docker/pkg/promise"
  10. "github.com/docker/engine-api/types"
  11. "github.com/spf13/cobra"
  12. )
  13. type execOptions struct {
  14. detachKeys string
  15. interactive bool
  16. tty bool
  17. detach bool
  18. user string
  19. privileged bool
  20. }
  21. // NewExecCommand creats a new cobra.Command for `docker exec`
  22. func NewExecCommand(dockerCli *client.DockerCli) *cobra.Command {
  23. var opts execOptions
  24. cmd := &cobra.Command{
  25. Use: "exec CONTAINER COMMAND [ARG...]",
  26. Short: "Run a command in a running container",
  27. Args: cli.RequiresMinArgs(2),
  28. RunE: func(cmd *cobra.Command, args []string) error {
  29. container := args[0]
  30. execCmd := args[1:]
  31. return runExec(dockerCli, &opts, container, execCmd)
  32. },
  33. }
  34. flags := cmd.Flags()
  35. flags.SetInterspersed(false)
  36. flags.StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching a container")
  37. flags.BoolVarP(&opts.interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
  38. flags.BoolVarP(&opts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
  39. flags.BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background")
  40. flags.StringVarP(&opts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
  41. flags.BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the command")
  42. return cmd
  43. }
  44. func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, execCmd []string) error {
  45. execConfig, err := parseExec(opts, container, execCmd)
  46. // just in case the ParseExec does not exit
  47. if container == "" || err != nil {
  48. return cli.StatusError{StatusCode: 1}
  49. }
  50. if opts.detachKeys != "" {
  51. dockerCli.ConfigFile().DetachKeys = opts.detachKeys
  52. }
  53. // Send client escape keys
  54. execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys
  55. ctx := context.Background()
  56. response, err := dockerCli.Client().ContainerExecCreate(ctx, container, *execConfig)
  57. if err != nil {
  58. return err
  59. }
  60. execID := response.ID
  61. if execID == "" {
  62. fmt.Fprintf(dockerCli.Out(), "exec ID empty")
  63. return nil
  64. }
  65. //Temp struct for execStart so that we don't need to transfer all the execConfig
  66. if !execConfig.Detach {
  67. if err := dockerCli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
  68. return err
  69. }
  70. } else {
  71. execStartCheck := types.ExecStartCheck{
  72. Detach: execConfig.Detach,
  73. Tty: execConfig.Tty,
  74. }
  75. if err := dockerCli.Client().ContainerExecStart(ctx, execID, execStartCheck); err != nil {
  76. return err
  77. }
  78. // For now don't print this - wait for when we support exec wait()
  79. // fmt.Fprintf(dockerCli.Out(), "%s\n", execID)
  80. return nil
  81. }
  82. // Interactive exec requested.
  83. var (
  84. out, stderr io.Writer
  85. in io.ReadCloser
  86. errCh chan error
  87. )
  88. if execConfig.AttachStdin {
  89. in = dockerCli.In()
  90. }
  91. if execConfig.AttachStdout {
  92. out = dockerCli.Out()
  93. }
  94. if execConfig.AttachStderr {
  95. if execConfig.Tty {
  96. stderr = dockerCli.Out()
  97. } else {
  98. stderr = dockerCli.Err()
  99. }
  100. }
  101. resp, err := dockerCli.Client().ContainerExecAttach(ctx, execID, *execConfig)
  102. if err != nil {
  103. return err
  104. }
  105. defer resp.Close()
  106. errCh = promise.Go(func() error {
  107. return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
  108. })
  109. if execConfig.Tty && dockerCli.IsTerminalIn() {
  110. if err := dockerCli.MonitorTtySize(ctx, execID, true); err != nil {
  111. fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
  112. }
  113. }
  114. if err := <-errCh; err != nil {
  115. logrus.Debugf("Error hijack: %s", err)
  116. return err
  117. }
  118. var status int
  119. if _, status, err = dockerCli.GetExecExitCode(ctx, execID); err != nil {
  120. return err
  121. }
  122. if status != 0 {
  123. return cli.StatusError{StatusCode: status}
  124. }
  125. return nil
  126. }
  127. // parseExec parses the specified args for the specified command and generates
  128. // an ExecConfig from it.
  129. func parseExec(opts *execOptions, container string, execCmd []string) (*types.ExecConfig, error) {
  130. execConfig := &types.ExecConfig{
  131. User: opts.user,
  132. Privileged: opts.privileged,
  133. Tty: opts.tty,
  134. Cmd: execCmd,
  135. Detach: opts.detach,
  136. // container is not used here
  137. }
  138. // If -d is not set, attach to everything by default
  139. if !opts.detach {
  140. execConfig.AttachStdout = true
  141. execConfig.AttachStderr = true
  142. if opts.interactive {
  143. execConfig.AttachStdin = true
  144. }
  145. }
  146. return execConfig, nil
  147. }