executor.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package main
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io"
  7. "os"
  8. "strings"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/container"
  11. "github.com/docker/docker/api/types/mount"
  12. "github.com/docker/docker/client"
  13. "github.com/docker/docker/pkg/stdcopy"
  14. )
  15. // testChunkExecutor executes integration-cli binary.
  16. // image needs to be the worker image itself. testFlags are OR-set of regexp for filtering tests.
  17. type testChunkExecutor func(image string, tests []string) (int64, string, error)
  18. func dryTestChunkExecutor() testChunkExecutor {
  19. return func(image string, tests []string) (int64, string, error) {
  20. return 0, fmt.Sprintf("DRY RUN (image=%q, tests=%v)", image, tests), nil
  21. }
  22. }
  23. // privilegedTestChunkExecutor invokes a privileged container from the worker
  24. // service via bind-mounted API socket so as to execute the test chunk
  25. func privilegedTestChunkExecutor(autoRemove bool) testChunkExecutor {
  26. return func(image string, tests []string) (int64, string, error) {
  27. cli, err := client.NewEnvClient()
  28. if err != nil {
  29. return 0, "", err
  30. }
  31. // propagate variables from the host (needs to be defined in the compose file)
  32. experimental := os.Getenv("DOCKER_EXPERIMENTAL")
  33. graphdriver := os.Getenv("DOCKER_GRAPHDRIVER")
  34. if graphdriver == "" {
  35. info, err := cli.Info(context.Background())
  36. if err != nil {
  37. return 0, "", err
  38. }
  39. graphdriver = info.Driver
  40. }
  41. // `daemon_dest` is similar to `$DEST` (e.g. `bundles/VERSION/test-integration`)
  42. // but it exists outside of `bundles` so as to make `$DOCKER_GRAPHDRIVER` work.
  43. //
  44. // Without this hack, `$DOCKER_GRAPHDRIVER` fails because of (e.g.) `overlay2 is not supported over overlayfs`
  45. //
  46. // see integration-cli/daemon/daemon.go
  47. daemonDest := "/daemon_dest"
  48. config := container.Config{
  49. Image: image,
  50. Env: []string{
  51. "TESTFLAGS=-check.f " + strings.Join(tests, "|"),
  52. "KEEPBUNDLE=1",
  53. "DOCKER_INTEGRATION_TESTS_VERIFIED=1", // for avoiding rebuilding integration-cli
  54. "DOCKER_EXPERIMENTAL=" + experimental,
  55. "DOCKER_GRAPHDRIVER=" + graphdriver,
  56. "DOCKER_INTEGRATION_DAEMON_DEST=" + daemonDest,
  57. },
  58. Labels: map[string]string{
  59. "org.dockerproject.integration-cli-on-swarm": "",
  60. "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.",
  61. },
  62. Entrypoint: []string{"hack/dind"},
  63. Cmd: []string{"hack/make.sh", "test-integration"},
  64. }
  65. hostConfig := container.HostConfig{
  66. AutoRemove: autoRemove,
  67. Privileged: true,
  68. Mounts: []mount.Mount{
  69. {
  70. Type: mount.TypeVolume,
  71. Target: daemonDest,
  72. },
  73. },
  74. }
  75. id, stream, err := runContainer(context.Background(), cli, config, hostConfig)
  76. if err != nil {
  77. return 0, "", err
  78. }
  79. var b bytes.Buffer
  80. teeContainerStream(&b, os.Stdout, os.Stderr, stream)
  81. resultC, errC := cli.ContainerWait(context.Background(), id, "")
  82. select {
  83. case err := <-errC:
  84. return 0, "", err
  85. case result := <-resultC:
  86. return result.StatusCode, b.String(), nil
  87. }
  88. }
  89. }
  90. func runContainer(ctx context.Context, cli *client.Client, config container.Config, hostConfig container.HostConfig) (string, io.ReadCloser, error) {
  91. created, err := cli.ContainerCreate(context.Background(),
  92. &config, &hostConfig, nil, "")
  93. if err != nil {
  94. return "", nil, err
  95. }
  96. if err = cli.ContainerStart(ctx, created.ID, types.ContainerStartOptions{}); err != nil {
  97. return "", nil, err
  98. }
  99. stream, err := cli.ContainerLogs(ctx,
  100. created.ID,
  101. types.ContainerLogsOptions{
  102. ShowStdout: true,
  103. ShowStderr: true,
  104. Follow: true,
  105. })
  106. return created.ID, stream, err
  107. }
  108. func teeContainerStream(w, stdout, stderr io.Writer, stream io.ReadCloser) {
  109. stdcopy.StdCopy(io.MultiWriter(w, stdout), io.MultiWriter(w, stderr), stream)
  110. stream.Close()
  111. }