run.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. package client
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "runtime"
  7. "strings"
  8. "github.com/Sirupsen/logrus"
  9. Cli "github.com/docker/docker/cli"
  10. derr "github.com/docker/docker/errors"
  11. "github.com/docker/docker/opts"
  12. "github.com/docker/docker/pkg/promise"
  13. "github.com/docker/docker/pkg/signal"
  14. "github.com/docker/docker/pkg/stringid"
  15. runconfigopts "github.com/docker/docker/runconfig/opts"
  16. "github.com/docker/engine-api/types"
  17. "github.com/docker/libnetwork/resolvconf/dns"
  18. )
  19. func (cid *cidFile) Close() error {
  20. cid.file.Close()
  21. if !cid.written {
  22. if err := os.Remove(cid.path); err != nil {
  23. return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
  24. }
  25. }
  26. return nil
  27. }
  28. func (cid *cidFile) Write(id string) error {
  29. if _, err := cid.file.Write([]byte(id)); err != nil {
  30. return fmt.Errorf("Failed to write the container ID to the file: %s", err)
  31. }
  32. cid.written = true
  33. return nil
  34. }
  35. // if container start fails with 'command not found' error, return 127
  36. // if container start fails with 'command cannot be invoked' error, return 126
  37. // return 125 for generic docker daemon failures
  38. func runStartContainerErr(err error) error {
  39. trimmedErr := strings.Trim(err.Error(), "Error response from daemon: ")
  40. statusError := Cli.StatusError{}
  41. derrCmdNotFound := derr.ErrorCodeCmdNotFound.Message()
  42. derrCouldNotInvoke := derr.ErrorCodeCmdCouldNotBeInvoked.Message()
  43. derrNoSuchImage := derr.ErrorCodeNoSuchImageHash.Message()
  44. derrNoSuchImageTag := derr.ErrorCodeNoSuchImageTag.Message()
  45. switch trimmedErr {
  46. case derrCmdNotFound:
  47. statusError = Cli.StatusError{StatusCode: 127}
  48. case derrCouldNotInvoke:
  49. statusError = Cli.StatusError{StatusCode: 126}
  50. case derrNoSuchImage, derrNoSuchImageTag:
  51. statusError = Cli.StatusError{StatusCode: 125}
  52. default:
  53. statusError = Cli.StatusError{StatusCode: 125}
  54. }
  55. return statusError
  56. }
  57. // CmdRun runs a command in a new container.
  58. //
  59. // Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
  60. func (cli *DockerCli) CmdRun(args ...string) error {
  61. cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
  62. addTrustedFlags(cmd, true)
  63. // These are flags not stored in Config/HostConfig
  64. var (
  65. flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
  66. flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
  67. flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
  68. flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
  69. flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
  70. flAttach *opts.ListOpts
  71. ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
  72. ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
  73. ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
  74. )
  75. config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
  76. // just in case the Parse does not exit
  77. if err != nil {
  78. cmd.ReportError(err.Error(), true)
  79. os.Exit(125)
  80. }
  81. if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
  82. fmt.Fprintf(cli.err, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n")
  83. }
  84. if len(hostConfig.DNS) > 0 {
  85. // check the DNS settings passed via --dns against
  86. // localhost regexp to warn if they are trying to
  87. // set a DNS to a localhost address
  88. for _, dnsIP := range hostConfig.DNS {
  89. if dns.IsLocalhost(dnsIP) {
  90. fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
  91. break
  92. }
  93. }
  94. }
  95. if config.Image == "" {
  96. cmd.Usage()
  97. return nil
  98. }
  99. config.ArgsEscaped = false
  100. if !*flDetach {
  101. if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
  102. return err
  103. }
  104. } else {
  105. if fl := cmd.Lookup("-attach"); fl != nil {
  106. flAttach = fl.Value.(*opts.ListOpts)
  107. if flAttach.Len() != 0 {
  108. return ErrConflictAttachDetach
  109. }
  110. }
  111. if *flAutoRemove {
  112. return ErrConflictDetachAutoRemove
  113. }
  114. config.AttachStdin = false
  115. config.AttachStdout = false
  116. config.AttachStderr = false
  117. config.StdinOnce = false
  118. }
  119. // Disable flSigProxy when in TTY mode
  120. sigProxy := *flSigProxy
  121. if config.Tty {
  122. sigProxy = false
  123. }
  124. // Telling the Windows daemon the initial size of the tty during start makes
  125. // a far better user experience rather than relying on subsequent resizes
  126. // to cause things to catch up.
  127. if runtime.GOOS == "windows" {
  128. hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
  129. }
  130. createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
  131. if err != nil {
  132. cmd.ReportError(err.Error(), true)
  133. return runStartContainerErr(err)
  134. }
  135. if sigProxy {
  136. sigc := cli.forwardAllSignals(createResponse.ID)
  137. defer signal.StopCatch(sigc)
  138. }
  139. var (
  140. waitDisplayID chan struct{}
  141. errCh chan error
  142. )
  143. if !config.AttachStdout && !config.AttachStderr {
  144. // Make this asynchronous to allow the client to write to stdin before having to read the ID
  145. waitDisplayID = make(chan struct{})
  146. go func() {
  147. defer close(waitDisplayID)
  148. fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
  149. }()
  150. }
  151. if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
  152. return ErrConflictRestartPolicyAndAutoRemove
  153. }
  154. if config.AttachStdin || config.AttachStdout || config.AttachStderr {
  155. var (
  156. out, stderr io.Writer
  157. in io.ReadCloser
  158. )
  159. if config.AttachStdin {
  160. in = cli.in
  161. }
  162. if config.AttachStdout {
  163. out = cli.out
  164. }
  165. if config.AttachStderr {
  166. if config.Tty {
  167. stderr = cli.out
  168. } else {
  169. stderr = cli.err
  170. }
  171. }
  172. if *flDetachKeys != "" {
  173. cli.configFile.DetachKeys = *flDetachKeys
  174. }
  175. options := types.ContainerAttachOptions{
  176. ContainerID: createResponse.ID,
  177. Stream: true,
  178. Stdin: config.AttachStdin,
  179. Stdout: config.AttachStdout,
  180. Stderr: config.AttachStderr,
  181. DetachKeys: cli.configFile.DetachKeys,
  182. }
  183. resp, err := cli.client.ContainerAttach(options)
  184. if err != nil {
  185. return err
  186. }
  187. if in != nil && config.Tty {
  188. if err := cli.setRawTerminal(); err != nil {
  189. return err
  190. }
  191. defer cli.restoreTerminal(in)
  192. }
  193. errCh = promise.Go(func() error {
  194. return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp)
  195. })
  196. }
  197. defer func() {
  198. if *flAutoRemove {
  199. options := types.ContainerRemoveOptions{
  200. ContainerID: createResponse.ID,
  201. RemoveVolumes: true,
  202. }
  203. if err := cli.client.ContainerRemove(options); err != nil {
  204. fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
  205. }
  206. }
  207. }()
  208. //start the container
  209. if err := cli.client.ContainerStart(createResponse.ID); err != nil {
  210. cmd.ReportError(err.Error(), false)
  211. return runStartContainerErr(err)
  212. }
  213. if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
  214. if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
  215. fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
  216. }
  217. }
  218. if errCh != nil {
  219. if err := <-errCh; err != nil {
  220. logrus.Debugf("Error hijack: %s", err)
  221. return err
  222. }
  223. }
  224. // Detached mode: wait for the id to be displayed and return.
  225. if !config.AttachStdout && !config.AttachStderr {
  226. // Detached mode
  227. <-waitDisplayID
  228. return nil
  229. }
  230. var status int
  231. // Attached mode
  232. if *flAutoRemove {
  233. // Warn user if they detached us
  234. js, err := cli.client.ContainerInspect(createResponse.ID)
  235. if err != nil {
  236. return runStartContainerErr(err)
  237. }
  238. if js.State.Running == true || js.State.Paused == true {
  239. fmt.Fprintf(cli.out, "Detached from %s, awaiting its termination in order to uphold \"--rm\".\n",
  240. stringid.TruncateID(createResponse.ID))
  241. }
  242. // Autoremove: wait for the container to finish, retrieve
  243. // the exit code and remove the container
  244. if status, err = cli.client.ContainerWait(createResponse.ID); err != nil {
  245. return runStartContainerErr(err)
  246. }
  247. if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
  248. return err
  249. }
  250. } else {
  251. // No Autoremove: Simply retrieve the exit code
  252. if !config.Tty {
  253. // In non-TTY mode, we can't detach, so we must wait for container exit
  254. if status, err = cli.client.ContainerWait(createResponse.ID); err != nil {
  255. return err
  256. }
  257. } else {
  258. // In TTY mode, there is a race: if the process dies too slowly, the state could
  259. // be updated after the getExitCode call and result in the wrong exit code being reported
  260. if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
  261. return err
  262. }
  263. }
  264. }
  265. if status != 0 {
  266. return Cli.StatusError{StatusCode: status}
  267. }
  268. return nil
  269. }