diff --git a/api/client/commands.go b/api/client/commands.go index 71229cc24d..2f4d875d5d 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -36,7 +36,6 @@ func (cli *DockerCli) Command(name string) func(...string) error { "restart": cli.CmdRestart, "rm": cli.CmdRm, "save": cli.CmdSave, - "start": cli.CmdStart, "stats": cli.CmdStats, "tag": cli.CmdTag, "top": cli.CmdTop, diff --git a/api/client/container/start.go b/api/client/container/start.go new file mode 100644 index 0000000000..711184ea91 --- /dev/null +++ b/api/client/container/start.go @@ -0,0 +1,152 @@ +package container + +import ( + "fmt" + "io" + "net/http/httputil" + "strings" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/client" + "github.com/docker/docker/cli" + "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/pkg/signal" + "github.com/docker/engine-api/types" + "github.com/spf13/cobra" +) + +type startOptions struct { + attach bool + openStdin bool + detachKeys string + + containers []string +} + +// NewStartCommand creats a new cobra.Command for `docker start` +func NewStartCommand(dockerCli *client.DockerCli) *cobra.Command { + var opts startOptions + + cmd := &cobra.Command{ + Use: "start [OPTIONS] CONTAINER [CONTAINER...]", + Short: "Start one or more stopped containers", + Args: cli.RequiresMinArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + opts.containers = args + return runStart(dockerCli, &opts) + }, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + + flags := cmd.Flags() + flags.BoolVarP(&opts.attach, "attach", "a", false, "Attach STDOUT/STDERR and forward signals") + flags.BoolVarP(&opts.openStdin, "interactive", "i", false, "Attach container's STDIN") + flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") + return cmd +} + +func runStart(dockerCli *client.DockerCli, opts *startOptions) error { + ctx, cancelFun := context.WithCancel(context.Background()) + + if opts.attach || opts.openStdin { + // We're going to attach to a container. + // 1. Ensure we only have one container. + if len(opts.containers) > 1 { + return fmt.Errorf("You cannot start and attach multiple containers at once.") + } + + // 2. Attach to the container. + container := opts.containers[0] + c, err := dockerCli.Client().ContainerInspect(ctx, container) + if err != nil { + return err + } + + if !c.Config.Tty { + sigc := dockerCli.ForwardAllSignals(ctx, container) + defer signal.StopCatch(sigc) + } + + if opts.detachKeys != "" { + dockerCli.ConfigFile().DetachKeys = opts.detachKeys + } + + options := types.ContainerAttachOptions{ + Stream: true, + Stdin: opts.openStdin && c.Config.OpenStdin, + Stdout: true, + Stderr: true, + DetachKeys: dockerCli.ConfigFile().DetachKeys, + } + + var in io.ReadCloser + + if options.Stdin { + in = dockerCli.In() + } + + resp, errAttach := dockerCli.Client().ContainerAttach(ctx, container, options) + if errAttach != nil && errAttach != httputil.ErrPersistEOF { + // ContainerAttach return an ErrPersistEOF (connection closed) + // means server met an error and put it in Hijacked connection + // keep the error and read detailed error message from hijacked connection + return errAttach + } + defer resp.Close() + cErr := promise.Go(func() error { + errHijack := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp) + if errHijack == nil { + return errAttach + } + return errHijack + }) + + // 3. Start the container. + if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil { + cancelFun() + <-cErr + return err + } + + // 4. Wait for attachment to break. + if c.Config.Tty && dockerCli.IsTerminalOut() { + if err := dockerCli.MonitorTtySize(ctx, container, false); err != nil { + fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) + } + } + if attchErr := <-cErr; attchErr != nil { + return attchErr + } + _, status, err := dockerCli.GetExitCode(ctx, container) + if err != nil { + return err + } + if status != 0 { + return cli.StatusError{StatusCode: status} + } + } else { + // We're not going to attach to anything. + // Start as many containers as we want. + return startContainersWithoutAttachments(dockerCli, ctx, opts.containers) + } + + return nil +} + +func startContainersWithoutAttachments(dockerCli *client.DockerCli, ctx context.Context, containers []string) error { + var failedContainers []string + for _, container := range containers { + if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil { + fmt.Fprintf(dockerCli.Err(), "%s\n", err) + failedContainers = append(failedContainers, container) + } else { + fmt.Fprintf(dockerCli.Out(), "%s\n", container) + } + } + + if len(failedContainers) > 0 { + return fmt.Errorf("Error: failed to start containers: %v", strings.Join(failedContainers, ", ")) + } + return nil +} diff --git a/api/client/start.go b/api/client/start.go deleted file mode 100644 index 3b4dc08e5f..0000000000 --- a/api/client/start.go +++ /dev/null @@ -1,165 +0,0 @@ -package client - -import ( - "fmt" - "io" - "net/http/httputil" - "os" - "strings" - - "golang.org/x/net/context" - - "github.com/Sirupsen/logrus" - Cli "github.com/docker/docker/cli" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/pkg/promise" - "github.com/docker/docker/pkg/signal" - "github.com/docker/engine-api/types" -) - -// ForwardAllSignals forwards signals to the contianer -// TODO: this can be unexported again once all container commands are under -// api/client/container -func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal { - sigc := make(chan os.Signal, 128) - signal.CatchAll(sigc) - go func() { - for s := range sigc { - if s == signal.SIGCHLD || s == signal.SIGPIPE { - continue - } - var sig string - for sigStr, sigN := range signal.SignalMap { - if sigN == s { - sig = sigStr - break - } - } - if sig == "" { - fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s) - continue - } - - if err := cli.client.ContainerKill(ctx, cid, sig); err != nil { - logrus.Debugf("Error sending signal: %s", err) - } - } - }() - return sigc -} - -// CmdStart starts one or more containers. -// -// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...] -func (cli *DockerCli) CmdStart(args ...string) error { - cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true) - attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals") - openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") - detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container") - cmd.Require(flag.Min, 1) - - cmd.ParseFlags(args, true) - - ctx, cancelFun := context.WithCancel(context.Background()) - - if *attach || *openStdin { - // We're going to attach to a container. - // 1. Ensure we only have one container. - if cmd.NArg() > 1 { - return fmt.Errorf("You cannot start and attach multiple containers at once.") - } - - // 2. Attach to the container. - container := cmd.Arg(0) - c, err := cli.client.ContainerInspect(ctx, container) - if err != nil { - return err - } - - if !c.Config.Tty { - sigc := cli.ForwardAllSignals(ctx, container) - defer signal.StopCatch(sigc) - } - - if *detachKeys != "" { - cli.configFile.DetachKeys = *detachKeys - } - - options := types.ContainerAttachOptions{ - Stream: true, - Stdin: *openStdin && c.Config.OpenStdin, - Stdout: true, - Stderr: true, - DetachKeys: cli.configFile.DetachKeys, - } - - var in io.ReadCloser - - if options.Stdin { - in = cli.in - } - - resp, errAttach := cli.client.ContainerAttach(ctx, container, options) - if errAttach != nil && errAttach != httputil.ErrPersistEOF { - // ContainerAttach return an ErrPersistEOF (connection closed) - // means server met an error and put it in Hijacked connection - // keep the error and read detailed error message from hijacked connection - return errAttach - } - defer resp.Close() - cErr := promise.Go(func() error { - errHijack := cli.HoldHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp) - if errHijack == nil { - return errAttach - } - return errHijack - }) - - // 3. Start the container. - if err := cli.client.ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil { - cancelFun() - <-cErr - return err - } - - // 4. Wait for attachment to break. - if c.Config.Tty && cli.isTerminalOut { - if err := cli.MonitorTtySize(ctx, container, false); err != nil { - fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) - } - } - if attchErr := <-cErr; attchErr != nil { - return attchErr - } - _, status, err := cli.GetExitCode(ctx, container) - if err != nil { - return err - } - if status != 0 { - return Cli.StatusError{StatusCode: status} - } - } else { - // We're not going to attach to anything. - // Start as many containers as we want. - return cli.startContainersWithoutAttachments(ctx, cmd.Args()) - } - - return nil -} - -func (cli *DockerCli) startContainersWithoutAttachments(ctx context.Context, containers []string) error { - var failedContainers []string - for _, container := range containers { - if err := cli.client.ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - failedContainers = append(failedContainers, container) - } else { - fmt.Fprintf(cli.out, "%s\n", container) - } - } - - if len(failedContainers) > 0 { - return fmt.Errorf("Error: failed to start containers: %v", strings.Join(failedContainers, ", ")) - } - return nil -} diff --git a/api/client/utils.go b/api/client/utils.go index deaf846b1c..c090dbeed8 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -204,3 +204,34 @@ func (cli *DockerCli) retrieveAuthConfigs() map[string]types.AuthConfig { acs, _ := getAllCredentials(cli.configFile) return acs } + +// ForwardAllSignals forwards signals to the contianer +// TODO: this can be unexported again once all container commands are under +// api/client/container +func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal { + sigc := make(chan os.Signal, 128) + signal.CatchAll(sigc) + go func() { + for s := range sigc { + if s == signal.SIGCHLD || s == signal.SIGPIPE { + continue + } + var sig string + for sigStr, sigN := range signal.SignalMap { + if sigN == s { + sig = sigStr + break + } + } + if sig == "" { + fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s) + continue + } + + if err := cli.client.ContainerKill(ctx, cid, sig); err != nil { + logrus.Debugf("Error sending signal: %s", err) + } + } + }() + return sigc +} diff --git a/cli/cobraadaptor/adaptor.go b/cli/cobraadaptor/adaptor.go index 139b9b1ec4..bc497dfe9d 100644 --- a/cli/cobraadaptor/adaptor.go +++ b/cli/cobraadaptor/adaptor.go @@ -36,6 +36,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor { container.NewCreateCommand(dockerCli), container.NewExportCommand(dockerCli), container.NewRunCommand(dockerCli), + container.NewStartCommand(dockerCli), container.NewStopCommand(dockerCli), image.NewRemoveCommand(dockerCli), image.NewSearchCommand(dockerCli), diff --git a/cli/usage.go b/cli/usage.go index 1bcce75a3d..f62e462b12 100644 --- a/cli/usage.go +++ b/cli/usage.go @@ -35,7 +35,6 @@ var DockerCommandUsage = []Command{ {"restart", "Restart a container"}, {"rm", "Remove one or more containers"}, {"save", "Save one or more images to a tar archive"}, - {"start", "Start one or more stopped containers"}, {"stats", "Display a live stream of container(s) resource usage statistics"}, {"tag", "Tag an image into a repository"}, {"top", "Display the running processes of a container"},