| 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() |
| } |