func ExampleDockerClient_AttachContainer() { docker, err := NewDockerClient("unix:///var/run/docker.sock", nil) if err != nil { panic(err) } cID, err := docker.CreateContainer(&ContainerConfig{ Cmd: []string{"echo", "hi"}, Image: "busybox", }, "") if err != nil { panic(err) } done := make(chan struct{}) if body, err := docker.AttachContainer(cID, &AttachOptions{ Stream: true, Stdout: true, }); err != nil { panic(err) } else { go func() { defer body.Close() if _, err := stdcopy.StdCopy(os.Stdout, os.Stderr, body); err != nil { panic(err) } close(done) }() } if err := docker.StartContainer(cID, nil); err != nil { panic(err) } <-done }
// Log forwards container logs to the project configured logger. func (c *Container) Log(ctx context.Context, l logger.Logger, follow bool) error { info, err := c.client.ContainerInspect(ctx, c.container.ID) if err != nil { return err } options := types.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, Follow: follow, Tail: "all", } responseBody, err := c.client.ContainerLogs(ctx, c.container.ID, options) if err != nil { return err } defer responseBody.Close() if info.Config.Tty { _, err = io.Copy(&logger.Wrapper{Logger: l}, responseBody) } else { _, err = stdcopy.StdCopy(&logger.Wrapper{Logger: l}, &logger.Wrapper{Logger: l, Err: true}, responseBody) } logrus.WithFields(logrus.Fields{"Logger": l, "err": err}).Debug("c.client.Logs() returned error") return err }
// Log forwards container logs to the project configured logger. func (c *Container) Log() error { container, err := c.findExisting() if container == nil || err != nil { return err } info, err := c.client.ContainerInspect(context.Background(), container.ID) if err != nil { return err } l := c.service.context.LoggerFactory.Create(c.name) options := types.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, Follow: c.service.context.FollowLog, Tail: "all", } responseBody, err := c.client.ContainerLogs(context.Background(), c.name, options) if err != nil { return err } defer responseBody.Close() if info.Config.Tty { _, err = io.Copy(&logger.Wrapper{Logger: l}, responseBody) } else { _, err = stdcopy.StdCopy(&logger.Wrapper{Logger: l}, &logger.Wrapper{Logger: l, Err: true}, responseBody) } logrus.WithFields(logrus.Fields{"Logger": l, "err": err}).Debug("c.client.Logs() returned error") return err }
func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error { ctx := context.Background() options := types.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, Since: opts.since, Timestamps: opts.timestamps, Follow: opts.follow, Tail: opts.tail, Details: opts.details, } responseBody, err := dockerCli.Client().ContainerLogs(ctx, opts.container, options) if err != nil { return err } defer responseBody.Close() c, err := dockerCli.Client().ContainerInspect(ctx, opts.container) if err != nil { return err } if c.Config.Tty { _, err = io.Copy(dockerCli.Out(), responseBody) } else { _, err = stdcopy.StdCopy(dockerCli.Out(), dockerCli.Err(), responseBody) } return err }
func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error { ctx := context.Background() options := types.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, Since: opts.since, Timestamps: opts.timestamps, Follow: opts.follow, Tail: opts.tail, Details: opts.details, } client := dockerCli.Client() responseBody, err := client.ServiceLogs(ctx, opts.service, options) if err != nil { return err } defer responseBody.Close() resolver := idresolver.New(client, opts.noResolve) stdout := &logWriter{ctx: ctx, opts: opts, r: resolver, w: dockerCli.Out()} stderr := &logWriter{ctx: ctx, opts: opts, r: resolver, w: dockerCli.Err()} // TODO(aluzzardi): Do an io.Copy for services with TTY enabled. _, err = stdcopy.StdCopy(stdout, stderr, responseBody) return err }
func (b *Builder) attachContainer(container string, input io.Reader) (chan error, error) { query := make(url.Values, 4) query.Set("stream", "true") query.Set("stdin", "true") query.Set("stdout", "true") query.Set("stderr", "true") urlPath := fmt.Sprintf("/containers/%s/attach?%s", container, query.Encode()) hijackStarted := make(chan int, 1) hijackErr := make(chan error, 1) // The output from /attach will be a multiplexed stream of stdout and // stderr. We need to use a pipe to copy this output into a stdcopy // de-multiplexer and into the build output. pipeReader, pipeWriter := io.Pipe() go func() { defer pipeReader.Close() stdcopy.StdCopy(b.out, b.out, pipeReader) }() go func() { hijackErr <- b.hijack("POST", urlPath, input, pipeWriter, hijackStarted) }() // Wait for the hijack to succeeed or fail. select { case <-hijackStarted: return hijackErr, nil case err := <-hijackErr: return nil, fmt.Errorf("unable to hijack attach tcp stream: %s", err) } }
func runLogs(dockerCli *client.DockerCli, opts *logsOptions) error { ctx := context.Background() c, err := dockerCli.Client().ContainerInspect(ctx, opts.container) if err != nil { return err } if !validDrivers[c.HostConfig.LogConfig.Type] { return fmt.Errorf("\"logs\" command is supported only for \"json-file\" and \"journald\" logging drivers (got: %s)", c.HostConfig.LogConfig.Type) } options := types.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, Since: opts.since, Timestamps: opts.timestamps, Follow: opts.follow, Tail: opts.tail, Details: opts.details, } responseBody, err := dockerCli.Client().ContainerLogs(ctx, opts.container, options) if err != nil { return err } defer responseBody.Close() if c.Config.Tty { _, err = io.Copy(dockerCli.Out(), responseBody) } else { _, err = stdcopy.StdCopy(dockerCli.Out(), dockerCli.Err(), responseBody) } return err }
func handleStreamResponse(resp *http.Response, streamOptions *streamOptions) error { var err error if !streamOptions.useJSONDecoder && resp.Header.Get("Content-Type") != "application/json" { if streamOptions.setRawTerminal { _, err = io.Copy(streamOptions.stdout, resp.Body) } else { _, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body) } return err } // if we want to get raw json stream, just copy it back to output // without decoding it if streamOptions.rawJSONStream { _, err = io.Copy(streamOptions.stdout, resp.Body) return err } if st, ok := streamOptions.stdout.(interface { io.Writer FD() uintptr IsTerminal() bool }); ok { err = jsonmessage.DisplayJSONMessagesToStream(resp.Body, st, nil) } else { err = jsonmessage.DisplayJSONMessagesStream(resp.Body, streamOptions.stdout, 0, false, nil) } return err }
func (e *engine) runJobNotify(r *Task, client dockerclient.Client) error { name := fmt.Sprintf("drone_build_%d_notify", r.Build.ID) defer func() { client.KillContainer(name, "9") client.RemoveContainer(name, true, true) }() // encode the build payload to write to stdin // when launching the build container in, err := encodeToLegacyFormat(r) if err != nil { log.Errorf("failure to marshal work. %s", err) return err } args := DefaultNotifyArgs args = append(args, "--") args = append(args, string(in)) conf := &dockerclient.ContainerConfig{ Image: DefaultAgent, Entrypoint: DefaultEntrypoint, Cmd: args, Env: e.envs, HostConfig: dockerclient.HostConfig{ Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"}, MemorySwappiness: -1, }, Volumes: map[string]struct{}{ "/var/run/docker.sock": struct{}{}, }, } log.Infof("preparing container %s", name) info, err := docker.Run(client, conf, name) if err != nil { log.Errorf("Error starting notification container %s. %s", name, err) } // for debugging purposes we print a failed notification executions // output to the logs. Otherwise we have no way to troubleshoot failed // notifications. This is temporary code until I've come up with // a better solution. if info != nil && info.State.ExitCode != 0 && log.GetLevel() >= log.InfoLevel { var buf bytes.Buffer rc, err := client.ContainerLogs(name, docker.LogOpts) if err == nil { defer rc.Close() stdcopy.StdCopy(&buf, &buf, io.LimitReader(rc, 50000)) } log.Infof("Notification container %s exited with %d", name, info.State.ExitCode) log.Infoln(buf.String()) } return err }
func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error { if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader([]byte{}) } req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in) if err != nil { return err } req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.URL.Host = cli.addr req.URL.Scheme = cli.scheme if method == "POST" { req.Header.Set("Content-Type", "plain/text") } if headers != nil { for k, v := range headers { req.Header[k] = v } } resp, err := cli.HTTPClient().Do(req) 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 } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 400 { body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } if len(body) == 0 { return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode)) } return fmt.Errorf("Error: %s", bytes.TrimSpace(body)) } if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") { return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut) } if stdout != nil || stderr != nil { // When TTY is ON, use regular copy if setRawTerminal { _, err = io.Copy(stdout, resp.Body) } else { _, err = stdcopy.StdCopy(stdout, stderr, resp.Body) } log.Debugf("[stream] End of stdout") return err } return nil }
// ExecRoot runs a command as root. func (l *LocalCluster) ExecRoot(ctx context.Context, i int, cmd []string) error { execRoot := func(ctx context.Context) error { cfg := types.ExecConfig{ User: "******", Privileged: true, Cmd: cmd, AttachStderr: true, AttachStdout: true, } createResp, err := l.client.ContainerExecCreate(ctx, l.Nodes[i].Container.id, cfg) if err != nil { return err } var outputStream, errorStream bytes.Buffer { resp, err := l.client.ContainerExecAttach(ctx, createResp.ID, cfg) if err != nil { return err } defer resp.Close() ch := make(chan error) go func() { _, err := stdcopy.StdCopy(&outputStream, &errorStream, resp.Reader) ch <- err }() if err := <-ch; err != nil { return err } } { resp, err := l.client.ContainerExecInspect(ctx, createResp.ID) if err != nil { return err } if resp.Running { return errors.Errorf("command still running") } if resp.ExitCode != 0 { return fmt.Errorf("error executing %s:\n%s\n%s", cmd, outputStream.String(), errorStream.String()) } } return nil } return retry(ctx, 3, 10*time.Second, "ExecRoot", matchNone, execRoot) }
// redirectResponseToOutputStream redirect the response stream to stdout and stderr. When tty is true, all stream will // only be redirected to stdout. func (d *kubeDockerClient) redirectResponseToOutputStream(tty bool, outputStream, errorStream io.Writer, resp io.Reader) error { if outputStream == nil { outputStream = ioutil.Discard } if errorStream == nil { errorStream = ioutil.Discard } var err error if tty { _, err = io.Copy(outputStream, resp) } else { _, err = dockerstdcopy.StdCopy(outputStream, errorStream, resp) } return err }
func GetStream(c *gin.Context) { engine_ := context.Engine(c) repo := session.Repo(c) buildn, _ := strconv.Atoi(c.Param("build")) jobn, _ := strconv.Atoi(c.Param("number")) c.Writer.Header().Set("Content-Type", "text/event-stream") build, err := store.GetBuildNumber(c, repo, buildn) if err != nil { log.Debugln("stream cannot get build number.", err) c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, jobn) if err != nil { log.Debugln("stream cannot get job number.", err) c.AbortWithError(404, err) return } node, err := store.GetNode(c, job.NodeID) if err != nil { log.Debugln("stream cannot get node.", err) c.AbortWithError(404, err) return } rc, err := engine_.Stream(build.ID, job.ID, node) if err != nil { c.AbortWithError(404, err) return } defer func() { rc.Close() }() go func() { <-c.Writer.CloseNotify() rc.Close() }() rw := &StreamWriter{c.Writer, 0} stdcopy.StdCopy(rw, rw, rc) }
func (c *Client) hijack(method, path string, setRawTerminal bool, out io.Writer) error { req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) if err != nil { return err } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) req.Header.Set("Content-Type", "plain/text") req.Host = c.addr dial, err := c.Dial() if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Can't connect to 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) // Hijack the connection to read / write rwc, br := clientconn.Hijack() defer rwc.Close() // launch a goroutine to copy the stream // of build output to the writer. errStdout := make(chan error, 1) go func() { var err error if setRawTerminal { _, err = io.Copy(out, br) } else { _, err = stdcopy.StdCopy(out, out, br) } errStdout <- err }() // wait for a response if err := <-errStdout; err != nil { return err } return nil }
func holdHijackedConnection(tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { var err error receiveStdout := make(chan error, 1) if outputStream != nil || errorStream != nil { go func() { // When TTY is ON, use regular copy if tty && outputStream != nil { _, err = io.Copy(outputStream, resp.Reader) } else { _, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader) } logrus.Debugf("[hijack] End of stdout") receiveStdout <- err }() } stdinDone := make(chan struct{}) go func() { if inputStream != nil { io.Copy(resp.Conn, inputStream) logrus.Debugf("[hijack] End of stdin") } if err := resp.CloseWrite(); err != nil { logrus.Debugf("Couldn't send EOF: %s", err) } close(stdinDone) }() select { case err := <-receiveStdout: if err != nil { logrus.Debugf("Error receiveStdout: %s", err) return err } case <-stdinDone: if outputStream != nil || errorStream != nil { if err := <-receiveStdout; err != nil { logrus.Debugf("Error receiveStdout: %s", err) return err } } } return nil }
// CmdLogs fetches the logs of a given container. // // docker logs [OPTIONS] CONTAINER func (cli *DockerCli) CmdLogs(args ...string) error { cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, Cli.DockerCommands["logs"].Description, true) follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output") since := cmd.String([]string{"-since"}, "", "Show logs since timestamp") times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps") details := cmd.Bool([]string{"-details"}, false, "Show extra details provided to logs") tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs") cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) name := cmd.Arg(0) ctx := context.Background() c, err := cli.client.ContainerInspect(ctx, name) if err != nil { return err } if !validDrivers[c.HostConfig.LogConfig.Type] { return fmt.Errorf("\"logs\" command is supported only for \"json-file\" and \"journald\" logging drivers (got: %s)", c.HostConfig.LogConfig.Type) } options := types.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, Since: *since, Timestamps: *times, Follow: *follow, Tail: *tail, Details: *details, } responseBody, err := cli.client.ContainerLogs(ctx, name, options) if err != nil { return err } defer responseBody.Close() if c.Config.Tty { _, err = io.Copy(cli.out, responseBody) } else { _, err = stdcopy.StdCopy(cli.out, cli.err, responseBody) } return err }
func (cli *HyperClient) readStreamOutput(body io.ReadCloser, contentType string, setRawTerminal bool, stdout, stderr io.Writer) error { defer body.Close() if utils.MatchesContentType(contentType, "application/json") { return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut, nil) } if stdout != nil || stderr != nil { // When TTY is ON, use regular copy var err error if setRawTerminal { _, err = io.Copy(stdout, body) } else { _, err = stdcopy.StdCopy(stdout, stderr, body) } return err } return nil }
func containerLogs(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] container, err := controllerManager.Container(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } data, err := controllerManager.ClusterManager().Logs(container, true, true) if err != nil { logger.Errorf("error getting logs for %s: %s", container.ID, err) http.Error(w, err.Error(), http.StatusInternalServerError) return } stdcopy.StdCopy(w, w, data) }
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error { defer body.Close() if api.MatchesContentType(contentType, "application/json") { return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut) } if stdout != nil || stderr != nil { // When TTY is ON, use regular copy var err error if rawTerminal { _, err = io.Copy(stdout, body) } else { _, err = stdcopy.StdCopy(stdout, stderr, body) } logrus.Debugf("[stream] End of stdout") return err } return nil }
func TestContainerLogs(t *testing.T) { client := testDockerClient(t) containerId := "foobar" logOptions := &LogOptions{ Follow: true, Stdout: true, Stderr: true, Timestamps: true, Tail: 10, } logsReader, err := client.ContainerLogs(containerId, logOptions) if err != nil { t.Fatal("cannot read logs from server") } stdoutBuffer := new(bytes.Buffer) stderrBuffer := new(bytes.Buffer) if _, err = stdcopy.StdCopy(stdoutBuffer, stderrBuffer, logsReader); err != nil { t.Fatal("cannot read logs from logs reader") } stdoutLogs := strings.TrimSpace(stdoutBuffer.String()) stderrLogs := strings.TrimSpace(stderrBuffer.String()) stdoutLogLines := strings.Split(stdoutLogs, "\n") stderrLogLines := strings.Split(stderrLogs, "\n") if len(stdoutLogLines) != 5 { t.Fatalf("wrong number of stdout logs: len=%d", len(stdoutLogLines)) } if len(stderrLogLines) != 5 { t.Fatalf("wrong number of stderr logs: len=%d", len(stdoutLogLines)) } for i, line := range stdoutLogLines { expectedSuffix := fmt.Sprintf("Z line %d", 41+2*i) if !strings.HasSuffix(line, expectedSuffix) { t.Fatalf("expected stdout log line \"%s\" to end with \"%s\"", line, expectedSuffix) } } for i, line := range stderrLogLines { expectedSuffix := fmt.Sprintf("Z line %d", 40+2*i) if !strings.HasSuffix(line, expectedSuffix) { t.Fatalf("expected stderr log line \"%s\" to end with \"%s\"", line, expectedSuffix) } } }
func (c *Container) Log() error { container, err := c.findExisting() if container == nil || err != nil { return err } info, err := c.client.InspectContainer(container.Id) if info == nil || err != nil { return err } l := c.service.context.LoggerFactory.Create(c.name) output, err := c.client.ContainerLogs(container.Id, &dockerclient.LogOptions{ Follow: true, Stdout: true, Stderr: true, Tail: 10, }) if err != nil { return err } if info.Config.Tty { scanner := bufio.NewScanner(output) for scanner.Scan() { l.Out([]byte(scanner.Text() + "\n")) } return scanner.Err() } else { _, err := stdcopy.StdCopy(&logger.LoggerWrapper{ Logger: l, }, &logger.LoggerWrapper{ Err: true, Logger: l, }, output) return err } return nil }
func handleStreamResponse(resp *http.Response, streamOptions *streamOptions) error { var err error if !streamOptions.useJSONDecoder && resp.Header.Get("Content-Type") != "application/json" { if streamOptions.setRawTerminal { _, err = io.Copy(streamOptions.stdout, resp.Body) } else { _, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body) } return err } // if we want to get raw json stream, just copy it back to output // without decoding it if streamOptions.rawJSONStream { _, err = io.Copy(streamOptions.stdout, resp.Body) return err } dec := json.NewDecoder(resp.Body) for { var m jsonMessage if err := dec.Decode(&m); err == io.EOF { break } else if err != nil { return err } if m.Stream != "" { fmt.Fprint(streamOptions.stdout, m.Stream) } else if m.Progress != "" { fmt.Fprintf(streamOptions.stdout, "%s %s\r", m.Status, m.Progress) } else if m.Error != "" { return errors.New(m.Error) } if m.Status != "" { fmt.Fprintln(streamOptions.stdout, m.Status) } } return nil }
func (s *DockerSuite) TestPostContainersAttach(c *check.C) { testRequires(c, DaemonIsLinux) expectSuccess := func(conn net.Conn, br *bufio.Reader, stream string, tty bool) { defer conn.Close() expected := []byte("success") _, err := conn.Write(expected) c.Assert(err, checker.IsNil) conn.SetReadDeadline(time.Now().Add(time.Second)) lenHeader := 0 if !tty { lenHeader = 8 } actual := make([]byte, len(expected)+lenHeader) _, err = io.ReadFull(br, actual) c.Assert(err, checker.IsNil) if !tty { fdMap := map[string]byte{ "stdin": 0, "stdout": 1, "stderr": 2, } c.Assert(actual[0], checker.Equals, fdMap[stream]) } c.Assert(actual[lenHeader:], checker.DeepEquals, expected, check.Commentf("Attach didn't return the expected data from %s", stream)) } expectTimeout := func(conn net.Conn, br *bufio.Reader, stream string) { defer conn.Close() _, err := conn.Write([]byte{'t'}) c.Assert(err, checker.IsNil) conn.SetReadDeadline(time.Now().Add(time.Second)) actual := make([]byte, 1) _, err = io.ReadFull(br, actual) opErr, ok := err.(*net.OpError) c.Assert(ok, checker.Equals, true, check.Commentf("Error is expected to be *net.OpError, got %v", err)) c.Assert(opErr.Timeout(), checker.Equals, true, check.Commentf("Read from %s is expected to timeout", stream)) } // Create a container that only emits stdout. cid, _ := dockerCmd(c, "run", "-di", "busybox", "cat") cid = strings.TrimSpace(cid) // Attach to the container's stdout stream. conn, br, err := sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain") c.Assert(err, checker.IsNil) // Check if the data from stdout can be received. expectSuccess(conn, br, "stdout", false) // Attach to the container's stderr stream. conn, br, err = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain") c.Assert(err, checker.IsNil) // Since the container only emits stdout, attaching to stderr should return nothing. expectTimeout(conn, br, "stdout") // Test the similar functions of the stderr stream. cid, _ = dockerCmd(c, "run", "-di", "busybox", "/bin/sh", "-c", "cat >&2") cid = strings.TrimSpace(cid) conn, br, err = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain") c.Assert(err, checker.IsNil) expectSuccess(conn, br, "stderr", false) conn, br, err = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain") c.Assert(err, checker.IsNil) expectTimeout(conn, br, "stderr") // Test with tty. cid, _ = dockerCmd(c, "run", "-dit", "busybox", "/bin/sh", "-c", "cat >&2") cid = strings.TrimSpace(cid) // Attach to stdout only. conn, br, err = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain") c.Assert(err, checker.IsNil) expectSuccess(conn, br, "stdout", true) // Attach without stdout stream. conn, br, err = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain") c.Assert(err, checker.IsNil) // Nothing should be received because both the stdout and stderr of the container will be // sent to the client as stdout when tty is enabled. expectTimeout(conn, br, "stdout") // Test the client API // Make sure we don't see "hello" if Logs is false client, err := client.NewEnvClient() c.Assert(err, checker.IsNil) cid, _ = dockerCmd(c, "run", "-di", "busybox", "/bin/sh", "-c", "echo hello; cat") cid = strings.TrimSpace(cid) attachOpts := types.ContainerAttachOptions{ Stream: true, Stdin: true, Stdout: true, } resp, err := client.ContainerAttach(context.Background(), cid, attachOpts) c.Assert(err, checker.IsNil) expectSuccess(resp.Conn, resp.Reader, "stdout", false) // Make sure we do see "hello" if Logs is true attachOpts.Logs = true resp, err = client.ContainerAttach(context.Background(), cid, attachOpts) c.Assert(err, checker.IsNil) defer resp.Conn.Close() resp.Conn.SetReadDeadline(time.Now().Add(time.Second)) _, err = resp.Conn.Write([]byte("success")) c.Assert(err, checker.IsNil) actualStdout := new(bytes.Buffer) actualStderr := new(bytes.Buffer) stdcopy.StdCopy(actualStdout, actualStderr, resp.Reader) c.Assert(actualStdout.Bytes(), checker.DeepEquals, []byte("hello\nsuccess"), check.Commentf("Attach didn't return the expected data from stdout")) }
func (c *Client) stream(method, path string, streamOptions streamOptions) error { if (method == "POST" || method == "PUT") && streamOptions.in == nil { streamOptions.in = bytes.NewReader(nil) } if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() if err != nil { return err } } req, err := http.NewRequest(method, c.getURL(path), streamOptions.in) if err != nil { return err } req.Header.Set("User-Agent", userAgent) if method == "POST" { req.Header.Set("Content-Type", "plain/text") } for key, val := range streamOptions.headers { req.Header.Set(key, val) } var resp *http.Response protocol := c.endpointURL.Scheme address := c.endpointURL.Path if streamOptions.stdout == nil { streamOptions.stdout = ioutil.Discard } if streamOptions.stderr == nil { streamOptions.stderr = ioutil.Discard } if protocol == "unix" { dial, err := net.Dial(protocol, address) if err != nil { return err } defer dial.Close() breader := bufio.NewReader(dial) err = req.Write(dial) if err != nil { return err } // ReadResponse may hang if server does not replay if streamOptions.timeout > 0 { dial.SetDeadline(time.Now().Add(streamOptions.timeout)) } if resp, err = http.ReadResponse(breader, req); err != nil { // Cancel timeout for future I/O operations if streamOptions.timeout > 0 { dial.SetDeadline(time.Time{}) } if strings.Contains(err.Error(), "connection refused") { return ErrConnectionRefused } return err } } else { if resp, err = c.HTTPClient.Do(req); err != nil { if strings.Contains(err.Error(), "connection refused") { return ErrConnectionRefused } return err } } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 400 { body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } return newError(resp.StatusCode, body) } if streamOptions.useJSONDecoder || resp.Header.Get("Content-Type") == "application/json" { // if we want to get raw json stream, just copy it back to output // without decoding it if streamOptions.rawJSONStream { _, err = io.Copy(streamOptions.stdout, resp.Body) return err } dec := json.NewDecoder(resp.Body) for { var m jsonMessage if err := dec.Decode(&m); err == io.EOF { break } else if err != nil { return err } if m.Stream != "" { fmt.Fprint(streamOptions.stdout, m.Stream) } else if m.Progress != "" { fmt.Fprintf(streamOptions.stdout, "%s %s\r", m.Status, m.Progress) } else if m.Error != "" { return errors.New(m.Error) } if m.Status != "" { fmt.Fprintln(streamOptions.stdout, m.Status) } } } else { if streamOptions.setRawTerminal { _, err = io.Copy(streamOptions.stdout, resp.Body) } else { _, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body) } return err } return nil }
func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error { if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() if err != nil { return err } } var params io.Reader if hijackOptions.data != nil { buf, err := json.Marshal(hijackOptions.data) if err != nil { return err } params = bytes.NewBuffer(buf) } if hijackOptions.stdout == nil { hijackOptions.stdout = ioutil.Discard } if hijackOptions.stderr == nil { hijackOptions.stderr = ioutil.Discard } req, err := http.NewRequest(method, c.getURL(path), params) if err != nil { return err } req.Header.Set("Content-Type", "plain/text") protocol := c.endpointURL.Scheme address := c.endpointURL.Path if protocol != "unix" { protocol = "tcp" address = c.endpointURL.Host } var dial net.Conn if c.TLSConfig != nil && protocol != "unix" { dial, err = tlsDial(protocol, address, c.TLSConfig) if err != nil { return err } } else { dial, err = net.Dial(protocol, address) if err != nil { return err } } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() clientconn.Do(req) if hijackOptions.success != nil { hijackOptions.success <- struct{}{} <-hijackOptions.success } rwc, br := clientconn.Hijack() defer rwc.Close() errChanOut := make(chan error, 1) errChanIn := make(chan error, 1) exit := make(chan bool) go func() { defer close(exit) defer close(errChanOut) var err error if hijackOptions.setRawTerminal { // When TTY is ON, use regular copy _, err = io.Copy(hijackOptions.stdout, br) } else { _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br) } errChanOut <- err }() go func() { if hijackOptions.in != nil { _, err := io.Copy(rwc, hijackOptions.in) errChanIn <- err } rwc.(interface { CloseWrite() error }).CloseWrite() }() <-exit select { case err = <-errChanIn: return err case err = <-errChanOut: return err } }
func (cli *Client) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}, hostname string) 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", utils.APIVERSION, path), params) if err != nil { return err } req.Header.Set("User-Agent", "Hyper-Client/"+utils.VERSION) 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() if err != nil { return err } // 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 ErrConnectionRefused } return err } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() // Server hijacks the connection, error 'connection closed' expected _, err = clientconn.Do(req) if err != nil { fmt.Printf("Client DO: %s\n", err.Error()) } rwc, br := clientconn.Hijack() defer rwc.Close() if started != nil { started <- rwc } var ( receiveStdout chan error ) if in != nil && setRawTerminal { // fmt.Printf("In the Raw Terminal!!!\n") } if stdout != nil || stderr != nil { receiveStdout = promise.Go(func() (err error) { defer func() { if in != nil { if setRawTerminal { } } }() if !setRawTerminal { _, err = stdcopy.StdCopy(stdout, stderr, br) } else { _, err = io.Copy(stdout, br) } // fmt.Printf("[hijack] End of stdout\n") return err }) } go func() { if in != nil { io.Copy(rwc, in) // fmt.Printf("[hijack] End of stdin\n") } if conn, ok := rwc.(interface { CloseWrite() error }); ok { if err := conn.CloseWrite(); err != nil { fmt.Printf("Couldn't send EOF: %s", err.Error()) } } }() if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { fmt.Printf("Error receiveStdout: %s\n", err.Error()) return err } } 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 }
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 = utils.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 := utils.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 }
func (e *engine) runJob(c context.Context, r *Task, updater *updater, client dockerclient.Client) error { name := fmt.Sprintf("drone_build_%d_job_%d", r.Build.ID, r.Job.ID) defer func() { if r.Job.Status == model.StatusRunning { r.Job.Status = model.StatusError r.Job.Finished = time.Now().UTC().Unix() r.Job.ExitCode = 255 } if r.Job.Status == model.StatusPending { r.Job.Status = model.StatusError r.Job.Started = time.Now().UTC().Unix() r.Job.Finished = time.Now().UTC().Unix() r.Job.ExitCode = 255 } updater.SetJob(c, r) client.KillContainer(name, "9") client.RemoveContainer(name, true, true) }() // marks the task as running r.Job.Status = model.StatusRunning r.Job.Started = time.Now().UTC().Unix() // encode the build payload to write to stdin // when launching the build container in, err := encodeToLegacyFormat(r) if err != nil { log.Errorf("failure to marshal work. %s", err) return err } // CREATE AND START BUILD args := DefaultBuildArgs if r.Build.Event == model.EventPull { args = DefaultPullRequestArgs } args = append(args, "--") args = append(args, string(in)) conf := &dockerclient.ContainerConfig{ Image: DefaultAgent, Entrypoint: DefaultEntrypoint, Cmd: args, Env: e.envs, HostConfig: dockerclient.HostConfig{ Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"}, }, Volumes: map[string]struct{}{ "/var/run/docker.sock": struct{}{}, }, } log.Infof("preparing container %s", name) client.PullImage(conf.Image, nil) _, err = docker.RunDaemon(client, conf, name) if err != nil { log.Errorf("error starting build container. %s", err) return err } // UPDATE STATUS err = updater.SetJob(c, r) if err != nil { log.Errorf("error updating job status as running. %s", err) return err } // WAIT FOR OUTPUT info, builderr := docker.Wait(client, name) switch { case info.State.ExitCode == 128: r.Job.ExitCode = info.State.ExitCode r.Job.Status = model.StatusKilled case info.State.ExitCode == 130: r.Job.ExitCode = info.State.ExitCode r.Job.Status = model.StatusKilled case builderr != nil: r.Job.Status = model.StatusError case info.State.ExitCode != 0: r.Job.ExitCode = info.State.ExitCode r.Job.Status = model.StatusFailure default: r.Job.Status = model.StatusSuccess } // send the logs to the datastore var buf bytes.Buffer rc, err := client.ContainerLogs(name, docker.LogOpts) if err != nil && builderr != nil { buf.WriteString("Error launching build") buf.WriteString(builderr.Error()) } else if err != nil { buf.WriteString("Error launching build") buf.WriteString(err.Error()) log.Errorf("error opening connection to logs. %s", err) return err } else { defer rc.Close() stdcopy.StdCopy(&buf, &buf, io.LimitReader(rc, 5000000)) } // update the task in the datastore r.Job.Finished = time.Now().UTC().Unix() err = updater.SetJob(c, r) if err != nil { log.Errorf("error updating job after completion. %s", err) return err } err = updater.SetLogs(c, r, ioutil.NopCloser(&buf)) if err != nil { log.Errorf("error updating logs. %s", err) return err } log.Debugf("completed job %d with status %s.", r.Job.ID, r.Job.Status) return nil }
func (cli *DockerCli) holdHijackedConnection(setRawTerminal bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { var ( err error oldState *term.State ) if inputStream != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { oldState, err = term.SetRawTerminal(cli.inFd) if err != nil { return err } defer term.RestoreTerminal(cli.inFd, oldState) } receiveStdout := make(chan error, 1) if outputStream != nil || errorStream != nil { go func() { defer func() { if inputStream != nil { if setRawTerminal && cli.isTerminalIn { term.RestoreTerminal(cli.inFd, oldState) } inputStream.Close() } }() // When TTY is ON, use regular copy if setRawTerminal && outputStream != nil { _, err = io.Copy(outputStream, resp.Reader) } else { _, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader) } logrus.Debugf("[hijack] End of stdout") receiveStdout <- err }() } stdinDone := make(chan struct{}) go func() { if inputStream != nil { io.Copy(resp.Conn, inputStream) logrus.Debugf("[hijack] End of stdin") } if err := resp.CloseWrite(); err != nil { logrus.Debugf("Couldn't send EOF: %s", err) } close(stdinDone) }() select { case err := <-receiveStdout: if err != nil { logrus.Debugf("Error receiveStdout: %s", err) return err } case <-stdinDone: if outputStream != nil || errorStream != nil { if err := <-receiveStdout; err != nil { logrus.Debugf("Error receiveStdout: %s", err) return err } } } return nil }