func (container *Container) waitForStart() error { if container.Config.MonitorDriver != MonitorExternal { container.monitor = newContainerMonitor(container, container.hostConfig.RestartPolicy) // block until we either receive an error from the initial start of the container's // process or until the process is running in the container select { case <-container.monitor.startSignal: case err := <-promise.Go(container.monitor.Start): return err } } else { // external monitor monitor := NewMonitorProxy(container, true) if monitor == nil { return fmt.Errorf("Create monitor proxy failed") } container.exMonitor = monitor select { case <-container.exMonitor.startSignal: pid := monitor.monitorCommand.cmd.Process.Pid container.monitorState.SetRunning(pid) log.Debugf("Monitor process has started with pid %v", pid) if err := container.WriteMonitorState(); err != nil { log.Errorf("write monitor state error: %v", err) } case err := <-promise.Go(monitor.Start): if err != nil { log.Debugf("MonitorProxy start error: %v", err) } return err } } return nil }
func (b *Builder) run(c *daemon.Container) error { var errCh chan error if b.Verbose { errCh = promise.Go(func() error { // FIXME: call the 'attach' job so that daemon.Attach can be made private // // FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach // but without hijacking for stdin. Also, with attach there can be race // condition because of some output already was printed before it. return <-b.Daemon.Attach(&c.StreamConfig, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream) }) } //start the container if err := c.Start(); err != nil { return err } if errCh != nil { if err := <-errCh; err != nil { return err } } // Wait for it to finish if ret, _ := c.WaitStop(-1 * time.Second); ret != 0 { err := &utils.JSONError{ Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret), Code: ret, } return err } return nil }
func (container *Container) exec(ExecConfig *ExecConfig) error { container.Lock() defer container.Unlock() callback := func(processConfig *execdriver.ProcessConfig, pid int) { if processConfig.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave // which we close here. if c, ok := processConfig.Stdout.(io.Closer); ok { c.Close() } } close(ExecConfig.waitStart) } // We use a callback here instead of a goroutine and an chan for // synchronization purposes cErr := promise.Go(func() error { return container.monitorExec(ExecConfig, callback) }) // Exec should not return until the process is actually running select { case <-ExecConfig.waitStart: case err := <-cErr: return err } return nil }
// Run creates, start and attach to the container based on the image name, // the specified configuration. // It will always create a new container. func (c *Container) Run(ctx context.Context, configOverride *config.ServiceConfig) (int, error) { var ( errCh chan error out, stderr io.Writer in io.ReadCloser ) if configOverride.StdinOpen { in = os.Stdin } if configOverride.Tty { out = os.Stdout } if configOverride.Tty { stderr = os.Stderr } options := types.ContainerAttachOptions{ Stream: true, Stdin: configOverride.StdinOpen, Stdout: configOverride.Tty, Stderr: configOverride.Tty, } resp, err := c.client.ContainerAttach(ctx, c.container.ID, options) if err != nil { return -1, err } // set raw terminal inFd, _ := term.GetFdInfo(in) state, err := term.SetRawTerminal(inFd) if err != nil { return -1, err } // restore raw terminal defer term.RestoreTerminal(inFd, state) // holdHijackedConnection (in goroutine) errCh = promise.Go(func() error { return holdHijackedConnection(configOverride.Tty, in, out, stderr, resp) }) if err := c.client.ContainerStart(ctx, c.container.ID, types.ContainerStartOptions{}); err != nil { return -1, err } if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return -1, err } exitedContainer, err := c.client.ContainerInspect(ctx, c.container.ID) if err != nil { return -1, err } return exitedContainer.State.ExitCode, nil }
// wait starts the container and wait until // we either receive an error from the initial start of the container's // process or until the process is running in the container func (m *containerMonitor) wait() error { select { case <-m.startSignal: case err := <-promise.Go(m.start): return err } return nil }
func (dm *DockerMonitor) Start() error { // block until we either receive an error from the initial start of the container's // process or until the process is running in the container select { case <-dm.m.startSignal: case err := <-promise.Go(dm.m.Start): return err } return nil }
func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst) srcSt, err := os.Stat(src) if err != nil { return err } if srcSt.IsDir() { return fmt.Errorf("Can't copy a directory") } // Clean up the trailing slash. This must be done in an operating // system specific manner. if dst[len(dst)-1] == os.PathSeparator { dst = filepath.Join(dst, filepath.Base(src)) } // Create the holding directory if necessary if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) { return err } r, w := io.Pipe() errC := promise.Go(func() error { defer w.Close() srcF, err := os.Open(src) if err != nil { return err } defer srcF.Close() hdr, err := tar.FileInfoHeader(srcSt, "") if err != nil { return err } hdr.Name = filepath.Base(dst) hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) tw := tar.NewWriter(w) defer tw.Close() if err := tw.WriteHeader(hdr); err != nil { return err } if _, err := io.Copy(tw, srcF); err != nil { return err } return nil }) defer func() { if er := <-errC; err != nil { err = er } }() return archiver.Untar(r, filepath.Dir(dst), nil) }
func (container *Container) waitForStart() error { container.monitor = newContainerMonitor(container, container.hostConfig.RestartPolicy) // block until we either receive an error from the initial start of the container's // process or until the process is running in the container select { case <-container.monitor.startSignal: case err := <-promise.Go(container.monitor.Start): return err } return nil }
func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { log.Debugf("CopyFileWithTar(%s, %s)", src, dst) srcSt, err := os.Stat(src) if err != nil { return err } if srcSt.IsDir() { return fmt.Errorf("Can't copy a directory") } // Clean up the trailing / if dst[len(dst)-1] == '/' { dst = path.Join(dst, filepath.Base(src)) } // Create the holding directory if necessary if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) { return err } r, w := io.Pipe() errC := promise.Go(func() error { defer w.Close() srcF, err := os.Open(src) if err != nil { return err } defer srcF.Close() hdr, err := tar.FileInfoHeader(srcSt, "") if err != nil { return err } hdr.Name = filepath.Base(dst) tw := tar.NewWriter(w) defer tw.Close() if err := tw.WriteHeader(hdr); err != nil { return err } if _, err := io.Copy(tw, srcF); err != nil { return err } return nil }) defer func() { if er := <-errC; err != nil { err = er } }() return archiver.Untar(r, filepath.Dir(dst), nil) }
func (cli *HyperClient) hijackRequest(method, pod, tag string, v *url.Values) error { var ( hijacked = make(chan io.Closer) errCh chan error ) // Block the return until the chan gets closed defer func() { if _, ok := <-hijacked; ok { fmt.Printf("Hijack did not finish (chan still open)\n") } }() request := fmt.Sprintf("/%s?%s", method, v.Encode()) errCh = promise.Go(func() error { return cli.hijack("POST", request, true, cli.in, cli.out, cli.out, hijacked, nil, "") }) if err := cli.monitorTtySize(pod, tag); err != nil { fmt.Printf("Monitor tty size fail for %s!\n", pod) } // Acknowledge the hijack before starting select { case closer := <-hijacked: // Make sure that hijack gets closed when returning. (result // in closing hijack chan and freeing server's goroutines. if closer != nil { defer closer.Close() } case err := <-errCh: if err != nil { fmt.Printf("Error hijack: %s", err.Error()) return err } } if err := <-errCh; err != nil { fmt.Printf("Error hijack: %s", err.Error()) return err } return nil }
func (d *Daemon) containerExec(container *container.Container, ec *exec.Config) error { container.Lock() defer container.Unlock() callback := func(processConfig *execdriver.ProcessConfig, pid int, chOOM <-chan struct{}) error { if processConfig.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave // which we close here. if c, ok := processConfig.Stdout.(io.Closer); ok { c.Close() } } ec.Close() return nil } // We use a callback here instead of a goroutine and an chan for // synchronization purposes cErr := promise.Go(func() error { return d.monitorExec(container, ec, callback) }) return ec.Wait(cErr) }
func attach(streamConfig *streamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { var ( cStdout, cStderr io.ReadCloser cStdin io.WriteCloser wg sync.WaitGroup errors = make(chan error, 3) ) if stdin != nil && openStdin { cStdin = streamConfig.StdinPipe() wg.Add(1) } if stdout != nil { cStdout = streamConfig.StdoutPipe() wg.Add(1) } if stderr != nil { cStderr = streamConfig.StderrPipe() wg.Add(1) } // Connect stdin of container to the http conn. go func() { if stdin == nil || !openStdin { return } logrus.Debugf("attach: stdin: begin") defer func() { if stdinOnce && !tty { cStdin.Close() } else { // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } } wg.Done() logrus.Debugf("attach: stdin: end") }() var err error if tty { _, err = copyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err == io.ErrClosedPipe { err = nil } if err != nil { logrus.Errorf("attach: stdin: %s", err) errors <- err return } }() attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) { if stream == nil { return } defer func() { // Make sure stdin gets closed if stdin != nil { stdin.Close() } streamPipe.Close() wg.Done() logrus.Debugf("attach: %s: end", name) }() logrus.Debugf("attach: %s: begin", name) _, err := io.Copy(stream, streamPipe) if err == io.ErrClosedPipe { err = nil } if err != nil { logrus.Errorf("attach: %s: %v", name, err) errors <- err } } go attachStream("stdout", stdout, cStdout) go attachStream("stderr", stderr, cStderr) return promise.Go(func() error { wg.Wait() close(errors) for err := range errors { if err != nil { return err } } return nil }) }
func runStart(dockerCli *client.DockerCli, opts *startOptions) error { ctx, cancelFun := context.WithCancel(context.Background()) if opts.attach || opts.openStdin { // We're going to attach to a container. // 1. Ensure we only have one container. if len(opts.containers) > 1 { return fmt.Errorf("You cannot start and attach multiple containers at once.") } // 2. Attach to the container. container := opts.containers[0] c, err := dockerCli.Client().ContainerInspect(ctx, container) if err != nil { return err } // We always use c.ID instead of container to maintain consistency during `docker start` if !c.Config.Tty { sigc := dockerCli.ForwardAllSignals(ctx, c.ID) defer signal.StopCatch(sigc) } if opts.detachKeys != "" { dockerCli.ConfigFile().DetachKeys = opts.detachKeys } options := types.ContainerAttachOptions{ Stream: true, Stdin: opts.openStdin && c.Config.OpenStdin, Stdout: true, Stderr: true, DetachKeys: dockerCli.ConfigFile().DetachKeys, } var in io.ReadCloser if options.Stdin { in = dockerCli.In() } resp, errAttach := dockerCli.Client().ContainerAttach(ctx, c.ID, options) if errAttach != nil && errAttach != httputil.ErrPersistEOF { // ContainerAttach return an ErrPersistEOF (connection closed) // means server met an error and put it in Hijacked connection // keep the error and read detailed error message from hijacked connection return errAttach } defer resp.Close() cErr := promise.Go(func() error { errHijack := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp) if errHijack == nil { return errAttach } return errHijack }) // 3. Start the container. if err := dockerCli.Client().ContainerStart(ctx, c.ID, types.ContainerStartOptions{}); err != nil { cancelFun() <-cErr return err } // 4. Wait for attachment to break. if c.Config.Tty && dockerCli.IsTerminalOut() { if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil { fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) } } if attchErr := <-cErr; attchErr != nil { return attchErr } _, status, err := getExitCode(dockerCli, ctx, c.ID) if err != nil { return err } if status != 0 { return cli.StatusError{StatusCode: status} } } else { // We're not going to attach to anything. // Start as many containers as we want. return startContainersWithoutAttachments(dockerCli, ctx, opts.containers) } return nil }
// CmdRun runs a command in a new container. // // Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] func (cli *DockerCli) CmdRun(args ...string) error { cmd := cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true) // These are flags not stored in Config/HostConfig var ( flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process") flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") flAttach *opts.ListOpts ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") ) config, hostConfig, cmd, err := runconfig.Parse(cmd, args) // just in case the Parse does not exit if err != nil { cmd.ReportError(err.Error(), true) os.Exit(1) } if len(hostConfig.Dns) > 0 { // check the DNS settings passed via --dns against // localhost regexp to warn if they are trying to // set a DNS to a localhost address for _, dnsIP := range hostConfig.Dns { if dns.IsLocalhost(dnsIP) { fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) break } } } if config.Image == "" { cmd.Usage() return nil } if !*flDetach { if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { return err } } else { if fl := cmd.Lookup("-attach"); fl != nil { flAttach = fl.Value.(*opts.ListOpts) if flAttach.Len() != 0 { return ErrConflictAttachDetach } } if *flAutoRemove { return ErrConflictDetachAutoRemove } config.AttachStdin = false config.AttachStdout = false config.AttachStderr = false config.StdinOnce = false } // Disable flSigProxy when in TTY mode sigProxy := *flSigProxy if config.Tty { sigProxy = false } // Telling the Windows daemon the initial size of the tty during start makes // a far better user experience rather than relying on subsequent resizes // to cause things to catch up. if runtime.GOOS == "windows" { hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize() } createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) if err != nil { return err } if sigProxy { sigc := cli.forwardAllSignals(createResponse.ID) defer signal.StopCatch(sigc) } var ( waitDisplayID chan struct{} errCh chan error ) if !config.AttachStdout && !config.AttachStderr { // Make this asynchronous to allow the client to write to stdin before having to read the ID waitDisplayID = make(chan struct{}) go func() { defer close(waitDisplayID) fmt.Fprintf(cli.out, "%s\n", createResponse.ID) }() } if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) { return ErrConflictRestartPolicyAndAutoRemove } // We need to instantiate the chan because the select needs it. It can // be closed but can't be uninitialized. hijacked := make(chan io.Closer) // Block the return until the chan gets closed defer func() { logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.") if _, ok := <-hijacked; ok { fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)") } }() if config.AttachStdin || config.AttachStdout || config.AttachStderr { var ( out, stderr io.Writer in io.ReadCloser v = url.Values{} ) v.Set("stream", "1") if config.AttachStdin { v.Set("stdin", "1") in = cli.in } if config.AttachStdout { v.Set("stdout", "1") out = cli.out } if config.AttachStderr { v.Set("stderr", "1") if config.Tty { stderr = cli.out } else { stderr = cli.err } } errCh = promise.Go(func() error { return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil) }) } else { close(hijacked) } // Acknowledge the hijack before starting select { case closer := <-hijacked: // Make sure that the hijack gets closed when returning (results // in closing the hijack chan and freeing server's goroutines) if closer != nil { defer closer.Close() } case err := <-errCh: if err != nil { logrus.Debugf("Error hijack: %s", err) return err } } defer func() { if *flAutoRemove { if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil { fmt.Fprintf(cli.err, "Error deleting container: %s\n", err) } } }() //start the container if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil { return err } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { if err := cli.monitorTtySize(createResponse.ID, false); err != nil { fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) } } if errCh != nil { if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return err } } // Detached mode: wait for the id to be displayed and return. if !config.AttachStdout && !config.AttachStderr { // Detached mode <-waitDisplayID return nil } var status int // Attached mode if *flAutoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil { return err } if _, status, err = getExitCode(cli, createResponse.ID); err != nil { return err } } else { // No Autoremove: Simply retrieve the exit code if !config.Tty { // In non-TTY mode, we can't detach, so we must wait for container exit if status, err = waitForExit(cli, createResponse.ID); err != nil { return err } } else { // In TTY mode, there is a race: if the process dies too slowly, the state could // be updated after the getExitCode call and result in the wrong exit code being reported if _, status, err = getExitCode(cli, createResponse.ID); err != nil { return err } } } if status != 0 { return StatusError{StatusCode: status} } return nil }
// CmdStart starts one or more containers. // // Usage: docker start [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStart(args ...string) error { cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true) attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals") openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) if *attach || *openStdin { // We're going to attach to a container. // 1. Ensure we only have one container. if cmd.NArg() > 1 { return fmt.Errorf("You cannot start and attach multiple containers at once.") } // 2. Attach to the container. containerID := cmd.Arg(0) c, err := cli.client.ContainerInspect(containerID) if err != nil { return err } if !c.Config.Tty { sigc := cli.forwardAllSignals(containerID) defer signal.StopCatch(sigc) } options := types.ContainerAttachOptions{ ContainerID: containerID, Stream: true, Stdin: *openStdin && c.Config.OpenStdin, Stdout: true, Stderr: true, } var in io.ReadCloser if options.Stdin { in = cli.in } resp, err := cli.client.ContainerAttach(options) if err != nil { return err } defer resp.Close() cErr := promise.Go(func() error { return cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp) }) // 3. Start the container. if err := cli.client.ContainerStart(containerID); err != nil { return err } // 4. Wait for attachment to break. if c.Config.Tty && cli.isTerminalOut { if err := cli.monitorTtySize(containerID, false); err != nil { fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) } } if attchErr := <-cErr; attchErr != nil { return attchErr } _, status, err := getExitCode(cli, containerID) if err != nil { return err } if status != 0 { return Cli.StatusError{StatusCode: status} } } else { // We're not going to attach to anything. // Start as many containers as we want. return cli.startContainersWithoutAttachments(cmd.Args()) } return nil }
func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error { stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In() client := dockerCli.Client() // TODO: pass this as an argument cmdPath := "run" var ( flAttach *opttypes.ListOpts ErrConflictAttachDetach = errors.New("Conflicting options: -a and -d") ErrConflictRestartPolicyAndAutoRemove = errors.New("Conflicting options: --restart and --rm") ) config, hostConfig, networkingConfig, err := parse(flags, copts) // just in case the parse does not exit if err != nil { reportError(stderr, cmdPath, err.Error(), true) return cli.StatusError{StatusCode: 125} } if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() { return ErrConflictRestartPolicyAndAutoRemove } if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 { fmt.Fprintln(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.") } if len(hostConfig.DNS) > 0 { // check the DNS settings passed via --dns against // localhost regexp to warn if they are trying to // set a DNS to a localhost address for _, dnsIP := range hostConfig.DNS { if dns.IsLocalhost(dnsIP) { fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) break } } } config.ArgsEscaped = false if !opts.detach { if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil { return err } } else { if fl := flags.Lookup("attach"); fl != nil { flAttach = fl.Value.(*opttypes.ListOpts) if flAttach.Len() != 0 { return ErrConflictAttachDetach } } config.AttachStdin = false config.AttachStdout = false config.AttachStderr = false config.StdinOnce = false } // Disable sigProxy when in TTY mode if config.Tty { opts.sigProxy = false } // Telling the Windows daemon the initial size of the tty during start makes // a far better user experience rather than relying on subsequent resizes // to cause things to catch up. if runtime.GOOS == "windows" { hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize() } ctx, cancelFun := context.WithCancel(context.Background()) createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name) if err != nil { reportError(stderr, cmdPath, err.Error(), true) return runStartContainerErr(err) } if opts.sigProxy { sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID) defer signal.StopCatch(sigc) } var ( waitDisplayID chan struct{} errCh chan error ) if !config.AttachStdout && !config.AttachStderr { // Make this asynchronous to allow the client to write to stdin before having to read the ID waitDisplayID = make(chan struct{}) go func() { defer close(waitDisplayID) fmt.Fprintln(stdout, createResponse.ID) }() } attach := config.AttachStdin || config.AttachStdout || config.AttachStderr if attach { var ( out, cerr io.Writer in io.ReadCloser ) if config.AttachStdin { in = stdin } if config.AttachStdout { out = stdout } if config.AttachStderr { if config.Tty { cerr = stdout } else { cerr = stderr } } if opts.detachKeys != "" { dockerCli.ConfigFile().DetachKeys = opts.detachKeys } options := types.ContainerAttachOptions{ Stream: true, Stdin: config.AttachStdin, Stdout: config.AttachStdout, Stderr: config.AttachStderr, DetachKeys: dockerCli.ConfigFile().DetachKeys, } resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options) if errAttach != nil && errAttach != httputil.ErrPersistEOF { // ContainerAttach returns an ErrPersistEOF (connection closed) // means server met an error and put it in Hijacked connection // keep the error and read detailed error message from hijacked connection later return errAttach } defer resp.Close() errCh = promise.Go(func() error { if errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp); errHijack != nil { return errHijack } return errAttach }) } statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, hostConfig.AutoRemove) //start the container if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil { // If we have holdHijackedConnection, we should notify // holdHijackedConnection we are going to exit and wait // to avoid the terminal are not restored. if attach { cancelFun() <-errCh } reportError(stderr, cmdPath, err.Error(), false) if hostConfig.AutoRemove { // wait container to be removed <-statusChan } return runStartContainerErr(err) } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() { if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil { fmt.Fprintln(stderr, "Error monitoring TTY size:", err) } } if errCh != nil { if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return err } } // Detached mode: wait for the id to be displayed and return. if !config.AttachStdout && !config.AttachStderr { // Detached mode <-waitDisplayID return nil } status := <-statusChan if status != 0 { return cli.StatusError{StatusCode: status} } return nil }
// CmdExec runs a command in a running container. // // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] func (cli *DockerCli) CmdExec(args ...string) error { cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true) detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container") execConfig, err := ParseExec(cmd, args) container := cmd.Arg(0) // just in case the ParseExec does not exit if container == "" || err != nil { return Cli.StatusError{StatusCode: 1} } if *detachKeys != "" { cli.configFile.DetachKeys = *detachKeys } // Send client escape keys execConfig.DetachKeys = cli.configFile.DetachKeys ctx := context.Background() response, err := cli.client.ContainerExecCreate(ctx, container, *execConfig) if err != nil { return err } execID := response.ID if execID == "" { fmt.Fprintf(cli.out, "exec ID empty") return nil } //Temp struct for execStart so that we don't need to transfer all the execConfig if !execConfig.Detach { if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { return err } } else { execStartCheck := types.ExecStartCheck{ Detach: execConfig.Detach, Tty: execConfig.Tty, } if err := cli.client.ContainerExecStart(ctx, execID, execStartCheck); err != nil { return err } // For now don't print this - wait for when we support exec wait() // fmt.Fprintf(cli.out, "%s\n", execID) return nil } // Interactive exec requested. var ( out, stderr io.Writer in io.ReadCloser errCh chan error ) if execConfig.AttachStdin { in = cli.in } if execConfig.AttachStdout { out = cli.out } if execConfig.AttachStderr { if execConfig.Tty { stderr = cli.out } else { stderr = cli.err } } resp, err := cli.client.ContainerExecAttach(ctx, execID, *execConfig) if err != nil { return err } defer resp.Close() errCh = promise.Go(func() error { return cli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp) }) if execConfig.Tty && cli.isTerminalIn { if err := cli.MonitorTtySize(ctx, execID, true); err != nil { fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) } } if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return err } var status int if _, status, err = cli.getExecExitCode(ctx, execID); err != nil { return err } if status != 0 { return Cli.StatusError{StatusCode: status} } return nil }
// FIXME: this should be private, and every outside subsystem // should go through the "container_attach" job. But that would require // that job to be properly documented, as well as the relationship between // Attach and ContainerAttach. // // This method is in use by builder/builder.go. func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var ( cStdout, cStderr io.ReadCloser nJobs int errors = make(chan error, 3) ) // Connect stdin of container to the http conn. if stdin != nil && openStdin { nJobs++ // Get the stdin pipe. if cStdin, err := streamConfig.StdinPipe(); err != nil { errors <- err } else { go func() { log.Debugf("attach: stdin: begin") defer log.Debugf("attach: stdin: end") // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if stdinOnce && !tty { defer cStdin.Close() } else { defer func() { if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } }() } if tty { _, err = utils.CopyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stdin: %s", err) } errors <- err }() } } if stdout != nil { nJobs++ // Get a reader end of a pipe that is attached as stdout to the container. if p, err := streamConfig.StdoutPipe(); err != nil { errors <- err } else { cStdout = p go func() { log.Debugf("attach: stdout: begin") defer log.Debugf("attach: stdout: end") // If we are in StdinOnce mode, then close stdin if stdinOnce && stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } _, err := io.Copy(stdout, cStdout) if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stdout: %s", err) } errors <- err }() } } else { // Point stdout of container to a no-op writer. go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStdout, err := streamConfig.StdoutPipe(); err != nil { log.Errorf("attach: stdout pipe: %s", err) } else { io.Copy(&ioutils.NopWriter{}, cStdout) } }() } if stderr != nil { nJobs++ if p, err := streamConfig.StderrPipe(); err != nil { errors <- err } else { cStderr = p go func() { log.Debugf("attach: stderr: begin") defer log.Debugf("attach: stderr: end") // If we are in StdinOnce mode, then close stdin // Why are we closing stdin here and above while handling stdout? if stdinOnce && stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } _, err := io.Copy(stderr, cStderr) if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stderr: %s", err) } errors <- err }() } } else { // Point stderr at a no-op writer. go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStderr, err := streamConfig.StderrPipe(); err != nil { log.Errorf("attach: stdout pipe: %s", err) } else { io.Copy(&ioutils.NopWriter{}, cStderr) } }() } return promise.Go(func() error { defer func() { if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } }() // FIXME: how to clean up the stdin goroutine without the unwanted side effect // of closing the passed stdin? Add an intermediary io.Pipe? for i := 0; i < nJobs; i++ { log.Debugf("attach: waiting for job %d/%d", i+1, nJobs) if err := <-errors; err != nil { log.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err) return err } log.Debugf("attach: job %d completed successfully", i+1) } log.Debugf("attach: all jobs completed successfully") return nil }) }
// CopyFileWithTar emulates the behavior of the 'cp' command-line // for a single file. It copies a regular file from path `src` to // path `dst`, and preserves all its metadata. func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst) srcSt, err := os.Stat(src) if err != nil { return err } if srcSt.IsDir() { return fmt.Errorf("Can't copy a directory") } // Clean up the trailing slash. This must be done in an operating // system specific manner. if dst[len(dst)-1] == os.PathSeparator { dst = filepath.Join(dst, filepath.Base(src)) } // Create the holding directory if necessary if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil { return err } r, w := io.Pipe() errC := promise.Go(func() error { defer w.Close() srcF, err := os.Open(src) if err != nil { return err } defer srcF.Close() hdr, err := tar.FileInfoHeader(srcSt, "") if err != nil { return err } hdr.Name = filepath.Base(dst) hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(archiver.UIDMaps, archiver.GIDMaps) if err != nil { return err } // only perform mapping if the file being copied isn't already owned by the // uid or gid of the remapped root in the container if remappedRootUID != hdr.Uid { xUID, err := idtools.ToHost(hdr.Uid, archiver.UIDMaps) if err != nil { return err } hdr.Uid = xUID } if remappedRootGID != hdr.Gid { xGID, err := idtools.ToHost(hdr.Gid, archiver.GIDMaps) if err != nil { return err } hdr.Gid = xGID } tw := tar.NewWriter(w) defer tw.Close() if err := tw.WriteHeader(hdr); err != nil { return err } if _, err := io.Copy(tw, srcF); err != nil { return err } return nil }) defer func() { if er := <-errC; err != nil { err = er } }() err = archiver.Untar(r, filepath.Dir(dst), nil) if err != nil { r.CloseWithError(err) } return err }
// CmdExec runs a command in a running container. // // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] func (cli *DockerCli) CmdExec(args ...string) error { cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true) execConfig, err := runconfig.ParseExec(cmd, args) // just in case the ParseExec does not exit if execConfig.Container == "" || err != nil { return Cli.StatusError{StatusCode: 1} } serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil) if err != nil { return err } defer serverResp.body.Close() var response types.ContainerExecCreateResponse if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil { return err } execID := response.ID if execID == "" { fmt.Fprintf(cli.out, "exec ID empty") return nil } //Temp struct for execStart so that we don't need to transfer all the execConfig execStartCheck := &types.ExecStartCheck{ Detach: execConfig.Detach, Tty: execConfig.Tty, } if !execConfig.Detach { if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { return err } } else { if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil { return err } // For now don't print this - wait for when we support exec wait() // fmt.Fprintf(cli.out, "%s\n", execID) return nil } // Interactive exec requested. var ( out, stderr io.Writer in io.ReadCloser hijacked = make(chan io.Closer) errCh chan error ) // Block the return until the chan gets closed defer func() { logrus.Debugf("End of CmdExec(), Waiting for hijack to finish.") if _, ok := <-hijacked; ok { fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)") } }() if execConfig.AttachStdin { in = cli.in } if execConfig.AttachStdout { out = cli.out } if execConfig.AttachStderr { if execConfig.Tty { stderr = cli.out } else { stderr = cli.err } } errCh = promise.Go(func() error { return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig) }) // Acknowledge the hijack before starting select { case closer := <-hijacked: // Make sure that hijack gets closed when returning. (result // in closing hijack chan and freeing server's goroutines. if closer != nil { defer closer.Close() } case err := <-errCh: if err != nil { logrus.Debugf("Error hijack: %s", err) return err } } if execConfig.Tty && cli.isTerminalIn { if err := cli.monitorTtySize(execID, true); err != nil { fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) } } if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return err } var status int if _, status, err = getExecExitCode(cli, execID); err != nil { return err } if status != 0 { return Cli.StatusError{StatusCode: status} } return nil }
// CmdStart starts one or more stopped containers. // // Usage: docker start [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStart(args ...string) error { var ( cErr chan error tty bool cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Start one or more stopped containers", true) attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals") openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") ) cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) if *attach || *openStdin { if cmd.NArg() > 1 { return fmt.Errorf("You cannot start and attach multiple containers at once.") } stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil) if err != nil { return err } var c types.ContainerJSON if err := json.NewDecoder(stream).Decode(&c); err != nil { return err } tty = c.Config.Tty if !tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) defer signal.StopCatch(sigc) } var in io.ReadCloser v := url.Values{} v.Set("stream", "1") if *openStdin && c.Config.OpenStdin { v.Set("stdin", "1") in = cli.in } v.Set("stdout", "1") v.Set("stderr", "1") hijacked := make(chan io.Closer) // Block the return until the chan gets closed defer func() { logrus.Debugf("CmdStart() returned, defer waiting for hijack to finish.") if _, ok := <-hijacked; ok { fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)") } cli.in.Close() }() cErr = promise.Go(func() error { return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil) }) // Acknowledge the hijack before starting select { case closer := <-hijacked: // Make sure that the hijack gets closed when returning (results // in closing the hijack chan and freeing server's goroutines) if closer != nil { defer closer.Close() } case err := <-cErr: if err != nil { return err } } } var encounteredError error var errNames []string for _, name := range cmd.Args() { _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, nil)) if err != nil { if !*attach && !*openStdin { // attach and openStdin is false means it could be starting multiple containers // when a container start failed, show the error message and start next fmt.Fprintf(cli.err, "%s\n", err) errNames = append(errNames, name) } else { encounteredError = err } } else { if !*attach && !*openStdin { fmt.Fprintf(cli.out, "%s\n", name) } } } if len(errNames) > 0 { encounteredError = fmt.Errorf("Error: failed to start containers: %v", errNames) } if encounteredError != nil { return encounteredError } if *openStdin || *attach { if tty && cli.isTerminalOut { if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) } } if attchErr := <-cErr; attchErr != nil { return attchErr } _, status, err := getExitCode(cli, cmd.Arg(0)) if err != nil { return err } if status != 0 { return StatusError{StatusCode: status} } } return nil }
func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, execCmd []string) error { execConfig, err := parseExec(opts, container, execCmd) // just in case the ParseExec does not exit if container == "" || err != nil { return cli.StatusError{StatusCode: 1} } if opts.detachKeys != "" { dockerCli.ConfigFile().DetachKeys = opts.detachKeys } // Send client escape keys execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys ctx := context.Background() response, err := dockerCli.Client().ContainerExecCreate(ctx, container, *execConfig) if err != nil { return err } execID := response.ID if execID == "" { fmt.Fprintf(dockerCli.Out(), "exec ID empty") return nil } //Temp struct for execStart so that we don't need to transfer all the execConfig if !execConfig.Detach { if err := dockerCli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { return err } } else { execStartCheck := types.ExecStartCheck{ Detach: execConfig.Detach, Tty: execConfig.Tty, } if err := dockerCli.Client().ContainerExecStart(ctx, execID, execStartCheck); err != nil { return err } // For now don't print this - wait for when we support exec wait() // fmt.Fprintf(dockerCli.Out(), "%s\n", execID) return nil } // Interactive exec requested. var ( out, stderr io.Writer in io.ReadCloser errCh chan error ) if execConfig.AttachStdin { in = dockerCli.In() } if execConfig.AttachStdout { out = dockerCli.Out() } if execConfig.AttachStderr { if execConfig.Tty { stderr = dockerCli.Out() } else { stderr = dockerCli.Err() } } resp, err := dockerCli.Client().ContainerExecAttach(ctx, execID, *execConfig) if err != nil { return err } defer resp.Close() errCh = promise.Go(func() error { return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp) }) if execConfig.Tty && dockerCli.IsTerminalIn() { if err := dockerCli.MonitorTtySize(ctx, execID, true); err != nil { fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) } } if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return err } var status int if _, status, err = dockerCli.GetExecExitCode(ctx, execID); err != nil { return err } if status != 0 { return cli.StatusError{StatusCode: status} } return nil }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error { defer func() { if started != nil { close(started) } }() params, err := cli.encodeData(data) if err != nil { return err } req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params) if err != nil { return err } req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.Header.Set("Content-Type", "plain/text") req.Host = cli.addr dial, err := cli.dial() if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") } return err } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() // Server hijacks the connection, error 'connection closed' expected clientconn.Do(req) rwc, br := clientconn.Hijack() defer rwc.Close() if started != nil { started <- rwc } var receiveStdout chan error var oldState *term.State if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { oldState, err = term.SetRawTerminal(cli.inFd) if err != nil { return err } defer term.RestoreTerminal(cli.inFd, oldState) } if stdout != nil || stderr != nil { receiveStdout = promise.Go(func() (err error) { defer func() { if in != nil { if setRawTerminal && cli.isTerminalIn { term.RestoreTerminal(cli.inFd, oldState) } // For some reason this Close call blocks on darwin.. // As the client exists right after, simply discard the close // until we find a better solution. if runtime.GOOS != "darwin" { in.Close() } } }() // When TTY is ON, use regular copy if setRawTerminal && stdout != nil { _, err = io.Copy(stdout, br) } else { _, err = stdcopy.StdCopy(stdout, stderr, br) } log.Debugf("[hijack] End of stdout") return err }) } sendStdin := promise.Go(func() error { if in != nil { io.Copy(rwc, in) log.Debugf("[hijack] End of stdin") } if tcpc, ok := rwc.(*net.TCPConn); ok { if err := tcpc.CloseWrite(); err != nil { log.Debugf("Couldn't send EOF: %s", err) } } else if unixc, ok := rwc.(*net.UnixConn); ok { if err := unixc.CloseWrite(); err != nil { log.Debugf("Couldn't send EOF: %s", err) } } // Discard errors due to pipe interruption return nil }) if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { log.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminalIn { if err := <-sendStdin; err != nil { log.Debugf("Error sendStdin: %s", err) return err } } return nil }
// CmdRun runs a command in a new container. // // Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] func (cli *DockerCli) CmdRun(args ...string) error { cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true) addTrustedFlags(cmd, true) // These are flags not stored in Config/HostConfig var ( flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process") flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container") flAttach *opts.ListOpts ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") ) config, hostConfig, cmd, err := runconfig.Parse(cmd, args) // just in case the Parse does not exit if err != nil { cmd.ReportError(err.Error(), true) os.Exit(125) } if hostConfig.OomKillDisable && hostConfig.Memory == 0 { fmt.Fprintf(cli.err, "WARNING: Dangerous only disable the OOM Killer on containers but not set the '-m/--memory' option\n") } if len(hostConfig.DNS) > 0 { // check the DNS settings passed via --dns against // localhost regexp to warn if they are trying to // set a DNS to a localhost address for _, dnsIP := range hostConfig.DNS { if dns.IsLocalhost(dnsIP) { fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) break } } } if config.Image == "" { cmd.Usage() return nil } config.ArgsEscaped = false if !*flDetach { if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { return err } } else { if fl := cmd.Lookup("-attach"); fl != nil { flAttach = fl.Value.(*opts.ListOpts) if flAttach.Len() != 0 { return ErrConflictAttachDetach } } if *flAutoRemove { return ErrConflictDetachAutoRemove } config.AttachStdin = false config.AttachStdout = false config.AttachStderr = false config.StdinOnce = false } // Disable flSigProxy when in TTY mode sigProxy := *flSigProxy if config.Tty { sigProxy = false } // Telling the Windows daemon the initial size of the tty during start makes // a far better user experience rather than relying on subsequent resizes // to cause things to catch up. if runtime.GOOS == "windows" { hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize() } createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) if err != nil { cmd.ReportError(err.Error(), true) return runStartContainerErr(err) } if sigProxy { sigc := cli.forwardAllSignals(createResponse.ID) defer signal.StopCatch(sigc) } var ( waitDisplayID chan struct{} errCh chan error ) if !config.AttachStdout && !config.AttachStderr { // Make this asynchronous to allow the client to write to stdin before having to read the ID waitDisplayID = make(chan struct{}) go func() { defer close(waitDisplayID) fmt.Fprintf(cli.out, "%s\n", createResponse.ID) }() } if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) { return ErrConflictRestartPolicyAndAutoRemove } if config.AttachStdin || config.AttachStdout || config.AttachStderr { var ( out, stderr io.Writer in io.ReadCloser ) if config.AttachStdin { in = cli.in } if config.AttachStdout { out = cli.out } if config.AttachStderr { if config.Tty { stderr = cli.out } else { stderr = cli.err } } if *flDetachKeys != "" { cli.configFile.DetachKeys = *flDetachKeys } options := types.ContainerAttachOptions{ ContainerID: createResponse.ID, Stream: true, Stdin: config.AttachStdin, Stdout: config.AttachStdout, Stderr: config.AttachStderr, DetachKeys: cli.configFile.DetachKeys, } resp, err := cli.client.ContainerAttach(options) if err != nil { return err } errCh = promise.Go(func() error { return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp) }) } defer func() { if *flAutoRemove { options := types.ContainerRemoveOptions{ ContainerID: createResponse.ID, RemoveVolumes: true, } if err := cli.client.ContainerRemove(options); err != nil { fmt.Fprintf(cli.err, "Error deleting container: %s\n", err) } } }() //start the container if err := cli.client.ContainerStart(createResponse.ID); err != nil { cmd.ReportError(err.Error(), false) return runStartContainerErr(err) } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { if err := cli.monitorTtySize(createResponse.ID, false); err != nil { fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) } } if errCh != nil { if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return err } } // Detached mode: wait for the id to be displayed and return. if !config.AttachStdout && !config.AttachStderr { // Detached mode <-waitDisplayID return nil } var status int // Attached mode if *flAutoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container if status, err = cli.client.ContainerWait(createResponse.ID); err != nil { return runStartContainerErr(err) } if _, status, err = getExitCode(cli, createResponse.ID); err != nil { return err } } else { // No Autoremove: Simply retrieve the exit code if !config.Tty { // In non-TTY mode, we can't detach, so we must wait for container exit if status, err = cli.client.ContainerWait(createResponse.ID); err != nil { return err } } else { // In TTY mode, there is a race: if the process dies too slowly, the state could // be updated after the getExitCode call and result in the wrong exit code being reported if _, status, err = getExitCode(cli, createResponse.ID); err != nil { return err } } } if status != 0 { return Cli.StatusError{StatusCode: status} } return nil }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error { defer func() { if started != nil { close(started) } }() params, err := cli.encodeData(data) if err != nil { return err } req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), params) if err != nil { return err } // Add CLI Config's HTTP Headers BEFORE we set the Docker headers // then the user can't change OUR headers for k, v := range cli.configFile.HTTPHeaders { req.Header.Set(k, v) } req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")") req.Header.Set("Content-Type", "text/plain") req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "tcp") req.Host = cli.addr dial, err := cli.dial() // When we set up a TCP connection for hijack, there could be long periods // of inactivity (a long running command with no output) that in certain // network setups may cause ECONNTIMEOUT, leaving the client in an unknown // state. Setting TCP KeepAlive on the socket connection will prohibit // ECONNTIMEOUT unless the socket connection truly is broken if tcpConn, ok := dial.(*net.TCPConn); ok { tcpConn.SetKeepAlive(true) tcpConn.SetKeepAlivePeriod(30 * time.Second) } if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") } return err } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() // Server hijacks the connection, error 'connection closed' expected clientconn.Do(req) rwc, br := clientconn.Hijack() defer rwc.Close() if started != nil { started <- rwc } var receiveStdout chan error var oldState *term.State if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { oldState, err = term.SetRawTerminal(cli.inFd) if err != nil { return err } defer term.RestoreTerminal(cli.inFd, oldState) } if stdout != nil || stderr != nil { receiveStdout = promise.Go(func() (err error) { defer func() { if in != nil { if setRawTerminal && cli.isTerminalIn { term.RestoreTerminal(cli.inFd, oldState) } // For some reason this Close call blocks on darwin.. // As the client exists right after, simply discard the close // until we find a better solution. if runtime.GOOS != "darwin" { in.Close() } } }() // When TTY is ON, use regular copy if setRawTerminal && stdout != nil { _, err = io.Copy(stdout, br) } else { _, err = stdcopy.StdCopy(stdout, stderr, br) } logrus.Debugf("[hijack] End of stdout") return err }) } sendStdin := promise.Go(func() error { if in != nil { io.Copy(rwc, in) logrus.Debugf("[hijack] End of stdin") } if conn, ok := rwc.(interface { CloseWrite() error }); ok { if err := conn.CloseWrite(); err != nil { logrus.Debugf("Couldn't send EOF: %s", err) } } // Discard errors due to pipe interruption return nil }) if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { logrus.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminalIn { if err := <-sendStdin; err != nil { logrus.Debugf("Error sendStdin: %s", err) return err } } return nil }
// CmdStart starts one or more containers. // // Usage: docker start [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStart(args ...string) error { cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true) attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals") openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container") cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) ctx, cancelFun := context.WithCancel(context.Background()) if *attach || *openStdin { // We're going to attach to a container. // 1. Ensure we only have one container. if cmd.NArg() > 1 { return fmt.Errorf("You cannot start and attach multiple containers at once.") } // 2. Attach to the container. container := cmd.Arg(0) c, err := cli.client.ContainerInspect(ctx, container) if err != nil { return err } if !c.Config.Tty { sigc := cli.forwardAllSignals(ctx, container) defer signal.StopCatch(sigc) } if *detachKeys != "" { cli.configFile.DetachKeys = *detachKeys } options := types.ContainerAttachOptions{ Stream: true, Stdin: *openStdin && c.Config.OpenStdin, Stdout: true, Stderr: true, DetachKeys: cli.configFile.DetachKeys, } var in io.ReadCloser if options.Stdin { in = cli.in } resp, errAttach := cli.client.ContainerAttach(ctx, container, options) if errAttach != nil && errAttach != httputil.ErrPersistEOF { // ContainerAttach return an ErrPersistEOF (connection closed) // means server met an error and put it in Hijacked connection // keep the error and read detailed error message from hijacked connection return errAttach } defer resp.Close() cErr := promise.Go(func() error { errHijack := cli.holdHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp) if errHijack == nil { return errAttach } return errHijack }) // 3. Start the container. if err := cli.client.ContainerStart(ctx, container); err != nil { cancelFun() <-cErr return err } // 4. Wait for attachment to break. if c.Config.Tty && cli.isTerminalOut { if err := cli.monitorTtySize(ctx, container, false); err != nil { fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) } } if attchErr := <-cErr; attchErr != nil { return attchErr } _, status, err := cli.getExitCode(ctx, container) if err != nil { return err } if status != 0 { return Cli.StatusError{StatusCode: status} } } else { // We're not going to attach to anything. // Start as many containers as we want. return cli.startContainersWithoutAttachments(ctx, cmd.Args()) } return nil }
func runStart(dockerCli *command.DockerCli, opts *startOptions) error { ctx, cancelFun := context.WithCancel(context.Background()) if opts.attach || opts.openStdin { // We're going to attach to a container. // 1. Ensure we only have one container. if len(opts.containers) > 1 { return fmt.Errorf("You cannot start and attach multiple containers at once.") } // 2. Attach to the container. container := opts.containers[0] c, err := dockerCli.Client().ContainerInspect(ctx, container) if err != nil { return err } // We always use c.ID instead of container to maintain consistency during `docker start` if !c.Config.Tty { sigc := ForwardAllSignals(ctx, dockerCli, c.ID) defer signal.StopCatch(sigc) } if opts.detachKeys != "" { dockerCli.ConfigFile().DetachKeys = opts.detachKeys } options := types.ContainerAttachOptions{ Stream: true, Stdin: opts.openStdin && c.Config.OpenStdin, Stdout: true, Stderr: true, DetachKeys: dockerCli.ConfigFile().DetachKeys, } var in io.ReadCloser if options.Stdin { in = dockerCli.In() } resp, errAttach := dockerCli.Client().ContainerAttach(ctx, c.ID, options) if errAttach != nil && errAttach != httputil.ErrPersistEOF { // ContainerAttach return an ErrPersistEOF (connection closed) // means server met an error and already put it in Hijacked connection, // we would keep the error and read the detailed error message from hijacked connection return errAttach } defer resp.Close() cErr := promise.Go(func() error { errHijack := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp) if errHijack == nil { return errAttach } return errHijack }) // 3. We should open a channel for receiving status code of the container // no matter it's detached, removed on daemon side(--rm) or exit normally. statusChan, statusErr := waitExitOrRemoved(dockerCli, context.Background(), c.ID, c.HostConfig.AutoRemove) startOptions := types.ContainerStartOptions{ CheckpointID: opts.checkpoint, } // 4. Start the container. if err := dockerCli.Client().ContainerStart(ctx, c.ID, startOptions); err != nil { cancelFun() <-cErr if c.HostConfig.AutoRemove && statusErr == nil { // wait container to be removed <-statusChan } return err } // 5. Wait for attachment to break. if c.Config.Tty && dockerCli.Out().IsTerminal() { if err := MonitorTtySize(ctx, dockerCli, c.ID, false); err != nil { fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) } } if attchErr := <-cErr; attchErr != nil { return attchErr } if statusErr != nil { return fmt.Errorf("can't get container's exit code: %v", statusErr) } if status := <-statusChan; status != 0 { return cli.StatusError{StatusCode: status} } } else if opts.checkpoint != "" { if len(opts.containers) > 1 { return fmt.Errorf("You cannot restore multiple containers at once.") } container := opts.containers[0] startOptions := types.ContainerStartOptions{ CheckpointID: opts.checkpoint, } return dockerCli.Client().ContainerStart(ctx, container, startOptions) } else { // We're not going to attach to anything. // Start as many containers as we want. return startContainersWithoutAttachments(dockerCli, ctx, opts.containers) } return nil }
func (cli *KraneCli) CmdRun(args ...string) error { // FIXME: just use runconfig.Parse already cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container") // These are flags not stored in Config/HostConfig var ( flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run the container in the background and print the new container ID") flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (even in non-TTY mode). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.") flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") flShip = cmd.String([]string{"ship", "-ship"}, "", "Ship name to deploy at") flAttach *opts.ListOpts ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") ) config, hostConfig, cmd, err := runconfig.Parse(cmd, args, nil) if err != nil { return err } if config.Image == "" || *flShip == "" { cmd.Usage() return nil } if *flDetach { if fl := cmd.Lookup("attach"); fl != nil { flAttach = fl.Value.(*opts.ListOpts) if flAttach.Len() != 0 { return ErrConflictAttachDetach } } if *flAutoRemove { return ErrConflictDetachAutoRemove } config.AttachStdin = false config.AttachStdout = false config.AttachStderr = false config.StdinOnce = false } // Disable flSigProxy when in TTY mode sigProxy := *flSigProxy if config.Tty { sigProxy = false } runResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName, *flShip) if err != nil { return err } if sigProxy { sigc := cli.forwardAllSignals(runResult.Get("Id")) defer signal.StopCatch(sigc) } var ( waitDisplayId chan struct{} errCh chan error ) if !config.AttachStdout && !config.AttachStderr { // Make this asynchronous to allow the client to write to stdin before having to read the ID waitDisplayId = make(chan struct{}) go func() { defer close(waitDisplayId) fmt.Fprintf(cli.out, "%s\n", runResult.Get("Id")) }() } if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") { return ErrConflictRestartPolicyAndAutoRemove } // We need to instantiate the chan because the select needs it. It can // be closed but can't be uninitialized. hijacked := make(chan io.Closer) // Block the return until the chan gets closed defer func() { log.Debugf("End of CmdRun(), Waiting for hijack to finish.") if _, ok := <-hijacked; ok { log.Errorf("Hijack did not finish (chan still open)") } }() if config.AttachStdin || config.AttachStdout || config.AttachStderr { var ( out, stderr io.Writer in io.ReadCloser v = url.Values{} ) v.Set("stream", "1") if config.AttachStdin { v.Set("stdin", "1") in = cli.in } if config.AttachStdout { v.Set("stdout", "1") out = cli.out } if config.AttachStderr { v.Set("stderr", "1") if config.Tty { stderr = cli.out } else { stderr = cli.err } } errCh = promise.Go(func() error { return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil) }) } else { close(hijacked) } // Acknowledge the hijack before starting select { case closer := <-hijacked: // Make sure that the hijack gets closed when returning (results // in closing the hijack chan and freeing server's goroutines) if closer != nil { defer closer.Close() } case err := <-errCh: if err != nil { log.Debugf("Error hijack: %s", err) return err } } //start the container if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", hostConfig, false)); err != nil { return err } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil { log.Errorf("Error monitoring TTY size: %s", err) } } if errCh != nil { if err := <-errCh; err != nil { log.Debugf("Error hijack: %s", err) return err } } // Detached mode: wait for the id to be displayed and return. if !config.AttachStdout && !config.AttachStderr { // Detached mode <-waitDisplayId return nil } var status int // Attached mode if *flAutoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil { return err } if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { return err } if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.Get("Id")+"?v=1", nil, false)); err != nil { return err } } else { // No Autoremove: Simply retrieve the exit code if !config.Tty { // In non-TTY mode, we can't detach, so we must wait for container exit if status, err = waitForExit(cli, runResult.Get("Id")); err != nil { return err } } else { // In TTY mode, there is a race: if the process dies too slowly, the state could // be updated after the getExitCode call and result in the wrong exit code being reported if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { return err } } } if status != 0 { return &utils.StatusError{StatusCode: status} } return nil }