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  }