github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/container/start.go (about)

     1  package container
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http/httputil"
     8  	"strings"
     9  
    10  	"github.com/docker/cli/cli"
    11  	"github.com/docker/cli/cli/command"
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/pkg/signal"
    14  	"github.com/docker/docker/pkg/term"
    15  	"github.com/pkg/errors"
    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.Cli) *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.SetAnnotation("checkpoint", "ostype", []string{"linux"})
    51  	flags.StringVar(&opts.checkpointDir, "checkpoint-dir", "", "Use a custom checkpoint storage directory")
    52  	flags.SetAnnotation("checkpoint-dir", "experimental", nil)
    53  	flags.SetAnnotation("checkpoint-dir", "ostype", []string{"linux"})
    54  	return cmd
    55  }
    56  
    57  // nolint: gocyclo
    58  func runStart(dockerCli command.Cli, opts *startOptions) error {
    59  	ctx, cancelFun := context.WithCancel(context.Background())
    60  	defer cancelFun()
    61  
    62  	if opts.attach || opts.openStdin {
    63  		// We're going to attach to a container.
    64  		// 1. Ensure we only have one container.
    65  		if len(opts.containers) > 1 {
    66  			return errors.New("you cannot start and attach multiple containers at once")
    67  		}
    68  
    69  		// 2. Attach to the container.
    70  		container := opts.containers[0]
    71  		c, err := dockerCli.Client().ContainerInspect(ctx, container)
    72  		if err != nil {
    73  			return err
    74  		}
    75  
    76  		// We always use c.ID instead of container to maintain consistency during `docker start`
    77  		if !c.Config.Tty {
    78  			sigc := ForwardAllSignals(ctx, dockerCli, c.ID)
    79  			defer signal.StopCatch(sigc)
    80  		}
    81  
    82  		if opts.detachKeys != "" {
    83  			dockerCli.ConfigFile().DetachKeys = opts.detachKeys
    84  		}
    85  
    86  		options := types.ContainerAttachOptions{
    87  			Stream:     true,
    88  			Stdin:      opts.openStdin && c.Config.OpenStdin,
    89  			Stdout:     true,
    90  			Stderr:     true,
    91  			DetachKeys: dockerCli.ConfigFile().DetachKeys,
    92  		}
    93  
    94  		var in io.ReadCloser
    95  
    96  		if options.Stdin {
    97  			in = dockerCli.In()
    98  		}
    99  
   100  		resp, errAttach := dockerCli.Client().ContainerAttach(ctx, c.ID, options)
   101  		if errAttach != nil && errAttach != httputil.ErrPersistEOF {
   102  			// ContainerAttach return an ErrPersistEOF (connection closed)
   103  			// means server met an error and already put it in Hijacked connection,
   104  			// we would keep the error and read the detailed error message from hijacked connection
   105  			return errAttach
   106  		}
   107  		defer resp.Close()
   108  
   109  		cErr := make(chan error, 1)
   110  
   111  		go func() {
   112  			cErr <- func() error {
   113  				streamer := hijackedIOStreamer{
   114  					streams:      dockerCli,
   115  					inputStream:  in,
   116  					outputStream: dockerCli.Out(),
   117  					errorStream:  dockerCli.Err(),
   118  					resp:         resp,
   119  					tty:          c.Config.Tty,
   120  					detachKeys:   options.DetachKeys,
   121  				}
   122  
   123  				errHijack := streamer.stream(ctx)
   124  				if errHijack == nil {
   125  					return errAttach
   126  				}
   127  				return errHijack
   128  			}()
   129  		}()
   130  
   131  		// 3. We should open a channel for receiving status code of the container
   132  		// no matter it's detached, removed on daemon side(--rm) or exit normally.
   133  		statusChan := waitExitOrRemoved(ctx, dockerCli, c.ID, c.HostConfig.AutoRemove)
   134  		startOptions := types.ContainerStartOptions{
   135  			CheckpointID:  opts.checkpoint,
   136  			CheckpointDir: opts.checkpointDir,
   137  		}
   138  
   139  		// 4. Start the container.
   140  		if err := dockerCli.Client().ContainerStart(ctx, c.ID, startOptions); err != nil {
   141  			cancelFun()
   142  			<-cErr
   143  			if c.HostConfig.AutoRemove {
   144  				// wait container to be removed
   145  				<-statusChan
   146  			}
   147  			return err
   148  		}
   149  
   150  		// 5. Wait for attachment to break.
   151  		if c.Config.Tty && dockerCli.Out().IsTerminal() {
   152  			if err := MonitorTtySize(ctx, dockerCli, c.ID, false); err != nil {
   153  				fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
   154  			}
   155  		}
   156  		if attachErr := <-cErr; attachErr != nil {
   157  			if _, ok := err.(term.EscapeError); ok {
   158  				// The user entered the detach escape sequence.
   159  				return nil
   160  			}
   161  			return attachErr
   162  		}
   163  
   164  		if status := <-statusChan; status != 0 {
   165  			return cli.StatusError{StatusCode: status}
   166  		}
   167  	} else if opts.checkpoint != "" {
   168  		if len(opts.containers) > 1 {
   169  			return errors.New("you cannot restore multiple containers at once")
   170  		}
   171  		container := opts.containers[0]
   172  		startOptions := types.ContainerStartOptions{
   173  			CheckpointID:  opts.checkpoint,
   174  			CheckpointDir: opts.checkpointDir,
   175  		}
   176  		return dockerCli.Client().ContainerStart(ctx, container, startOptions)
   177  
   178  	} else {
   179  		// We're not going to attach to anything.
   180  		// Start as many containers as we want.
   181  		return startContainersWithoutAttachments(ctx, dockerCli, opts.containers)
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  func startContainersWithoutAttachments(ctx context.Context, dockerCli command.Cli, containers []string) error {
   188  	var failedContainers []string
   189  	for _, container := range containers {
   190  		if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
   191  			fmt.Fprintln(dockerCli.Err(), err)
   192  			failedContainers = append(failedContainers, container)
   193  			continue
   194  		}
   195  		fmt.Fprintln(dockerCli.Out(), container)
   196  	}
   197  
   198  	if len(failedContainers) > 0 {
   199  		return errors.Errorf("Error: failed to start containers: %s", strings.Join(failedContainers, ", "))
   200  	}
   201  	return nil
   202  }