github.com/kunnos/engine@v1.13.1/cli/command/container/run.go (about) 1 package container 2 3 import ( 4 "fmt" 5 "io" 6 "net/http/httputil" 7 "os" 8 "runtime" 9 "strings" 10 "syscall" 11 12 "golang.org/x/net/context" 13 14 "github.com/sirupsen/logrus" 15 "github.com/docker/docker/api/types" 16 "github.com/docker/docker/cli" 17 "github.com/docker/docker/cli/command" 18 opttypes "github.com/docker/docker/opts" 19 "github.com/docker/docker/pkg/promise" 20 "github.com/docker/docker/pkg/signal" 21 runconfigopts "github.com/docker/docker/runconfig/opts" 22 "github.com/docker/libnetwork/resolvconf/dns" 23 "github.com/spf13/cobra" 24 "github.com/spf13/pflag" 25 ) 26 27 type runOptions struct { 28 detach bool 29 sigProxy bool 30 name string 31 detachKeys string 32 } 33 34 // NewRunCommand create a new `docker run` command 35 func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command { 36 var opts runOptions 37 var copts *runconfigopts.ContainerOptions 38 39 cmd := &cobra.Command{ 40 Use: "run [OPTIONS] IMAGE [COMMAND] [ARG...]", 41 Short: "Run a command in a new container", 42 Args: cli.RequiresMinArgs(1), 43 RunE: func(cmd *cobra.Command, args []string) error { 44 copts.Image = args[0] 45 if len(args) > 1 { 46 copts.Args = args[1:] 47 } 48 return runRun(dockerCli, cmd.Flags(), &opts, copts) 49 }, 50 } 51 52 flags := cmd.Flags() 53 flags.SetInterspersed(false) 54 55 // These are flags not stored in Config/HostConfig 56 flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID") 57 flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process") 58 flags.StringVar(&opts.name, "name", "", "Assign a name to the container") 59 flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") 60 61 // Add an explicit help that doesn't have a `-h` to prevent the conflict 62 // with hostname 63 flags.Bool("help", false, "Print usage") 64 65 command.AddTrustedFlags(flags, true) 66 copts = runconfigopts.AddFlags(flags) 67 return cmd 68 } 69 70 func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error { 71 stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In() 72 client := dockerCli.Client() 73 // TODO: pass this as an argument 74 cmdPath := "run" 75 76 var ( 77 flAttach *opttypes.ListOpts 78 ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") 79 ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") 80 ) 81 82 config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts) 83 84 // just in case the Parse does not exit 85 if err != nil { 86 reportError(stderr, cmdPath, err.Error(), true) 87 return cli.StatusError{StatusCode: 125} 88 } 89 90 if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() { 91 return ErrConflictRestartPolicyAndAutoRemove 92 } 93 if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 { 94 fmt.Fprintf(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n") 95 } 96 97 if len(hostConfig.DNS) > 0 { 98 // check the DNS settings passed via --dns against 99 // localhost regexp to warn if they are trying to 100 // set a DNS to a localhost address 101 for _, dnsIP := range hostConfig.DNS { 102 if dns.IsLocalhost(dnsIP) { 103 fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) 104 break 105 } 106 } 107 } 108 109 config.ArgsEscaped = false 110 111 if !opts.detach { 112 if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil { 113 return err 114 } 115 } else { 116 if fl := flags.Lookup("attach"); fl != nil { 117 flAttach = fl.Value.(*opttypes.ListOpts) 118 if flAttach.Len() != 0 { 119 return ErrConflictAttachDetach 120 } 121 } 122 123 config.AttachStdin = false 124 config.AttachStdout = false 125 config.AttachStderr = false 126 config.StdinOnce = false 127 } 128 129 // Disable sigProxy when in TTY mode 130 if config.Tty { 131 opts.sigProxy = false 132 } 133 134 // Telling the Windows daemon the initial size of the tty during start makes 135 // a far better user experience rather than relying on subsequent resizes 136 // to cause things to catch up. 137 if runtime.GOOS == "windows" { 138 hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize() 139 } 140 141 ctx, cancelFun := context.WithCancel(context.Background()) 142 143 createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name) 144 if err != nil { 145 reportError(stderr, cmdPath, err.Error(), true) 146 return runStartContainerErr(err) 147 } 148 if opts.sigProxy { 149 sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID) 150 defer signal.StopCatch(sigc) 151 } 152 var ( 153 waitDisplayID chan struct{} 154 errCh chan error 155 ) 156 if !config.AttachStdout && !config.AttachStderr { 157 // Make this asynchronous to allow the client to write to stdin before having to read the ID 158 waitDisplayID = make(chan struct{}) 159 go func() { 160 defer close(waitDisplayID) 161 fmt.Fprintf(stdout, "%s\n", createResponse.ID) 162 }() 163 } 164 attach := config.AttachStdin || config.AttachStdout || config.AttachStderr 165 if attach { 166 var ( 167 out, cerr io.Writer 168 in io.ReadCloser 169 ) 170 if config.AttachStdin { 171 in = stdin 172 } 173 if config.AttachStdout { 174 out = stdout 175 } 176 if config.AttachStderr { 177 if config.Tty { 178 cerr = stdout 179 } else { 180 cerr = stderr 181 } 182 } 183 184 if opts.detachKeys != "" { 185 dockerCli.ConfigFile().DetachKeys = opts.detachKeys 186 } 187 188 options := types.ContainerAttachOptions{ 189 Stream: true, 190 Stdin: config.AttachStdin, 191 Stdout: config.AttachStdout, 192 Stderr: config.AttachStderr, 193 DetachKeys: dockerCli.ConfigFile().DetachKeys, 194 } 195 196 resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options) 197 if errAttach != nil && errAttach != httputil.ErrPersistEOF { 198 // ContainerAttach returns an ErrPersistEOF (connection closed) 199 // means server met an error and put it in Hijacked connection 200 // keep the error and read detailed error message from hijacked connection later 201 return errAttach 202 } 203 defer resp.Close() 204 205 errCh = promise.Go(func() error { 206 errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp) 207 if errHijack == nil { 208 return errAttach 209 } 210 return errHijack 211 }) 212 } 213 214 statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, hostConfig.AutoRemove) 215 216 //start the container 217 if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil { 218 // If we have holdHijackedConnection, we should notify 219 // holdHijackedConnection we are going to exit and wait 220 // to avoid the terminal are not restored. 221 if attach { 222 cancelFun() 223 <-errCh 224 } 225 226 reportError(stderr, cmdPath, err.Error(), false) 227 if hostConfig.AutoRemove { 228 // wait container to be removed 229 <-statusChan 230 } 231 return runStartContainerErr(err) 232 } 233 234 if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() { 235 if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil { 236 fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err) 237 } 238 } 239 240 if errCh != nil { 241 if err := <-errCh; err != nil { 242 logrus.Debugf("Error hijack: %s", err) 243 return err 244 } 245 } 246 247 // Detached mode: wait for the id to be displayed and return. 248 if !config.AttachStdout && !config.AttachStderr { 249 // Detached mode 250 <-waitDisplayID 251 return nil 252 } 253 254 status := <-statusChan 255 if status != 0 { 256 return cli.StatusError{StatusCode: status} 257 } 258 return nil 259 } 260 261 // reportError is a utility method that prints a user-friendly message 262 // containing the error that occurred during parsing and a suggestion to get help 263 func reportError(stderr io.Writer, name string, str string, withHelp bool) { 264 if withHelp { 265 str += ".\nSee '" + os.Args[0] + " " + name + " --help'" 266 } 267 fmt.Fprintf(stderr, "%s: %s.\n", os.Args[0], str) 268 } 269 270 // if container start fails with 'not found'/'no such' error, return 127 271 // if container start fails with 'permission denied' error, return 126 272 // return 125 for generic docker daemon failures 273 func runStartContainerErr(err error) error { 274 trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ") 275 statusError := cli.StatusError{StatusCode: 125} 276 if strings.Contains(trimmedErr, "executable file not found") || 277 strings.Contains(trimmedErr, "no such file or directory") || 278 strings.Contains(trimmedErr, "system cannot find the file specified") { 279 statusError = cli.StatusError{StatusCode: 127} 280 } else if strings.Contains(trimmedErr, syscall.EACCES.Error()) { 281 statusError = cli.StatusError{StatusCode: 126} 282 } 283 284 return statusError 285 }