github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/cli/command/container/start.go (about)

     1  package container
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http/httputil"
     7  	"strings"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/cli"
    13  	"github.com/docker/docker/cli/command"
    14  	"github.com/docker/docker/pkg/promise"
    15  	"github.com/docker/docker/pkg/signal"
    16  	"github.com/spf13/cobra"
    17  )
    18  
    19  type startOptions struct {
    20  	attach     bool
    21  	openStdin  bool
    22  	detachKeys string
    23  	checkpoint string
    24  
    25  	containers []string
    26  }
    27  
    28  // NewStartCommand creates a new cobra.Command for `docker start`
    29  func NewStartCommand(dockerCli *command.DockerCli) *cobra.Command {
    30  	var opts startOptions
    31  
    32  	cmd := &cobra.Command{
    33  		Use:   "start [OPTIONS] CONTAINER [CONTAINER...]",
    34  		Short: "Start one or more stopped containers",
    35  		Args:  cli.RequiresMinArgs(1),
    36  		RunE: func(cmd *cobra.Command, args []string) error {
    37  			opts.containers = args
    38  			return runStart(dockerCli, &opts)
    39  		},
    40  	}
    41  
    42  	flags := cmd.Flags()
    43  	flags.BoolVarP(&opts.attach, "attach", "a", false, "Attach STDOUT/STDERR and forward signals")
    44  	flags.BoolVarP(&opts.openStdin, "interactive", "i", false, "Attach container's STDIN")
    45  	flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
    46  
    47  	if dockerCli.HasExperimental() {
    48  		flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint")
    49  	}
    50  
    51  	return cmd
    52  }
    53  
    54  func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
    55  	ctx, cancelFun := context.WithCancel(context.Background())
    56  
    57  	if opts.attach || opts.openStdin {
    58  		// We're going to attach to a container.
    59  		// 1. Ensure we only have one container.
    60  		if len(opts.containers) > 1 {
    61  			return fmt.Errorf("You cannot start and attach multiple containers at once.")
    62  		}
    63  
    64  		// 2. Attach to the container.
    65  		container := opts.containers[0]
    66  		c, err := dockerCli.Client().ContainerInspect(ctx, container)
    67  		if err != nil {
    68  			return err
    69  		}
    70  
    71  		// We always use c.ID instead of container to maintain consistency during `docker start`
    72  		if !c.Config.Tty {
    73  			sigc := ForwardAllSignals(ctx, dockerCli, c.ID)
    74  			defer signal.StopCatch(sigc)
    75  		}
    76  
    77  		if opts.detachKeys != "" {
    78  			dockerCli.ConfigFile().DetachKeys = opts.detachKeys
    79  		}
    80  
    81  		options := types.ContainerAttachOptions{
    82  			Stream:     true,
    83  			Stdin:      opts.openStdin && c.Config.OpenStdin,
    84  			Stdout:     true,
    85  			Stderr:     true,
    86  			DetachKeys: dockerCli.ConfigFile().DetachKeys,
    87  		}
    88  
    89  		var in io.ReadCloser
    90  
    91  		if options.Stdin {
    92  			in = dockerCli.In()
    93  		}
    94  
    95  		resp, errAttach := dockerCli.Client().ContainerAttach(ctx, c.ID, options)
    96  		if errAttach != nil && errAttach != httputil.ErrPersistEOF {
    97  			// ContainerAttach return an ErrPersistEOF (connection closed)
    98  			// means server met an error and already put it in Hijacked connection,
    99  			// we would keep the error and read the detailed error message from hijacked connection
   100  			return errAttach
   101  		}
   102  		defer resp.Close()
   103  		cErr := promise.Go(func() error {
   104  			errHijack := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp)
   105  			if errHijack == nil {
   106  				return errAttach
   107  			}
   108  			return errHijack
   109  		})
   110  
   111  		// 3. We should open a channel for receiving status code of the container
   112  		// no matter it's detached, removed on daemon side(--rm) or exit normally.
   113  		statusChan := waitExitOrRemoved(dockerCli, ctx, c.ID, c.HostConfig.AutoRemove)
   114  		startOptions := types.ContainerStartOptions{
   115  			CheckpointID: opts.checkpoint,
   116  		}
   117  
   118  		// 4. Start the container.
   119  		if err := dockerCli.Client().ContainerStart(ctx, c.ID, startOptions); err != nil {
   120  			cancelFun()
   121  			<-cErr
   122  			if c.HostConfig.AutoRemove {
   123  				// wait container to be removed
   124  				<-statusChan
   125  			}
   126  			return err
   127  		}
   128  
   129  		// 5. Wait for attachment to break.
   130  		if c.Config.Tty && dockerCli.Out().IsTerminal() {
   131  			if err := MonitorTtySize(ctx, dockerCli, c.ID, false); err != nil {
   132  				fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
   133  			}
   134  		}
   135  		if attchErr := <-cErr; attchErr != nil {
   136  			return attchErr
   137  		}
   138  
   139  		if status := <-statusChan; status != 0 {
   140  			return cli.StatusError{StatusCode: status}
   141  		}
   142  	} else if opts.checkpoint != "" {
   143  		if len(opts.containers) > 1 {
   144  			return fmt.Errorf("You cannot restore multiple containers at once.")
   145  		}
   146  		container := opts.containers[0]
   147  		startOptions := types.ContainerStartOptions{
   148  			CheckpointID: opts.checkpoint,
   149  		}
   150  		return dockerCli.Client().ContainerStart(ctx, container, startOptions)
   151  
   152  	} else {
   153  		// We're not going to attach to anything.
   154  		// Start as many containers as we want.
   155  		return startContainersWithoutAttachments(dockerCli, ctx, opts.containers)
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  func startContainersWithoutAttachments(dockerCli *command.DockerCli, ctx context.Context, containers []string) error {
   162  	var failedContainers []string
   163  	for _, container := range containers {
   164  		if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
   165  			fmt.Fprintf(dockerCli.Err(), "%s\n", err)
   166  			failedContainers = append(failedContainers, container)
   167  		} else {
   168  			fmt.Fprintf(dockerCli.Out(), "%s\n", container)
   169  		}
   170  	}
   171  
   172  	if len(failedContainers) > 0 {
   173  		return fmt.Errorf("Error: failed to start containers: %v", strings.Join(failedContainers, ", "))
   174  	}
   175  	return nil
   176  }