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