github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/run.go (about) 1 package container 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "strings" 9 "syscall" 10 11 "github.com/docker/cli/cli" 12 "github.com/docker/cli/cli/command" 13 "github.com/docker/cli/cli/command/completion" 14 "github.com/docker/cli/opts" 15 "github.com/docker/docker/api/types/container" 16 "github.com/moby/sys/signal" 17 "github.com/moby/term" 18 "github.com/pkg/errors" 19 "github.com/sirupsen/logrus" 20 "github.com/spf13/cobra" 21 "github.com/spf13/pflag" 22 ) 23 24 type runOptions struct { 25 createOptions 26 detach bool 27 sigProxy bool 28 detachKeys string 29 } 30 31 // NewRunCommand create a new `docker run` command 32 func NewRunCommand(dockerCli command.Cli) *cobra.Command { 33 var options runOptions 34 var copts *containerOptions 35 36 cmd := &cobra.Command{ 37 Use: "run [OPTIONS] IMAGE [COMMAND] [ARG...]", 38 Short: "Create and run a new container from an image", 39 Args: cli.RequiresMinArgs(1), 40 RunE: func(cmd *cobra.Command, args []string) error { 41 copts.Image = args[0] 42 if len(args) > 1 { 43 copts.Args = args[1:] 44 } 45 return runRun(cmd.Context(), dockerCli, cmd.Flags(), &options, copts) 46 }, 47 ValidArgsFunction: completion.ImageNames(dockerCli), 48 Annotations: map[string]string{ 49 "category-top": "1", 50 "aliases": "docker container run, docker run", 51 }, 52 } 53 54 flags := cmd.Flags() 55 flags.SetInterspersed(false) 56 57 // These are flags not stored in Config/HostConfig 58 flags.BoolVarP(&options.detach, "detach", "d", false, "Run container in background and print container ID") 59 flags.BoolVar(&options.sigProxy, "sig-proxy", true, "Proxy received signals to the process") 60 flags.StringVar(&options.name, "name", "", "Assign a name to the container") 61 flags.StringVar(&options.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") 62 flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before running ("`+PullImageAlways+`", "`+PullImageMissing+`", "`+PullImageNever+`")`) 63 flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output") 64 65 // Add an explicit help that doesn't have a `-h` to prevent the conflict 66 // with hostname 67 flags.Bool("help", false, "Print usage") 68 69 command.AddPlatformFlag(flags, &options.platform) 70 command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) 71 copts = addFlags(flags) 72 73 cmd.RegisterFlagCompletionFunc( 74 "env", 75 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 76 return os.Environ(), cobra.ShellCompDirectiveNoFileComp 77 }, 78 ) 79 cmd.RegisterFlagCompletionFunc( 80 "env-file", 81 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 82 return nil, cobra.ShellCompDirectiveDefault 83 }, 84 ) 85 cmd.RegisterFlagCompletionFunc( 86 "network", 87 completion.NetworkNames(dockerCli), 88 ) 89 return cmd 90 } 91 92 func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error { 93 if err := validatePullOpt(ropts.pull); err != nil { 94 reportError(dockerCli.Err(), "run", err.Error(), true) 95 return cli.StatusError{StatusCode: 125} 96 } 97 proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll())) 98 newEnv := []string{} 99 for k, v := range proxyConfig { 100 if v == nil { 101 newEnv = append(newEnv, k) 102 } else { 103 newEnv = append(newEnv, k+"="+*v) 104 } 105 } 106 copts.env = *opts.NewListOptsRef(&newEnv, nil) 107 containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType) 108 // just in case the parse does not exit 109 if err != nil { 110 reportError(dockerCli.Err(), "run", err.Error(), true) 111 return cli.StatusError{StatusCode: 125} 112 } 113 if err = validateAPIVersion(containerCfg, dockerCli.CurrentVersion()); err != nil { 114 reportError(dockerCli.Err(), "run", err.Error(), true) 115 return cli.StatusError{StatusCode: 125} 116 } 117 return runContainer(ctx, dockerCli, ropts, copts, containerCfg) 118 } 119 120 //nolint:gocyclo 121 func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOptions, copts *containerOptions, containerCfg *containerConfig) error { 122 config := containerCfg.Config 123 stdout, stderr := dockerCli.Out(), dockerCli.Err() 124 apiClient := dockerCli.Client() 125 126 config.ArgsEscaped = false 127 128 if !runOpts.detach { 129 if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil { 130 return err 131 } 132 } else { 133 if copts.attach.Len() != 0 { 134 return errors.New("Conflicting options: -a and -d") 135 } 136 137 config.AttachStdin = false 138 config.AttachStdout = false 139 config.AttachStderr = false 140 config.StdinOnce = false 141 } 142 143 ctx, cancelFun := context.WithCancel(ctx) 144 defer cancelFun() 145 146 containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions) 147 if err != nil { 148 reportError(stderr, "run", err.Error(), true) 149 return runStartContainerErr(err) 150 } 151 if runOpts.sigProxy { 152 sigc := notifyAllSignals() 153 go ForwardAllSignals(ctx, apiClient, containerID, sigc) 154 defer signal.StopCatch(sigc) 155 } 156 157 var ( 158 waitDisplayID chan struct{} 159 errCh chan error 160 ) 161 if !config.AttachStdout && !config.AttachStderr { 162 // Make this asynchronous to allow the client to write to stdin before having to read the ID 163 waitDisplayID = make(chan struct{}) 164 go func() { 165 defer close(waitDisplayID) 166 _, _ = fmt.Fprintln(stdout, containerID) 167 }() 168 } 169 attach := config.AttachStdin || config.AttachStdout || config.AttachStderr 170 if attach { 171 detachKeys := dockerCli.ConfigFile().DetachKeys 172 if runOpts.detachKeys != "" { 173 detachKeys = runOpts.detachKeys 174 } 175 176 closeFn, err := attachContainer(ctx, dockerCli, containerID, &errCh, config, container.AttachOptions{ 177 Stream: true, 178 Stdin: config.AttachStdin, 179 Stdout: config.AttachStdout, 180 Stderr: config.AttachStderr, 181 DetachKeys: detachKeys, 182 }) 183 if err != nil { 184 return err 185 } 186 defer closeFn() 187 } 188 189 // New context here because we don't to cancel waiting on container exit/remove 190 // when we cancel attach, etc. 191 statusCtx, cancelStatusCtx := context.WithCancel(context.WithoutCancel(ctx)) 192 defer cancelStatusCtx() 193 statusChan := waitExitOrRemoved(statusCtx, apiClient, containerID, copts.autoRemove) 194 195 // start the container 196 if err := apiClient.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil { 197 // If we have hijackedIOStreamer, we should notify 198 // hijackedIOStreamer we are going to exit and wait 199 // to avoid the terminal are not restored. 200 if attach { 201 cancelFun() 202 <-errCh 203 } 204 205 reportError(stderr, "run", err.Error(), false) 206 if copts.autoRemove { 207 // wait container to be removed 208 <-statusChan 209 } 210 return runStartContainerErr(err) 211 } 212 213 if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() { 214 if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil { 215 _, _ = fmt.Fprintln(stderr, "Error monitoring TTY size:", err) 216 } 217 } 218 219 if errCh != nil { 220 if err := <-errCh; err != nil { 221 if _, ok := err.(term.EscapeError); ok { 222 // The user entered the detach escape sequence. 223 return nil 224 } 225 226 logrus.Debugf("Error hijack: %s", err) 227 return err 228 } 229 } 230 231 // Detached mode: wait for the id to be displayed and return. 232 if !config.AttachStdout && !config.AttachStderr { 233 // Detached mode 234 <-waitDisplayID 235 return nil 236 } 237 238 status := <-statusChan 239 if status != 0 { 240 return cli.StatusError{StatusCode: status} 241 } 242 return nil 243 } 244 245 func attachContainer(ctx context.Context, dockerCli command.Cli, containerID string, errCh *chan error, config *container.Config, options container.AttachOptions) (func(), error) { 246 resp, errAttach := dockerCli.Client().ContainerAttach(ctx, containerID, options) 247 if errAttach != nil { 248 return nil, errAttach 249 } 250 251 var ( 252 out, cerr io.Writer 253 in io.ReadCloser 254 ) 255 if options.Stdin { 256 in = dockerCli.In() 257 } 258 if options.Stdout { 259 out = dockerCli.Out() 260 } 261 if options.Stderr { 262 if config.Tty { 263 cerr = dockerCli.Out() 264 } else { 265 cerr = dockerCli.Err() 266 } 267 } 268 269 ch := make(chan error, 1) 270 *errCh = ch 271 272 go func() { 273 ch <- func() error { 274 streamer := hijackedIOStreamer{ 275 streams: dockerCli, 276 inputStream: in, 277 outputStream: out, 278 errorStream: cerr, 279 resp: resp, 280 tty: config.Tty, 281 detachKeys: options.DetachKeys, 282 } 283 284 if errHijack := streamer.stream(ctx); errHijack != nil { 285 return errHijack 286 } 287 return errAttach 288 }() 289 }() 290 return resp.Close, nil 291 } 292 293 // reportError is a utility method that prints a user-friendly message 294 // containing the error that occurred during parsing and a suggestion to get help 295 func reportError(stderr io.Writer, name string, str string, withHelp bool) { 296 str = strings.TrimSuffix(str, ".") + "." 297 if withHelp { 298 str += "\nSee 'docker " + name + " --help'." 299 } 300 _, _ = fmt.Fprintln(stderr, "docker:", str) 301 } 302 303 // if container start fails with 'not found'/'no such' error, return 127 304 // if container start fails with 'permission denied' error, return 126 305 // return 125 for generic docker daemon failures 306 func runStartContainerErr(err error) error { 307 trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ") 308 statusError := cli.StatusError{StatusCode: 125} 309 if strings.Contains(trimmedErr, "executable file not found") || 310 strings.Contains(trimmedErr, "no such file or directory") || 311 strings.Contains(trimmedErr, "system cannot find the file specified") { 312 statusError = cli.StatusError{StatusCode: 127} 313 } else if strings.Contains(trimmedErr, syscall.EACCES.Error()) || 314 strings.Contains(trimmedErr, syscall.EISDIR.Error()) { 315 statusError = cli.StatusError{StatusCode: 126} 316 } 317 318 return statusError 319 }