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