blob: d37b6533f6f15b534cf159090526306f4935b4d1 [file] [log] [blame]
package main
import (
"bytes"
"context"
"fmt"
"io"
"os"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
)
// testChunkExecutor executes integration-cli binary.
// image needs to be the worker image itself. testFlags are OR-set of regexp for filtering tests.
type testChunkExecutor func(image string, tests []string) (int64, string, error)
func dryTestChunkExecutor() testChunkExecutor {
return func(image string, tests []string) (int64, string, error) {
return 0, fmt.Sprintf("DRY RUN (image=%q, tests=%v)", image, tests), nil
}
}
// privilegedTestChunkExecutor invokes a privileged container from the worker
// service via bind-mounted API socket so as to execute the test chunk
func privilegedTestChunkExecutor(autoRemove bool) testChunkExecutor {
return func(image string, tests []string) (int64, string, error) {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return 0, "", err
}
// propagate variables from the host (needs to be defined in the compose file)
experimental := os.Getenv("DOCKER_EXPERIMENTAL")
graphdriver := os.Getenv("DOCKER_GRAPHDRIVER")
if graphdriver == "" {
info, err := cli.Info(context.Background())
if err != nil {
return 0, "", err
}
graphdriver = info.Driver
}
// `daemon_dest` is similar to `$DEST` (e.g. `bundles/VERSION/test-integration`)
// but it exists outside of `bundles` so as to make `$DOCKER_GRAPHDRIVER` work.
//
// Without this hack, `$DOCKER_GRAPHDRIVER` fails because of (e.g.) `overlay2 is not supported over overlayfs`
//
// see integration-cli/daemon/daemon.go
daemonDest := "/daemon_dest"
config := container.Config{
Image: image,
Env: []string{
"TESTFLAGS=-check.f " + strings.Join(tests, "|"),
"KEEPBUNDLE=1",
"DOCKER_INTEGRATION_TESTS_VERIFIED=1", // for avoiding rebuilding integration-cli
"DOCKER_EXPERIMENTAL=" + experimental,
"DOCKER_GRAPHDRIVER=" + graphdriver,
"DOCKER_INTEGRATION_DAEMON_DEST=" + daemonDest,
},
Labels: map[string]string{
"org.dockerproject.integration-cli-on-swarm": "",
"org.dockerproject.integration-cli-on-swarm.comment": "this non-service container is created for running privileged programs on Swarm. you can remove this container manually if the corresponding service is already stopped.",
},
Entrypoint: []string{"hack/dind"},
Cmd: []string{"hack/make.sh", "test-integration"},
}
hostConfig := container.HostConfig{
AutoRemove: autoRemove,
Privileged: true,
Mounts: []mount.Mount{
{
Type: mount.TypeVolume,
Target: daemonDest,
},
},
}
id, stream, err := runContainer(context.Background(), cli, config, hostConfig)
if err != nil {
return 0, "", err
}
var b bytes.Buffer
teeContainerStream(&b, os.Stdout, os.Stderr, stream)
resultC, errC := cli.ContainerWait(context.Background(), id, "")
select {
case err := <-errC:
return 0, "", err
case result := <-resultC:
return result.StatusCode, b.String(), nil
}
}
}
func runContainer(ctx context.Context, cli *client.Client, config container.Config, hostConfig container.HostConfig) (string, io.ReadCloser, error) {
created, err := cli.ContainerCreate(context.Background(),
&config, &hostConfig, nil, "")
if err != nil {
return "", nil, err
}
if err = cli.ContainerStart(ctx, created.ID, types.ContainerStartOptions{}); err != nil {
return "", nil, err
}
stream, err := cli.ContainerLogs(ctx,
created.ID,
types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
})
return created.ID, stream, err
}
func teeContainerStream(w, stdout, stderr io.Writer, stream io.ReadCloser) {
stdcopy.StdCopy(io.MultiWriter(w, stdout), io.MultiWriter(w, stderr), stream)
stream.Close()
}