github.com/openshift/moby-moby@v1.13.2-0.20170601211448-f5ec1e2936dc/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 ) 80 81 config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts) 82 83 // just in case the Parse does not exit 84 if err != nil { 85 reportError(stderr, cmdPath, err.Error(), true) 86 return cli.StatusError{StatusCode: 125} 87 } 88 89 if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 { 90 fmt.Fprintf(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n") 91 } 92 93 if len(hostConfig.DNS) > 0 { 94 // check the DNS settings passed via --dns against 95 // localhost regexp to warn if they are trying to 96 // set a DNS to a localhost address 97 for _, dnsIP := range hostConfig.DNS { 98 if dns.IsLocalhost(dnsIP) { 99 fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) 100 break 101 } 102 } 103 } 104 105 config.ArgsEscaped = false 106 107 if !opts.detach { 108 if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil { 109 return err 110 } 111 } else { 112 if fl := flags.Lookup("attach"); fl != nil { 113 flAttach = fl.Value.(*opttypes.ListOpts) 114 if flAttach.Len() != 0 { 115 return ErrConflictAttachDetach 116 } 117 } 118 119 config.AttachStdin = false 120 config.AttachStdout = false 121 config.AttachStderr = false 122 config.StdinOnce = false 123 } 124 125 // Disable sigProxy when in TTY mode 126 if config.Tty { 127 opts.sigProxy = false 128 } 129 130 // Telling the Windows daemon the initial size of the tty during start makes 131 // a far better user experience rather than relying on subsequent resizes 132 // to cause things to catch up. 133 if runtime.GOOS == "windows" { 134 hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize() 135 } 136 137 ctx, cancelFun := context.WithCancel(context.Background()) 138 139 // preserve AutoRemove state. createContainer() / ContainerCreate() disables daemon-side auto-remove on API < 1.25 140 autoRemove := hostConfig.AutoRemove 141 142 createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name) 143 if err != nil { 144 reportError(stderr, cmdPath, err.Error(), true) 145 return runStartContainerErr(err) 146 } 147 if opts.sigProxy { 148 sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID) 149 defer signal.StopCatch(sigc) 150 } 151 var ( 152 waitDisplayID chan struct{} 153 errCh chan error 154 ) 155 if !config.AttachStdout && !config.AttachStderr { 156 // Make this asynchronous to allow the client to write to stdin before having to read the ID 157 waitDisplayID = make(chan struct{}) 158 go func() { 159 defer close(waitDisplayID) 160 fmt.Fprintf(stdout, "%s\n", createResponse.ID) 161 }() 162 } 163 attach := config.AttachStdin || config.AttachStdout || config.AttachStderr 164 if attach { 165 var ( 166 out, cerr io.Writer 167 in io.ReadCloser 168 ) 169 if config.AttachStdin { 170 in = stdin 171 } 172 if config.AttachStdout { 173 out = stdout 174 } 175 if config.AttachStderr { 176 if config.Tty { 177 cerr = stdout 178 } else { 179 cerr = stderr 180 } 181 } 182 183 if opts.detachKeys != "" { 184 dockerCli.ConfigFile().DetachKeys = opts.detachKeys 185 } 186 187 options := types.ContainerAttachOptions{ 188 Stream: true, 189 Stdin: config.AttachStdin, 190 Stdout: config.AttachStdout, 191 Stderr: config.AttachStderr, 192 DetachKeys: dockerCli.ConfigFile().DetachKeys, 193 } 194 195 resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options) 196 if errAttach != nil && errAttach != httputil.ErrPersistEOF { 197 // ContainerAttach returns an ErrPersistEOF (connection closed) 198 // means server met an error and put it in Hijacked connection 199 // keep the error and read detailed error message from hijacked connection later 200 return errAttach 201 } 202 defer resp.Close() 203 204 errCh = promise.Go(func() error { 205 errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp) 206 if errHijack == nil { 207 return errAttach 208 } 209 return errHijack 210 }) 211 } 212 213 statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, autoRemove) 214 215 //start the container 216 if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil { 217 // If we have holdHijackedConnection, we should notify 218 // holdHijackedConnection we are going to exit and wait 219 // to avoid the terminal are not restored. 220 if attach { 221 cancelFun() 222 <-errCh 223 } 224 225 reportError(stderr, cmdPath, err.Error(), false) 226 if autoRemove { 227 // wait container to be removed 228 <-statusChan 229 } 230 return runStartContainerErr(err) 231 } 232 233 if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() { 234 if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil { 235 fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err) 236 } 237 } 238 239 if errCh != nil { 240 if err := <-errCh; err != nil { 241 logrus.Debugf("Error hijack: %s", err) 242 return err 243 } 244 } 245 246 // Detached mode: wait for the id to be displayed and return. 247 if !config.AttachStdout && !config.AttachStderr { 248 // Detached mode 249 <-waitDisplayID 250 return nil 251 } 252 253 status := <-statusChan 254 if status != 0 { 255 return cli.StatusError{StatusCode: status} 256 } 257 return nil 258 } 259 260 // reportError is a utility method that prints a user-friendly message 261 // containing the error that occurred during parsing and a suggestion to get help 262 func reportError(stderr io.Writer, name string, str string, withHelp bool) { 263 if withHelp { 264 str += ".\nSee '" + os.Args[0] + " " + name + " --help'" 265 } 266 fmt.Fprintf(stderr, "%s: %s.\n", os.Args[0], str) 267 } 268 269 // if container start fails with 'not found'/'no such' error, return 127 270 // if container start fails with 'permission denied' error, return 126 271 // return 125 for generic docker daemon failures 272 func runStartContainerErr(err error) error { 273 trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ") 274 statusError := cli.StatusError{StatusCode: 125} 275 if strings.Contains(trimmedErr, "executable file not found") || 276 strings.Contains(trimmedErr, "no such file or directory") || 277 strings.Contains(trimmedErr, "system cannot find the file specified") { 278 statusError = cli.StatusError{StatusCode: 127} 279 } else if strings.Contains(trimmedErr, syscall.EACCES.Error()) { 280 statusError = cli.StatusError{StatusCode: 126} 281 } 282 283 return statusError 284 }