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>
This commit is contained in:
Kir Kolyshkin 2017-11-08 11:04:42 -08:00
parent 1474ec1ecf
commit 01143afe54
1 changed files with 86 additions and 0 deletions

View File

@ -0,0 +1,86 @@
package container
import (
"bytes"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"golang.org/x/net/context"
)
// ExecResult represents a result returned from Exec()
type ExecResult struct {
ExitCode int
outBuffer *bytes.Buffer
errBuffer *bytes.Buffer
}
// Stdout returns stdout output of a command run by Exec()
func (res *ExecResult) Stdout() string {
return res.outBuffer.String()
}
// Stderr returns stderr output of a command run by Exec()
func (res *ExecResult) Stderr() string {
return res.errBuffer.String()
}
// Combined returns combined stdout and stderr output of a command run by Exec()
func (res *ExecResult) Combined() string {
return res.outBuffer.String() + res.errBuffer.String()
}
// Exec executes a command inside a container, returning the result
// containing stdout, stderr, and exit code. Note:
// - this is a synchronous operation;
// - cmd stdin is closed.
func Exec(ctx context.Context, cli client.APIClient, id string, cmd []string) (ExecResult, error) {
// prepare exec
execConfig := types.ExecConfig{
AttachStdout: true,
AttachStderr: true,
Cmd: cmd,
}
cresp, err := cli.ContainerExecCreate(ctx, id, execConfig)
if err != nil {
return ExecResult{}, err
}
execID := cresp.ID
// run it, with stdout/stderr attached
aresp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
if err != nil {
return ExecResult{}, err
}
defer aresp.Close()
// read the output
var outBuf, errBuf bytes.Buffer
outputDone := make(chan error)
go func() {
// StdCopy demultiplexes the stream into two buffers
_, err = stdcopy.StdCopy(&outBuf, &errBuf, aresp.Reader)
outputDone <- err
}()
select {
case err := <-outputDone:
if err != nil {
return ExecResult{}, err
}
break
case <-ctx.Done():
return ExecResult{}, ctx.Err()
}
// get the exit code
iresp, err := cli.ContainerExecInspect(ctx, execID)
if err != nil {
return ExecResult{}, err
}
return ExecResult{ExitCode: iresp.ExitCode, outBuffer: &outBuf, errBuffer: &errBuf}, nil
}