github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_run.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "errors" 21 "fmt" 22 "runtime" 23 "strings" 24 25 "github.com/containerd/console" 26 "github.com/containerd/log" 27 "github.com/containerd/nerdctl/pkg/api/types" 28 "github.com/containerd/nerdctl/pkg/clientutil" 29 "github.com/containerd/nerdctl/pkg/cmd/container" 30 "github.com/containerd/nerdctl/pkg/consoleutil" 31 "github.com/containerd/nerdctl/pkg/containerutil" 32 "github.com/containerd/nerdctl/pkg/defaults" 33 "github.com/containerd/nerdctl/pkg/errutil" 34 "github.com/containerd/nerdctl/pkg/labels" 35 "github.com/containerd/nerdctl/pkg/logging" 36 "github.com/containerd/nerdctl/pkg/netutil" 37 "github.com/containerd/nerdctl/pkg/signalutil" 38 "github.com/containerd/nerdctl/pkg/taskutil" 39 "github.com/spf13/cobra" 40 ) 41 42 const ( 43 tiniInitBinary = "tini" 44 ) 45 46 func newRunCommand() *cobra.Command { 47 shortHelp := "Run a command in a new container. Optionally specify \"ipfs://\" or \"ipns://\" scheme to pull image from IPFS." 48 longHelp := shortHelp 49 switch runtime.GOOS { 50 case "windows": 51 longHelp += "\n" 52 longHelp += "WARNING: `nerdctl run` is experimental on Windows and currently broken (https://github.com/containerd/nerdctl/issues/28)" 53 case "freebsd": 54 longHelp += "\n" 55 longHelp += "WARNING: `nerdctl run` is experimental on FreeBSD and currently requires `--net=none` (https://github.com/containerd/nerdctl/blob/main/docs/freebsd.md)" 56 } 57 var runCommand = &cobra.Command{ 58 Use: "run [flags] IMAGE [COMMAND] [ARG...]", 59 Args: cobra.MinimumNArgs(1), 60 Short: shortHelp, 61 Long: longHelp, 62 RunE: runAction, 63 ValidArgsFunction: runShellComplete, 64 SilenceUsage: true, 65 SilenceErrors: true, 66 } 67 68 runCommand.Flags().SetInterspersed(false) 69 setCreateFlags(runCommand) 70 71 runCommand.Flags().BoolP("detach", "d", false, "Run container in background and print container ID") 72 runCommand.Flags().StringSliceP("attach", "a", []string{}, "Attach STDIN, STDOUT, or STDERR") 73 74 return runCommand 75 } 76 77 func setCreateFlags(cmd *cobra.Command) { 78 79 // No "-h" alias for "--help", because "-h" for "--hostname". 80 cmd.Flags().Bool("help", false, "show help") 81 82 cmd.Flags().BoolP("tty", "t", false, "Allocate a pseudo-TTY") 83 cmd.Flags().Bool("sig-proxy", true, "Proxy received signals to the process (default true)") 84 cmd.Flags().BoolP("interactive", "i", false, "Keep STDIN open even if not attached") 85 cmd.Flags().String("restart", "no", `Restart policy to apply when a container exits (implemented values: "no"|"always|on-failure:n|unless-stopped")`) 86 cmd.RegisterFlagCompletionFunc("restart", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 87 return []string{"no", "always", "on-failure", "unless-stopped"}, cobra.ShellCompDirectiveNoFileComp 88 }) 89 cmd.Flags().Bool("rm", false, "Automatically remove the container when it exits") 90 cmd.Flags().String("pull", "missing", `Pull image before running ("always"|"missing"|"never")`) 91 cmd.RegisterFlagCompletionFunc("pull", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 92 return []string{"always", "missing", "never"}, cobra.ShellCompDirectiveNoFileComp 93 }) 94 cmd.Flags().String("stop-signal", "SIGTERM", "Signal to stop a container") 95 cmd.Flags().Int("stop-timeout", 0, "Timeout (in seconds) to stop a container") 96 cmd.Flags().String("detach-keys", consoleutil.DefaultDetachKeys, "Override the default detach keys") 97 98 // #region for init process 99 cmd.Flags().Bool("init", false, "Run an init process inside the container, Default to use tini") 100 cmd.Flags().String("init-binary", tiniInitBinary, "The custom binary to use as the init process") 101 // #endregion 102 103 // #region platform flags 104 cmd.Flags().String("platform", "", "Set platform (e.g. \"amd64\", \"arm64\")") // not a slice, and there is no --all-platforms 105 cmd.RegisterFlagCompletionFunc("platform", shellCompletePlatforms) 106 // #endregion 107 108 // #region network flags 109 // network (net) is defined as StringSlice, not StringArray, to allow specifying "--network=cni1,cni2" 110 cmd.Flags().StringSlice("network", []string{netutil.DefaultNetworkName}, `Connect a container to a network ("bridge"|"host"|"none"|"container:<container>"|<CNI>)`) 111 cmd.RegisterFlagCompletionFunc("network", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 112 return shellCompleteNetworkNames(cmd, []string{}) 113 }) 114 cmd.Flags().StringSlice("net", []string{netutil.DefaultNetworkName}, `Connect a container to a network ("bridge"|"host"|"none"|<CNI>)`) 115 cmd.RegisterFlagCompletionFunc("net", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 116 return shellCompleteNetworkNames(cmd, []string{}) 117 }) 118 // dns is defined as StringSlice, not StringArray, to allow specifying "--dns=1.1.1.1,8.8.8.8" (compatible with Podman) 119 cmd.Flags().StringSlice("dns", nil, "Set custom DNS servers") 120 cmd.Flags().StringSlice("dns-search", nil, "Set custom DNS search domains") 121 // We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way. 122 cmd.Flags().StringSlice("dns-opt", nil, "Set DNS options") 123 cmd.Flags().StringSlice("dns-option", nil, "Set DNS options") 124 // publish is defined as StringSlice, not StringArray, to allow specifying "--publish=80:80,443:443" (compatible with Podman) 125 cmd.Flags().StringSliceP("publish", "p", nil, "Publish a container's port(s) to the host") 126 cmd.Flags().String("ip", "", "IPv4 address to assign to the container") 127 cmd.Flags().String("ip6", "", "IPv6 address to assign to the container") 128 cmd.Flags().StringP("hostname", "h", "", "Container host name") 129 cmd.Flags().String("mac-address", "", "MAC address to assign to the container") 130 // #endregion 131 132 cmd.Flags().String("ipc", "", `IPC namespace to use ("host"|"private")`) 133 cmd.RegisterFlagCompletionFunc("ipc", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 134 return []string{"host", "private"}, cobra.ShellCompDirectiveNoFileComp 135 }) 136 // #region cgroups, namespaces, and ulimits flags 137 cmd.Flags().Float64("cpus", 0.0, "Number of CPUs") 138 cmd.Flags().StringP("memory", "m", "", "Memory limit") 139 cmd.Flags().String("memory-reservation", "", "Memory soft limit") 140 cmd.Flags().String("memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") 141 cmd.Flags().Int64("memory-swappiness", -1, "Tune container memory swappiness (0 to 100) (default -1)") 142 cmd.Flags().String("kernel-memory", "", "Kernel memory limit (deprecated)") 143 cmd.Flags().Bool("oom-kill-disable", false, "Disable OOM Killer") 144 cmd.Flags().Int("oom-score-adj", 0, "Tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000)") 145 cmd.Flags().String("pid", "", "PID namespace to use") 146 cmd.Flags().String("uts", "", "UTS namespace to use") 147 cmd.RegisterFlagCompletionFunc("pid", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 148 return []string{"host"}, cobra.ShellCompDirectiveNoFileComp 149 }) 150 cmd.Flags().Int64("pids-limit", -1, "Tune container pids limit (set -1 for unlimited)") 151 cmd.Flags().StringSlice("cgroup-conf", nil, "Configure cgroup v2 (key=value)") 152 cmd.Flags().Uint16("blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") 153 cmd.Flags().String("cgroupns", defaults.CgroupnsMode(), `Cgroup namespace to use, the default depends on the cgroup version ("host"|"private")`) 154 cmd.Flags().String("cgroup-parent", "", "Optional parent cgroup for the container") 155 cmd.RegisterFlagCompletionFunc("cgroupns", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 156 return []string{"host", "private"}, cobra.ShellCompDirectiveNoFileComp 157 }) 158 cmd.Flags().String("cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") 159 cmd.Flags().String("cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") 160 cmd.Flags().Uint64("cpu-shares", 0, "CPU shares (relative weight)") 161 cmd.Flags().Int64("cpu-quota", -1, "Limit CPU CFS (Completely Fair Scheduler) quota") 162 cmd.Flags().Uint64("cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") 163 // device is defined as StringSlice, not StringArray, to allow specifying "--device=DEV1,DEV2" (compatible with Podman) 164 cmd.Flags().StringSlice("device", nil, "Add a host device to the container") 165 // ulimit is defined as StringSlice, not StringArray, to allow specifying "--ulimit=ULIMIT1,ULIMIT2" (compatible with Podman) 166 cmd.Flags().StringSlice("ulimit", nil, "Ulimit options") 167 cmd.Flags().String("rdt-class", "", "Name of the RDT class (or CLOS) to associate the container with") 168 // #endregion 169 170 // user flags 171 cmd.Flags().StringP("user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") 172 cmd.Flags().String("umask", "", "Set the umask inside the container. Defaults to 0022") 173 cmd.Flags().StringSlice("group-add", []string{}, "Add additional groups to join") 174 175 // #region security flags 176 cmd.Flags().StringArray("security-opt", []string{}, "Security options") 177 cmd.RegisterFlagCompletionFunc("security-opt", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 178 return []string{ 179 "seccomp=", "seccomp=" + defaults.SeccompProfileName, "seccomp=unconfined", 180 "apparmor=", "apparmor=" + defaults.AppArmorProfileName, "apparmor=unconfined", 181 "no-new-privileges", 182 "privileged-without-host-devices"}, cobra.ShellCompDirectiveNoFileComp 183 }) 184 // cap-add and cap-drop are defined as StringSlice, not StringArray, to allow specifying "--cap-add=CAP_SYS_ADMIN,CAP_NET_ADMIN" (compatible with Podman) 185 cmd.Flags().StringSlice("cap-add", []string{}, "Add Linux capabilities") 186 cmd.RegisterFlagCompletionFunc("cap-add", capShellComplete) 187 cmd.Flags().StringSlice("cap-drop", []string{}, "Drop Linux capabilities") 188 cmd.RegisterFlagCompletionFunc("cap-drop", capShellComplete) 189 cmd.Flags().Bool("privileged", false, "Give extended privileges to this container") 190 // #endregion 191 192 // #region runtime flags 193 cmd.Flags().String("runtime", defaults.Runtime, "Runtime to use for this container, e.g. \"crun\", or \"io.containerd.runsc.v1\"") 194 // sysctl needs to be StringArray, not StringSlice, to prevent "foo=foo1,foo2" from being split to {"foo=foo1", "foo2"} 195 cmd.Flags().StringArray("sysctl", nil, "Sysctl options") 196 // gpus needs to be StringArray, not StringSlice, to prevent "capabilities=utility,device=DEV" from being split to {"capabilities=utility", "device=DEV"} 197 cmd.Flags().StringArray("gpus", nil, "GPU devices to add to the container ('all' to pass all GPUs)") 198 cmd.RegisterFlagCompletionFunc("gpus", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 199 return []string{"all"}, cobra.ShellCompDirectiveNoFileComp 200 }) 201 // #endregion 202 203 // #region mount flags 204 // volume needs to be StringArray, not StringSlice, to prevent "/foo:/foo:ro,Z" from being split to {"/foo:/foo:ro", "Z"} 205 cmd.Flags().StringArrayP("volume", "v", nil, "Bind mount a volume") 206 // tmpfs needs to be StringArray, not StringSlice, to prevent "/foo:size=64m,exec" from being split to {"/foo:size=64m", "exec"} 207 cmd.Flags().StringArray("tmpfs", nil, "Mount a tmpfs directory") 208 cmd.Flags().StringArray("mount", nil, "Attach a filesystem mount to the container") 209 // volumes-from needs to be StringArray, not StringSlice, to prevent "id1,id2" from being split to {"id1", "id2"} (compatible with Docker) 210 cmd.Flags().StringArray("volumes-from", nil, "Mount volumes from the specified container(s)") 211 // #endregion 212 213 // rootfs flags 214 cmd.Flags().Bool("read-only", false, "Mount the container's root filesystem as read only") 215 // rootfs flags (from Podman) 216 cmd.Flags().Bool("rootfs", false, "The first argument is not an image but the rootfs to the exploded container") 217 218 // #region env flags 219 // entrypoint needs to be StringArray, not StringSlice, to prevent "FOO=foo1,foo2" from being split to {"FOO=foo1", "foo2"} 220 // entrypoint StringArray is an internal implementation to support `nerdctl compose` entrypoint yaml filed with multiple strings 221 // users are not expected to specify multiple --entrypoint flags manually. 222 cmd.Flags().StringArray("entrypoint", nil, "Overwrite the default ENTRYPOINT of the image") 223 cmd.Flags().StringP("workdir", "w", "", "Working directory inside the container") 224 // env needs to be StringArray, not StringSlice, to prevent "FOO=foo1,foo2" from being split to {"FOO=foo1", "foo2"} 225 cmd.Flags().StringArrayP("env", "e", nil, "Set environment variables") 226 // add-host is defined as StringSlice, not StringArray, to allow specifying "--add-host=HOST1:IP1,HOST2:IP2" (compatible with Podman) 227 cmd.Flags().StringSlice("add-host", nil, "Add a custom host-to-IP mapping (host:ip)") 228 // env-file is defined as StringSlice, not StringArray, to allow specifying "--env-file=FILE1,FILE2" (compatible with Podman) 229 cmd.Flags().StringSlice("env-file", nil, "Set environment variables from file") 230 231 // #region metadata flags 232 cmd.Flags().String("name", "", "Assign a name to the container") 233 // label needs to be StringArray, not StringSlice, to prevent "foo=foo1,foo2" from being split to {"foo=foo1", "foo2"} 234 cmd.Flags().StringArrayP("label", "l", nil, "Set metadata on container") 235 cmd.RegisterFlagCompletionFunc("label", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 236 return labels.ShellCompletions, cobra.ShellCompDirectiveNoFileComp 237 }) 238 239 // label-file is defined as StringSlice, not StringArray, to allow specifying "--env-file=FILE1,FILE2" (compatible with Podman) 240 cmd.Flags().StringSlice("label-file", nil, "Set metadata on container from file") 241 cmd.Flags().String("cidfile", "", "Write the container ID to the file") 242 // #endregion 243 244 // #region logging flags 245 // log-opt needs to be StringArray, not StringSlice, to prevent "env=os,customer" from being split to {"env=os", "customer"} 246 cmd.Flags().String("log-driver", "json-file", "Logging driver for the container. Default is json-file. It also supports logURI (eg: --log-driver binary://<path>)") 247 cmd.RegisterFlagCompletionFunc("log-driver", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 248 return logging.Drivers(), cobra.ShellCompDirectiveNoFileComp 249 }) 250 cmd.Flags().StringArray("log-opt", nil, "Log driver options") 251 // #endregion 252 253 // shared memory flags 254 cmd.Flags().String("shm-size", "", "Size of /dev/shm") 255 cmd.Flags().String("pidfile", "", "file path to write the task's pid") 256 257 // #region verify flags 258 cmd.Flags().String("verify", "none", "Verify the image (none|cosign|notation)") 259 cmd.RegisterFlagCompletionFunc("verify", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 260 return []string{"none", "cosign", "notation"}, cobra.ShellCompDirectiveNoFileComp 261 }) 262 cmd.Flags().String("cosign-key", "", "Path to the public key file, KMS, URI or Kubernetes Secret for --verify=cosign") 263 cmd.Flags().String("cosign-certificate-identity", "", "The identity expected in a valid Fulcio certificate for --verify=cosign. Valid values include email address, DNS names, IP addresses, and URIs. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows") 264 cmd.Flags().String("cosign-certificate-identity-regexp", "", "A regular expression alternative to --cosign-certificate-identity for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows") 265 cmd.Flags().String("cosign-certificate-oidc-issuer", "", "The OIDC issuer expected in a valid Fulcio certificate for --verify=cosign, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows") 266 cmd.Flags().String("cosign-certificate-oidc-issuer-regexp", "", "A regular expression alternative to --certificate-oidc-issuer for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows") 267 // #endregion 268 269 cmd.Flags().String("ipfs-address", "", "multiaddr of IPFS API (default uses $IPFS_PATH env variable if defined or local directory ~/.ipfs)") 270 271 cmd.Flags().String("isolation", "default", "Specify isolation technology for container. On Linux the only valid value is default. Windows options are host, process and hyperv with process isolation as the default") 272 cmd.RegisterFlagCompletionFunc("isolation", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 273 if runtime.GOOS == "windows" { 274 return []string{"default", "host", "process", "hyperv"}, cobra.ShellCompDirectiveNoFileComp 275 } 276 return []string{"default"}, cobra.ShellCompDirectiveNoFileComp 277 }) 278 279 } 280 281 func processCreateCommandFlagsInRun(cmd *cobra.Command) (opt types.ContainerCreateOptions, err error) { 282 opt, err = processContainerCreateOptions(cmd) 283 if err != nil { 284 return 285 } 286 287 opt.InRun = true 288 289 opt.SigProxy, err = cmd.Flags().GetBool("sig-proxy") 290 if err != nil { 291 return 292 } 293 opt.Interactive, err = cmd.Flags().GetBool("interactive") 294 if err != nil { 295 return 296 } 297 opt.Detach, err = cmd.Flags().GetBool("detach") 298 if err != nil { 299 return 300 } 301 opt.DetachKeys, err = cmd.Flags().GetString("detach-keys") 302 if err != nil { 303 return 304 } 305 opt.Attach, err = cmd.Flags().GetStringSlice("attach") 306 if err != nil { 307 return 308 } 309 310 validAttachFlag := true 311 for i, str := range opt.Attach { 312 opt.Attach[i] = strings.ToUpper(str) 313 314 if opt.Attach[i] != "STDIN" && opt.Attach[i] != "STDOUT" && opt.Attach[i] != "STDERR" { 315 validAttachFlag = false 316 } 317 } 318 if !validAttachFlag { 319 return opt, fmt.Errorf("invalid stream specified with -a flag. Valid streams are STDIN, STDOUT, and STDERR") 320 } 321 322 return opt, nil 323 } 324 325 // runAction is heavily based on ctr implementation: 326 // https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/run/run.go 327 func runAction(cmd *cobra.Command, args []string) error { 328 createOpt, err := processCreateCommandFlagsInRun(cmd) 329 if err != nil { 330 return err 331 } 332 333 client, ctx, cancel, err := clientutil.NewClientWithPlatform(cmd.Context(), createOpt.GOptions.Namespace, createOpt.GOptions.Address, createOpt.Platform) 334 if err != nil { 335 return err 336 } 337 defer cancel() 338 339 if createOpt.Rm && createOpt.Detach { 340 return errors.New("flags -d and --rm cannot be specified together") 341 } 342 343 if len(createOpt.Attach) > 0 && createOpt.Detach { 344 return errors.New("flags -d and -a cannot be specified together") 345 } 346 347 netFlags, err := loadNetworkFlags(cmd) 348 if err != nil { 349 return fmt.Errorf("failed to load networking flags: %s", err) 350 } 351 352 netManager, err := containerutil.NewNetworkingOptionsManager(createOpt.GOptions, netFlags, client) 353 if err != nil { 354 return err 355 } 356 357 c, gc, err := container.Create(ctx, client, args, netManager, createOpt) 358 if err != nil { 359 if gc != nil { 360 defer gc() 361 } 362 return err 363 } 364 // defer setting `nerdctl/error` label in case of error 365 defer func() { 366 if err != nil { 367 containerutil.UpdateErrorLabel(ctx, c, err) 368 } 369 }() 370 371 id := c.ID() 372 if createOpt.Rm && !createOpt.Detach { 373 defer func() { 374 // NOTE: OCI hooks (which are used for CNI network setup/teardown on Linux) 375 // are not currently supported on Windows, so we must explicitly call 376 // network setup/cleanup from the main nerdctl executable. 377 if runtime.GOOS == "windows" { 378 if err := netManager.CleanupNetworking(ctx, c); err != nil { 379 log.L.Warnf("failed to clean up container networking: %s", err) 380 } 381 } 382 if err := container.RemoveContainer(ctx, c, createOpt.GOptions, true, true, client); err != nil { 383 log.L.WithError(err).Warnf("failed to remove container %s", id) 384 } 385 }() 386 } 387 388 var con console.Console 389 if createOpt.TTY && !createOpt.Detach { 390 con = console.Current() 391 defer con.Reset() 392 if err := con.SetRaw(); err != nil { 393 return err 394 } 395 } 396 397 lab, err := c.Labels(ctx) 398 if err != nil { 399 return err 400 } 401 logURI := lab[labels.LogURI] 402 detachC := make(chan struct{}) 403 task, err := taskutil.NewTask(ctx, client, c, createOpt.Attach, createOpt.Interactive, createOpt.TTY, createOpt.Detach, 404 con, logURI, createOpt.DetachKeys, createOpt.GOptions.Namespace, detachC) 405 if err != nil { 406 return err 407 } 408 if err := task.Start(ctx); err != nil { 409 return err 410 } 411 412 if createOpt.Detach { 413 fmt.Fprintln(createOpt.Stdout, id) 414 return nil 415 } 416 if createOpt.TTY { 417 if err := consoleutil.HandleConsoleResize(ctx, task, con); err != nil { 418 log.L.WithError(err).Error("console resize") 419 } 420 } else { 421 if createOpt.SigProxy { 422 sigC := signalutil.ForwardAllSignals(ctx, task) 423 defer signalutil.StopCatch(sigC) 424 } 425 } 426 427 statusC, err := task.Wait(ctx) 428 if err != nil { 429 return err 430 } 431 select { 432 // io.Wait() would return when either 1) the user detaches from the container OR 2) the container is about to exit. 433 // 434 // If we replace the `select` block with io.Wait() and 435 // directly use task.Status() to check the status of the container after io.Wait() returns, 436 // it can still be running even though the container is about to exit (somehow especially for Windows). 437 // 438 // As a result, we need a separate detachC to distinguish from the 2 cases mentioned above. 439 case <-detachC: 440 io := task.IO() 441 if io == nil { 442 return errors.New("got a nil IO from the task") 443 } 444 io.Wait() 445 case status := <-statusC: 446 if createOpt.Rm { 447 if _, taskDeleteErr := task.Delete(ctx); taskDeleteErr != nil { 448 log.L.Error(taskDeleteErr) 449 } 450 } 451 code, _, err := status.Result() 452 if err != nil { 453 return err 454 } 455 if code != 0 { 456 return errutil.NewExitCoderErr(int(code)) 457 } 458 } 459 return nil 460 }