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 }