github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+incompatible/cli/command/container/start.go (about)

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