Browse code

integration: add container.Exec()

Some test cases might need an ability to execute a command inside a
container (in order to analyse its output and/or exit code). It is a bit
complicated operation to do so using engine API. The function provided
aims to hide this complexity, making exec almost as simple as 'docker
exec'.

NOTE that the exec is synchronous, and command's stdin is closed.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Kir Kolyshkin authored on 2017/11/09 04:04:42
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,86 @@
0
+package container
1
+
2
+import (
3
+	"bytes"
4
+
5
+	"github.com/docker/docker/api/types"
6
+	"github.com/docker/docker/client"
7
+	"github.com/docker/docker/pkg/stdcopy"
8
+	"golang.org/x/net/context"
9
+)
10
+
11
+// ExecResult represents a result returned from Exec()
12
+type ExecResult struct {
13
+	ExitCode  int
14
+	outBuffer *bytes.Buffer
15
+	errBuffer *bytes.Buffer
16
+}
17
+
18
+// Stdout returns stdout output of a command run by Exec()
19
+func (res *ExecResult) Stdout() string {
20
+	return res.outBuffer.String()
21
+}
22
+
23
+// Stderr returns stderr output of a command run by Exec()
24
+func (res *ExecResult) Stderr() string {
25
+	return res.errBuffer.String()
26
+}
27
+
28
+// Combined returns combined stdout and stderr output of a command run by Exec()
29
+func (res *ExecResult) Combined() string {
30
+	return res.outBuffer.String() + res.errBuffer.String()
31
+}
32
+
33
+// Exec executes a command inside a container, returning the result
34
+// containing stdout, stderr, and exit code. Note:
35
+//  - this is a synchronous operation;
36
+//  - cmd stdin is closed.
37
+func Exec(ctx context.Context, cli client.APIClient, id string, cmd []string) (ExecResult, error) {
38
+	// prepare exec
39
+	execConfig := types.ExecConfig{
40
+		AttachStdout: true,
41
+		AttachStderr: true,
42
+		Cmd:          cmd,
43
+	}
44
+	cresp, err := cli.ContainerExecCreate(ctx, id, execConfig)
45
+	if err != nil {
46
+		return ExecResult{}, err
47
+	}
48
+	execID := cresp.ID
49
+
50
+	// run it, with stdout/stderr attached
51
+	aresp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
52
+	if err != nil {
53
+		return ExecResult{}, err
54
+	}
55
+	defer aresp.Close()
56
+
57
+	// read the output
58
+	var outBuf, errBuf bytes.Buffer
59
+	outputDone := make(chan error)
60
+
61
+	go func() {
62
+		// StdCopy demultiplexes the stream into two buffers
63
+		_, err = stdcopy.StdCopy(&outBuf, &errBuf, aresp.Reader)
64
+		outputDone <- err
65
+	}()
66
+
67
+	select {
68
+	case err := <-outputDone:
69
+		if err != nil {
70
+			return ExecResult{}, err
71
+		}
72
+		break
73
+
74
+	case <-ctx.Done():
75
+		return ExecResult{}, ctx.Err()
76
+	}
77
+
78
+	// get the exit code
79
+	iresp, err := cli.ContainerExecInspect(ctx, execID)
80
+	if err != nil {
81
+		return ExecResult{}, err
82
+	}
83
+
84
+	return ExecResult{ExitCode: iresp.ExitCode, outBuffer: &outBuf, errBuffer: &errBuf}, nil
85
+}