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