start.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package client
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http/httputil"
  6. "os"
  7. "strings"
  8. "golang.org/x/net/context"
  9. "github.com/Sirupsen/logrus"
  10. Cli "github.com/docker/docker/cli"
  11. flag "github.com/docker/docker/pkg/mflag"
  12. "github.com/docker/docker/pkg/promise"
  13. "github.com/docker/docker/pkg/signal"
  14. "github.com/docker/engine-api/types"
  15. )
  16. // ForwardAllSignals forwards signals to the contianer
  17. // TODO: this can be unexported again once all container commands are under
  18. // api/client/container
  19. func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal {
  20. sigc := make(chan os.Signal, 128)
  21. signal.CatchAll(sigc)
  22. go func() {
  23. for s := range sigc {
  24. if s == signal.SIGCHLD || s == signal.SIGPIPE {
  25. continue
  26. }
  27. var sig string
  28. for sigStr, sigN := range signal.SignalMap {
  29. if sigN == s {
  30. sig = sigStr
  31. break
  32. }
  33. }
  34. if sig == "" {
  35. fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
  36. continue
  37. }
  38. if err := cli.client.ContainerKill(ctx, cid, sig); err != nil {
  39. logrus.Debugf("Error sending signal: %s", err)
  40. }
  41. }
  42. }()
  43. return sigc
  44. }
  45. // CmdStart starts one or more containers.
  46. //
  47. // Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
  48. func (cli *DockerCli) CmdStart(args ...string) error {
  49. cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
  50. attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
  51. openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
  52. detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
  53. cmd.Require(flag.Min, 1)
  54. cmd.ParseFlags(args, true)
  55. ctx, cancelFun := context.WithCancel(context.Background())
  56. if *attach || *openStdin {
  57. // We're going to attach to a container.
  58. // 1. Ensure we only have one container.
  59. if cmd.NArg() > 1 {
  60. return fmt.Errorf("You cannot start and attach multiple containers at once.")
  61. }
  62. // 2. Attach to the container.
  63. container := cmd.Arg(0)
  64. c, err := cli.client.ContainerInspect(ctx, container)
  65. if err != nil {
  66. return err
  67. }
  68. if !c.Config.Tty {
  69. sigc := cli.ForwardAllSignals(ctx, container)
  70. defer signal.StopCatch(sigc)
  71. }
  72. if *detachKeys != "" {
  73. cli.configFile.DetachKeys = *detachKeys
  74. }
  75. options := types.ContainerAttachOptions{
  76. Stream: true,
  77. Stdin: *openStdin && c.Config.OpenStdin,
  78. Stdout: true,
  79. Stderr: true,
  80. DetachKeys: cli.configFile.DetachKeys,
  81. }
  82. var in io.ReadCloser
  83. if options.Stdin {
  84. in = cli.in
  85. }
  86. resp, errAttach := cli.client.ContainerAttach(ctx, container, options)
  87. if errAttach != nil && errAttach != httputil.ErrPersistEOF {
  88. // ContainerAttach return an ErrPersistEOF (connection closed)
  89. // means server met an error and put it in Hijacked connection
  90. // keep the error and read detailed error message from hijacked connection
  91. return errAttach
  92. }
  93. defer resp.Close()
  94. cErr := promise.Go(func() error {
  95. errHijack := cli.HoldHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp)
  96. if errHijack == nil {
  97. return errAttach
  98. }
  99. return errHijack
  100. })
  101. // 3. Start the container.
  102. if err := cli.client.ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
  103. cancelFun()
  104. <-cErr
  105. return err
  106. }
  107. // 4. Wait for attachment to break.
  108. if c.Config.Tty && cli.isTerminalOut {
  109. if err := cli.MonitorTtySize(ctx, container, false); err != nil {
  110. fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
  111. }
  112. }
  113. if attchErr := <-cErr; attchErr != nil {
  114. return attchErr
  115. }
  116. _, status, err := cli.GetExitCode(ctx, container)
  117. if err != nil {
  118. return err
  119. }
  120. if status != 0 {
  121. return Cli.StatusError{StatusCode: status}
  122. }
  123. } else {
  124. // We're not going to attach to anything.
  125. // Start as many containers as we want.
  126. return cli.startContainersWithoutAttachments(ctx, cmd.Args())
  127. }
  128. return nil
  129. }
  130. func (cli *DockerCli) startContainersWithoutAttachments(ctx context.Context, containers []string) error {
  131. var failedContainers []string
  132. for _, container := range containers {
  133. if err := cli.client.ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
  134. fmt.Fprintf(cli.err, "%s\n", err)
  135. failedContainers = append(failedContainers, container)
  136. } else {
  137. fmt.Fprintf(cli.out, "%s\n", container)
  138. }
  139. }
  140. if len(failedContainers) > 0 {
  141. return fmt.Errorf("Error: failed to start containers: %v", strings.Join(failedContainers, ", "))
  142. }
  143. return nil
  144. }