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