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