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