diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ba30181cff0..f08824dfebdc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,35 +30,8 @@ env: GOTESTSUM_VERSION: "v1.9.0" # same as one in Dockerfile jobs: - prepare-test-integration: - runs-on: ubuntu-22.04 - steps: - - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: ${{ env.BUILDX_VERSION }} - driver-opts: image=${{ env.BUILDKIT_IMAGE }} - buildkitd-flags: --debug - - - name: Build - uses: docker/bake-action@v4 - with: - targets: integration-test-base - set: | - *.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }} - *.cache-to=type=gha,scope=${{ env.TEST_CACHE_SCOPE }} - test-integration: runs-on: ubuntu-22.04 - needs: - - prepare-test-integration env: TESTFLAGS_DOCKER: "-v --parallel=1 --timeout=30m" TEST_IMAGE_BUILD: "0" @@ -66,18 +39,44 @@ jobs: strategy: fail-fast: false matrix: + buildkit: + - master + - latest + - buildx-stable-1 + - v0.13.0 + - v0.12.5 + - v0.11.6 worker: - - docker - - docker\+containerd # same as docker, but with containerd snapshotter - docker-container - - remote + - remote+docker-container + - remote+tcp pkg: - ./tests + include: + - worker: docker + pkg: ./tests + - worker: docker+containerd # same as docker, but with containerd snapshotter + pkg: ./tests steps: - name: Prepare run: | - echo "TESTREPORTS_NAME=${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + echo "TESTREPORTS_NAME=${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.buildkit }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + if [ -n "${{ matrix.buildkit }}" ]; then + echo "TEST_BUILDKIT_TAG=${{ matrix.buildkit }}" >> $GITHUB_ENV + fi + testFlags="--run=//worker=$(echo "${{ matrix.worker }}" | sed 's/\+/\\+/g')$" + case "${{ matrix.worker }}" in + docker | docker+containerd | docker@* | docker+containerd@* | remote*) + echo "TESTFLAGS=${{ env.TESTFLAGS_DOCKER }} $testFlags" >> $GITHUB_ENV + ;; + *) + echo "TESTFLAGS=${{ env.TESTFLAGS }} $testFlags" >> $GITHUB_ENV + ;; + esac + if [[ "${{ matrix.worker }}" == "docker"* ]]; then + echo "TEST_DOCKERD=1" >> $GITHUB_ENV + fi - name: Checkout uses: actions/checkout@v4 @@ -99,7 +98,6 @@ jobs: with: targets: integration-test set: | - *.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }} *.output=type=docker,name=${{ env.TEST_IMAGE_ID }} - name: Test @@ -107,8 +105,6 @@ jobs: ./hack/test env: TEST_REPORT_SUFFIX: "-${{ env.TESTREPORTS_NAME }}" - TEST_DOCKERD: "${{ startsWith(matrix.worker, 'docker') && '1' || '0' }}" - TESTFLAGS: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker\\+containerd') && env.TESTFLAGS_DOCKER || env.TESTFLAGS }} --run=//worker=${{ matrix.worker }}$" TESTPKGS: "${{ matrix.pkg }}" - name: Send to Codecov @@ -355,27 +351,3 @@ jobs: with: draft: true files: ${{ env.DESTDIR }}/* - - buildkit-edge: - runs-on: ubuntu-22.04 - continue-on-error: true - steps: - - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: ${{ env.BUILDX_VERSION }} - driver-opts: image=moby/buildkit:master - buildkitd-flags: --debug - - - # Just run a bake target to check eveything runs fine - name: Build - uses: docker/bake-action@v4 - with: - targets: binaries diff --git a/Dockerfile b/Dockerfile index f48105ee987c..d6c13962f9f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,14 +7,14 @@ ARG XX_VERSION=1.4.0 ARG DOCKER_VERSION=25.0.2 ARG GOTESTSUM_VERSION=v1.9.0 ARG REGISTRY_VERSION=2.8.0 -ARG BUILDKIT_VERSION=v0.12.5 +ARG UNDOCK_VERSION=0.7.0 FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest FROM moby/moby-bin:$DOCKER_VERSION AS docker-engine FROM dockereng/cli-bin:$DOCKER_VERSION AS docker-cli FROM registry:$REGISTRY_VERSION AS registry -FROM moby/buildkit:$BUILDKIT_VERSION AS buildkit +FROM crazymax/undock:$UNDOCK_VERSION AS undock FROM golatest AS gobase COPY --from=xx / / @@ -90,8 +90,7 @@ COPY --link --from=gotestsum /out/gotestsum /usr/bin/ COPY --link --from=registry /bin/registry /usr/bin/ COPY --link --from=docker-engine / /usr/bin/ COPY --link --from=docker-cli / /usr/bin/ -COPY --link --from=buildkit /usr/bin/buildkitd /usr/bin/ -COPY --link --from=buildkit /usr/bin/buildctl /usr/bin/ +COPY --link --from=undock /usr/local/bin/undock /usr/bin/ COPY --link --from=binaries /buildx /usr/bin/ FROM integration-test-base AS integration-test diff --git a/hack/test b/hack/test index b1cea0832f51..6ab303c4e11b 100755 --- a/hack/test +++ b/hack/test @@ -10,6 +10,7 @@ set -eu -o pipefail : "${TEST_REPORT_SUFFIX=}" : "${TEST_KEEP_CACHE=}" : "${TEST_DOCKERD=}" +: "${TEST_BUILDKIT_TAG=}" : "${TEST_BUILDKIT_IMAGE=}" if [ "$TEST_IMAGE_BUILD" = "1" ]; then @@ -24,11 +25,11 @@ gotestArgs="-mod=vendor -coverprofile=/testreports/coverage-report$TEST_REPORT_S cacheVolume="buildx-test-cache" if ! docker container inspect "$cacheVolume" >/dev/null 2>/dev/null; then - docker create -v /root/.cache -v /root/.cache/registry -v /go/pkg/mod --name "$cacheVolume" alpine + docker create -v /root/.cache -v /root/.cache/registry -v /root/.cache/undock -v /go/pkg/mod --name "$cacheVolume" alpine fi if [ "$TEST_KEEP_CACHE" != "1" ]; then trap 'docker rm -v $cacheVolume' EXIT fi -cid=$(docker create --rm -v /tmp $testReportsVol --volumes-from=$cacheVolume -e GITHUB_REF -e TEST_DOCKERD -e TEST_BUILDKIT_IMAGE -e SKIP_INTEGRATION_TESTS -e GOTESTSUM_FORMAT ${BUILDKIT_INTEGRATION_SNAPSHOTTER:+"-eBUILDKIT_INTEGRATION_SNAPSHOTTER"} -e BUILDKIT_REGISTRY_MIRROR_DIR=/root/.cache/registry --privileged $TEST_IMAGE_ID gotestsum $gotestsumArgs --packages="${TESTPKGS:-./...}" -- $gotestArgs ${TESTFLAGS:--v}) +cid=$(docker create --rm -v /tmp $testReportsVol --volumes-from=$cacheVolume -e GITHUB_REF -e TEST_DOCKERD -e TEST_BUILDKIT_IMAGE -e TEST_BUILDKIT_TAG -e SKIP_INTEGRATION_TESTS -e GOTESTSUM_FORMAT ${BUILDKIT_INTEGRATION_SNAPSHOTTER:+"-eBUILDKIT_INTEGRATION_SNAPSHOTTER"} -e BUILDKIT_REGISTRY_MIRROR_DIR=/root/.cache/registry --privileged $TEST_IMAGE_ID gotestsum $gotestsumArgs --packages="${TESTPKGS:-./...}" -- $gotestArgs ${TESTFLAGS:--v}) docker start -a -i $cid diff --git a/tests/bake.go b/tests/bake.go index 4cadd559ee0b..33b85c81a46a 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" "github.com/containerd/continuity/fs/fstest" @@ -641,9 +640,10 @@ target "default" { } func testBakeMultiExporters(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker" { - t.Skip("skipping test for non-docker workers") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } + skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "multi exporters") registry, err := sb.NewRegistry() if errors.Is(err, integration.ErrRequirements) { @@ -654,30 +654,12 @@ func testBakeMultiExporters(t *testing.T, sb integration.Sandbox) { targetReg := registry + "/buildx/registry:latest" targetStore := "buildx:local-" + identity.NewID() - var builderName string t.Cleanup(func() { - if builderName == "" { - return - } - cmd := dockerCmd(sb, withArgs("image", "rm", targetStore)) cmd.Stderr = os.Stderr require.NoError(t, cmd.Run()) - - out, err := rmCmd(sb, withArgs(builderName)) - require.NoError(t, err, out) }) - // TODO: use stable buildkit image when v0.13.0 released - out, err := createCmd(sb, withArgs( - "--driver", "docker-container", - "--buildkitd-flags=--allow-insecure-entitlement=network.host", - "--driver-opt", "network=host", - "--driver-opt", "image=moby/buildkit:v0.13.0-rc3", - )) - require.NoError(t, err, out) - builderName = strings.TrimSpace(out) - dockerfile := []byte(` FROM scratch COPY foo /foo @@ -699,7 +681,6 @@ target "default" { "--set", fmt.Sprintf("*.output=type=oci,dest=%s/result", dir), } cmd := buildxCmd(sb, withDir(dir), withArgs("bake"), withArgs(outputs...)) - cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName) outb, err := cmd.CombinedOutput() require.NoError(t, err, string(outb)) @@ -722,9 +703,10 @@ target "default" { } func testBakeLoadPush(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker" { - t.Skip("skipping test for non-docker workers") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } + skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "multi exporters") registry, err := sb.NewRegistry() if errors.Is(err, integration.ErrRequirements) { @@ -734,30 +716,12 @@ func testBakeLoadPush(t *testing.T, sb integration.Sandbox) { target := registry + "/buildx/registry:" + identity.NewID() - var builderName string t.Cleanup(func() { - if builderName == "" { - return - } - cmd := dockerCmd(sb, withArgs("image", "rm", target)) cmd.Stderr = os.Stderr require.NoError(t, cmd.Run()) - - out, err := rmCmd(sb, withArgs(builderName)) - require.NoError(t, err, out) }) - // TODO: use stable buildkit image when v0.13.0 released - out, err := createCmd(sb, withArgs( - "--driver", "docker-container", - "--buildkitd-flags=--allow-insecure-entitlement=network.host", - "--driver-opt", "network=host", - "--driver-opt", "image=moby/buildkit:v0.13.0-rc3", - )) - require.NoError(t, err, out) - builderName = strings.TrimSpace(out) - dockerfile := []byte(` FROM scratch COPY foo /foo @@ -774,7 +738,6 @@ target "default" { ) cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--push", "--load", fmt.Sprintf("--set=*.tags=%s", target))) - cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName) outb, err := cmd.CombinedOutput() require.NoError(t, err, string(outb)) diff --git a/tests/build.go b/tests/build.go index 5bf14fd11526..500cade1d8e7 100644 --- a/tests/build.go +++ b/tests/build.go @@ -125,10 +125,14 @@ func testBuildRegistryExportAttestations(t *testing.T, sb integration.Sandbox) { target := registry + "/buildx/registry:latest" out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=image,name=%s,push=true", target), "--provenance=true", dir)) - if sb.Name() == "docker" { + if isMobyWorker(sb) { require.Error(t, err) require.Contains(t, out, "Attestation is not supported") return + } else if !isMobyContainerdSnapWorker(sb) && !matchesBuildKitVersion(t, sb, ">= 0.11.0-0") { + require.Error(t, err) + require.Contains(t, out, "Attestations are not supported by the current BuildKit daemon") + return } require.NoError(t, err, string(out)) @@ -200,7 +204,7 @@ func testImageIDOutput(t *testing.T, sb integration.Sandbox) { func testBuildMobyFromLocalImage(t *testing.T, sb integration.Sandbox) { if !isDockerWorker(sb) { - t.Skip("skipping test for non-docker workers") + t.Skip("only testing with docker workers") } // pull image @@ -247,6 +251,7 @@ RUN busybox | head -1 | grep v1.36.1 } func testBuildDetailsLink(t *testing.T, sb integration.Sandbox) { + skipNoCompatBuildKit(t, sb, ">= 0.11.0-0", "build details link") buildDetailsPattern := regexp.MustCompile(`(?m)^View build details: docker-desktop://dashboard/build/[^/]+/[^/]+/[^/]+\n$`) // build simple dockerfile @@ -290,7 +295,7 @@ RUN exit 1`) func testBuildProgress(t *testing.T, sb integration.Sandbox) { dir := createTestProject(t) - driver, _, _ := strings.Cut(sb.Name(), "+") + sbDriver, _ := driverName(sb.Name()) name := sb.Address() // progress=tty @@ -301,7 +306,7 @@ func testBuildProgress(t *testing.T, sb integration.Sandbox) { io.Copy(buf, f) ttyOutput := buf.String() require.Contains(t, ttyOutput, "[+] Building") - require.Contains(t, ttyOutput, fmt.Sprintf("%s:%s", driver, name)) + require.Contains(t, ttyOutput, fmt.Sprintf("%s:%s", sbDriver, name)) require.Contains(t, ttyOutput, "=> [internal] load build definition from Dockerfile") require.Contains(t, ttyOutput, "=> [base 1/3] FROM docker.io/library/busybox:latest") @@ -309,15 +314,16 @@ func testBuildProgress(t *testing.T, sb integration.Sandbox) { cmd = buildxCmd(sb, withArgs("build", "--progress=plain", "--output=type=cacheonly", dir)) plainOutput, err := cmd.CombinedOutput() require.NoError(t, err) - require.Contains(t, string(plainOutput), fmt.Sprintf(`#0 building with "%s" instance using %s driver`, name, driver)) + require.Contains(t, string(plainOutput), fmt.Sprintf(`#0 building with "%s" instance using %s driver`, name, sbDriver)) require.Contains(t, string(plainOutput), "[internal] load build definition from Dockerfile") require.Contains(t, string(plainOutput), "[base 1/3] FROM docker.io/library/busybox:latest") } func testBuildAnnotations(t *testing.T, sb integration.Sandbox) { - if sb.Name() == "docker" { + if isMobyWorker(sb) { t.Skip("annotations not supported on docker worker") } + skipNoCompatBuildKit(t, sb, ">= 0.11.0-0", "annotations") dir := createTestProject(t) @@ -374,8 +380,8 @@ func testBuildLabelNoKey(t *testing.T, sb integration.Sandbox) { } func testBuildCacheExportNotSupported(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker" { - t.Skip("skipping test for non-docker workers") + if !isMobyWorker(sb) { + t.Skip("only testing with docker worker") } dir := createTestProject(t) @@ -386,8 +392,8 @@ func testBuildCacheExportNotSupported(t *testing.T, sb integration.Sandbox) { } func testBuildOCIExportNotSupported(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker" { - t.Skip("skipping test for non-docker workers") + if !isMobyWorker(sb) { + t.Skip("only testing with docker worker") } dir := createTestProject(t) @@ -398,8 +404,8 @@ func testBuildOCIExportNotSupported(t *testing.T, sb integration.Sandbox) { } func testBuildMultiPlatformNotSupported(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker" { - t.Skip("skipping test for non-docker workers") + if !isMobyWorker(sb) { + t.Skip("only testing with docker worker") } dir := createTestProject(t) @@ -426,9 +432,10 @@ RUN ping -c 1 buildx.host-gateway-ip.local } func testBuildNetworkModeBridge(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker" { - t.Skip("skipping test for non-docker workers") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } + skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "network bridge") var builderName string t.Cleanup(func() { @@ -439,8 +446,10 @@ func testBuildNetworkModeBridge(t *testing.T, sb integration.Sandbox) { require.NoError(t, err, out) }) - // TODO: use stable buildkit image when v0.13.0 released - out, err := createCmd(sb, withArgs("--driver", "docker-container", "--buildkitd-flags=--oci-worker-net=bridge --allow-insecure-entitlement=network.host", "--driver-opt", "image=moby/buildkit:v0.13.0-rc3")) + out, err := createCmd(sb, withArgs( + "--driver", "docker-container", + "--buildkitd-flags=--oci-worker-net=bridge --allow-insecure-entitlement=network.host", + )) require.NoError(t, err, out) builderName = strings.TrimSpace(out) @@ -546,9 +555,10 @@ func testBuildRef(t *testing.T, sb integration.Sandbox) { } func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker" { - t.Skip("skipping test for non-docker workers") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } + skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "multi exporters") registry, err := sb.NewRegistry() if errors.Is(err, integration.ErrRequirements) { @@ -559,30 +569,12 @@ func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) { targetReg := registry + "/buildx/registry:latest" targetStore := "buildx:local-" + identity.NewID() - var builderName string t.Cleanup(func() { - if builderName == "" { - return - } - cmd := dockerCmd(sb, withArgs("image", "rm", targetStore)) cmd.Stderr = os.Stderr require.NoError(t, cmd.Run()) - - out, err := rmCmd(sb, withArgs(builderName)) - require.NoError(t, err, out) }) - // TODO: use stable buildkit image when v0.13.0 released - out, err := createCmd(sb, withArgs( - "--driver", "docker-container", - "--buildkitd-flags=--allow-insecure-entitlement=network.host", - "--driver-opt", "network=host", - "--driver-opt", "image=moby/buildkit:v0.13.0-rc3", - )) - require.NoError(t, err, out) - builderName = strings.TrimSpace(out) - dir := createTestProject(t) outputs := []string{ @@ -591,7 +583,6 @@ func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) { "--output", fmt.Sprintf("type=oci,dest=%s/result", dir), } cmd := buildxCmd(sb, withArgs("build"), withArgs(outputs...), withArgs(dir)) - cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName) outb, err := cmd.CombinedOutput() require.NoError(t, err, string(outb)) @@ -614,9 +605,10 @@ func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) { } func testBuildLoadPush(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker" { - t.Skip("skipping test for non-docker workers") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } + skipNoCompatBuildKit(t, sb, ">= 0.13.0-0", "multi exporters") registry, err := sb.NewRegistry() if errors.Is(err, integration.ErrRequirements) { @@ -626,30 +618,12 @@ func testBuildLoadPush(t *testing.T, sb integration.Sandbox) { target := registry + "/buildx/registry:" + identity.NewID() - var builderName string t.Cleanup(func() { - if builderName == "" { - return - } - cmd := dockerCmd(sb, withArgs("image", "rm", target)) cmd.Stderr = os.Stderr require.NoError(t, cmd.Run()) - - out, err := rmCmd(sb, withArgs(builderName)) - require.NoError(t, err, out) }) - // TODO: use stable buildkit image when v0.13.0 released - out, err := createCmd(sb, withArgs( - "--driver", "docker-container", - "--buildkitd-flags=--allow-insecure-entitlement=network.host", - "--driver-opt", "network=host", - "--driver-opt", "image=moby/buildkit:v0.13.0-rc3", - )) - require.NoError(t, err, out) - builderName = strings.TrimSpace(out) - dir := createTestProject(t) cmd := buildxCmd(sb, withArgs( @@ -657,7 +631,6 @@ func testBuildLoadPush(t *testing.T, sb integration.Sandbox) { fmt.Sprintf("-t=%s", target), dir, )) - cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName) outb, err := cmd.CombinedOutput() require.NoError(t, err, string(outb)) diff --git a/tests/create.go b/tests/create.go index 1a2757fcad5f..195691fbbc21 100644 --- a/tests/create.go +++ b/tests/create.go @@ -26,8 +26,8 @@ var createTests = []func(t *testing.T, sb integration.Sandbox){ } func testCreateMemoryLimit(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("only testing for docker-container driver") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } var builderName string @@ -45,8 +45,8 @@ func testCreateMemoryLimit(t *testing.T, sb integration.Sandbox) { } func testCreateRestartAlways(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("only testing for docker-container driver") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } var builderName string diff --git a/tests/dialstdio.go b/tests/dialstdio.go index afb441cb749f..4a034a5f8ba2 100644 --- a/tests/dialstdio.go +++ b/tests/dialstdio.go @@ -61,6 +61,7 @@ func testDialStdio(t *testing.T, sb integration.Sandbox) { } }() + skipNoCompatBuildKit(t, sb, ">= 0.11.0-0", "unknown method Info for service moby.buildkit.v1.Control") _, err = c.Info(sb.Context()) require.NoError(t, err) diff --git a/tests/imagetools.go b/tests/imagetools.go index 59d06944fe9a..b6d21b49663f 100644 --- a/tests/imagetools.go +++ b/tests/imagetools.go @@ -22,8 +22,8 @@ var imagetoolsTests = []func(t *testing.T, sb integration.Sandbox){ } func testImagetoolsCopyManifest(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("imagetools tests are not driver specific and only run on docker-container") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker, imagetools only runs on docker-container") } dir := createDockerfile(t) @@ -81,8 +81,8 @@ func testImagetoolsCopyManifest(t *testing.T, sb integration.Sandbox) { } func testImagetoolsCopyIndex(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("imagetools tests are not driver specific and only run on docker-container") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker, imagetools only runs on docker-container") } dir := createDockerfile(t) @@ -130,8 +130,8 @@ func testImagetoolsCopyIndex(t *testing.T, sb integration.Sandbox) { } func testImagetoolsInspectAndFilter(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("imagetools tests are not driver specific and only run on docker-container") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker, imagetools only runs on docker-container") } dir := createDockerfile(t) @@ -181,8 +181,8 @@ func testImagetoolsInspectAndFilter(t *testing.T, sb integration.Sandbox) { } func testImagetoolsAnnotation(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("imagetools tests are not driver specific and only run on docker-container") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker, imagetools only runs on docker-container") } dir := createDockerfile(t) diff --git a/tests/inspect.go b/tests/inspect.go index 755ac16563ea..8dea100fa07d 100644 --- a/tests/inspect.go +++ b/tests/inspect.go @@ -41,7 +41,7 @@ func testInspect(t *testing.T, sb integration.Sandbox) { } require.Equal(t, sb.Address(), name) - sbDriver, _, _ := strings.Cut(sb.Name(), "+") + sbDriver, _ := driverName(sb.Name()) require.Equal(t, sbDriver, driver) if isDockerWorker(sb) { require.NotEmpty(t, hostGatewayIP, "host-gateway-ip worker label should be set with docker driver") @@ -51,8 +51,8 @@ func testInspect(t *testing.T, sb integration.Sandbox) { } func testInspectBuildkitdFlags(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("only testing for docker-container driver") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } var builderName string @@ -81,8 +81,8 @@ func testInspectBuildkitdFlags(t *testing.T, sb integration.Sandbox) { } func testInspectNetworkHostEntitlement(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("only testing for docker-container driver") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } var builderName string diff --git a/tests/integration.go b/tests/integration.go index 335b757c3d9b..b14e73bf14c5 100644 --- a/tests/integration.go +++ b/tests/integration.go @@ -3,14 +3,21 @@ package tests import ( "os" "os/exec" + "path/filepath" "strings" + "sync" "testing" + "github.com/Masterminds/semver/v3" "github.com/containerd/continuity/fs/fstest" "github.com/moby/buildkit/util/testutil/integration" "github.com/stretchr/testify/require" ) +const defaultBuildKitTag = "buildx-stable-1" + +var buildkitImage string + func tmpdir(t *testing.T, appliers ...fstest.Applier) string { t.Helper() tmpdir := t.TempDir() @@ -71,7 +78,110 @@ func dockerCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd { return cmd } +func isMobyWorker(sb integration.Sandbox) bool { + name, hasFeature := driverName(sb.Name()) + return name == "docker" && !hasFeature +} + +func isMobyContainerdSnapWorker(sb integration.Sandbox) bool { + name, hasFeature := driverName(sb.Name()) + return name == "docker" && hasFeature +} + func isDockerWorker(sb integration.Sandbox) bool { - sbDriver, _, _ := strings.Cut(sb.Name(), "+") - return sbDriver == "docker" + name, _ := driverName(sb.Name()) + return name == "docker" +} + +func isDockerContainerWorker(sb integration.Sandbox) bool { + name, _ := driverName(sb.Name()) + return name == "docker-container" +} + +func driverName(sbName string) (string, bool) { + name := sbName + var hasFeature bool + if b, _, ok := strings.Cut(name, "+"); ok { + name = b + hasFeature = true + } + return name, hasFeature +} + +func buildkitTag() string { + if v := os.Getenv("TEST_BUILDKIT_TAG"); v != "" { + return v + } + return defaultBuildKitTag +} + +var ( + bkvers map[string]string + bkversMu sync.Mutex +) + +func buildkitVersion(t *testing.T, sb integration.Sandbox) string { + bkversMu.Lock() + defer bkversMu.Unlock() + + if bkvers == nil { + bkvers = make(map[string]string) + } + + ver, ok := bkvers[sb.Name()] + if !ok { + out, err := inspectCmd(sb, withArgs(sb.Address())) + require.NoError(t, err, out) + for _, line := range strings.Split(out, "\n") { + if v, ok := strings.CutPrefix(line, "BuildKit version:"); ok { + ver = strings.TrimSpace(v) + bkvers[sb.Name()] = ver + } + } + if ver == "" { + t.Logf("BuildKit version not found in inspect output, extract it from the image.\n%s", out) + undockBin, err := exec.LookPath("undock") + require.NoError(t, err, "undock not found") + + destDir := t.TempDir() + t.Cleanup(func() { + os.RemoveAll(destDir) + }) + + cmd := exec.Command(undockBin, "--cachedir", "/root/.cache/undock", "--include", "/usr/bin/buildkitd", "--rm-dist", buildkitImage, destDir) + require.NoErrorf(t, cmd.Run(), "failed to extract buildkitd binary from %q", buildkitImage) + + cmd = exec.Command(filepath.Join(destDir, "usr", "bin", "buildkitd"), "--version") + out, err := cmd.CombinedOutput() + require.NoErrorf(t, err, "failed to get BuildKit version from %q: %s", buildkitImage, string(out)) + + v := strings.Fields(strings.TrimSpace(string(out))) + if len(v) != 4 { + require.Fail(t, "unexpected version format: "+strings.TrimSpace(string(out))) + } + ver = v[2] + bkvers[sb.Name()] = ver + } + } + + return ver +} + +func matchesBuildKitVersion(t *testing.T, sb integration.Sandbox, constraint string) bool { + c, err := semver.NewConstraint(constraint) + if err != nil { + return false + } + v, err := semver.NewVersion(buildkitVersion(t, sb)) + if err != nil { + // if the version is not a valid semver, we assume it matches (master) + return true + } + return c.Check(v) +} + +func skipNoCompatBuildKit(t *testing.T, sb integration.Sandbox, constraint string, msg string) { + if !matchesBuildKitVersion(t, sb, constraint) { + t.Skipf("buildkit version %s does not match %s constraint (%s)", buildkitVersion(t, sb), constraint, msg) + } } diff --git a/tests/integration_test.go b/tests/integration_test.go index 49c5d06dd055..a9d49ea76fb5 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -35,7 +35,7 @@ func TestIntegration(t *testing.T) { func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sandbox)) { mirroredImages := integration.OfficialImages("busybox:latest", "alpine:latest") - buildkitImage := "docker.io/moby/buildkit:buildx-stable-1" + buildkitImage = "docker.io/moby/buildkit:" + buildkitTag() if bkworkers.IsTestDockerd() { if img, ok := os.LookupEnv("TEST_BUILDKIT_IMAGE"); ok { ref, err := reference.ParseNormalizedNamed(img) diff --git a/tests/ls.go b/tests/ls.go index 53246d9826ab..1e147fe0c1e6 100644 --- a/tests/ls.go +++ b/tests/ls.go @@ -34,7 +34,7 @@ func testLs(t *testing.T, sb integration.Sandbox) { }, } - sbDriver, _, _ := strings.Cut(sb.Name(), "+") + sbDriver, _ := driverName(sb.Name()) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/tests/rm.go b/tests/rm.go index 40cf8c942697..5f7d45569cbe 100644 --- a/tests/rm.go +++ b/tests/rm.go @@ -21,8 +21,8 @@ var rmTests = []func(t *testing.T, sb integration.Sandbox){ } func testRm(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("only testing for docker-container driver") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } out, err := rmCmd(sb, withArgs("default")) @@ -40,8 +40,8 @@ func testRm(t *testing.T, sb integration.Sandbox) { } func testRmMulti(t *testing.T, sb integration.Sandbox) { - if sb.Name() != "docker-container" { - t.Skip("only testing for docker-container driver") + if !isDockerContainerWorker(sb) { + t.Skip("only testing with docker-container worker") } var builderNames []string diff --git a/tests/workers/docker.go b/tests/workers/docker.go index cd6d6084b8af..37c3a4d8c017 100644 --- a/tests/workers/docker.go +++ b/tests/workers/docker.go @@ -24,7 +24,8 @@ func InitDockerWorker() { type dockerWorker struct { id string containerdSnapshotter bool - unsupported []string + + unsupported []string } func (c dockerWorker) Name() string { diff --git a/tests/workers/remote.go b/tests/workers/remote.go index 4ded704f4f3f..8247cc950de5 100644 --- a/tests/workers/remote.go +++ b/tests/workers/remote.go @@ -2,24 +2,41 @@ package workers import ( "context" + "fmt" + "net" "os" "os/exec" + "sync" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/util/testutil/integration" - bkworkers "github.com/moby/buildkit/util/testutil/workers" "github.com/pkg/errors" ) +var protos = []string{ + "docker-container", + "tcp", +} + func InitRemoteWorker() { - integration.Register(&remoteWorker{ - id: "remote", - }) + for _, p := range protos { + integration.Register(&remoteWorker{ + id: "remote+" + p, + proto: p, + }) + } } type remoteWorker struct { - id string + id string + proto string + unsupported []string + + docker integration.Backend + dockerClose func() error + dockerErr error + dockerOnce sync.Once } func (w remoteWorker) Name() string { @@ -35,32 +52,71 @@ func (w remoteWorker) NetNSDetached() bool { } func (w remoteWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) { - oci := bkworkers.OCI{ID: w.id} - bk, bkclose, err := oci.New(ctx, cfg) - if err != nil { - return bk, cl, err + w.dockerOnce.Do(func() { + w.docker, w.dockerClose, w.dockerErr = dockerWorker{id: w.id}.New(ctx, cfg) + }) + if w.dockerErr != nil { + return w.docker, w.dockerClose, w.dockerErr } + bkCtnName := "buildkit-integration-" + identity.NewID() name := "integration-remote-" + identity.NewID() + envs := append( + os.Environ(), + "BUILDX_CONFIG=/tmp/buildx-"+name, + "DOCKER_CONTEXT="+w.docker.DockerAddress(), + ) + + // random host port for buildkit container + l, _ := net.Listen("tcp", ":0") //nolint:gosec + _ = l.Close() + bkPort := l.Addr().(*net.TCPAddr).Port + + // create buildkit container + bkCtnCmd := exec.Command("docker", "run", + "-d", "--rm", + "--privileged", + "-p", fmt.Sprintf("%d:1234", bkPort), + "--name="+bkCtnName, + "moby/buildkit:buildx-stable-1", + "--addr=tcp://0.0.0.0:1234", + ) + bkCtnCmd.Env = envs + if out, err := bkCtnCmd.CombinedOutput(); err != nil { + return nil, nil, errors.Wrapf(err, "failed to create buildkit container %s: %s", bkCtnName, string(out)) + } + + // create builder + var endpoint string + switch w.proto { + case "docker-container": + endpoint = fmt.Sprintf("docker-container://%s", bkCtnName) + case "tcp": + endpoint = fmt.Sprintf("tcp://localhost:%d", bkPort) + default: + return nil, nil, errors.Errorf("unsupported protocol %s", w.proto) + } cmd := exec.Command("buildx", "create", "--bootstrap", "--name="+name, "--driver=remote", - bk.Address(), + endpoint, ) - cmd.Env = append(os.Environ(), "BUILDX_CONFIG=/tmp/buildx-"+name) - if err := cmd.Run(); err != nil { - return nil, nil, errors.Wrapf(err, "failed to create buildx instance %s", name) + cmd.Env = envs + if out, err := cmd.CombinedOutput(); err != nil { + return nil, nil, errors.Wrapf(err, "failed to create buildx instance %s: %s", name, string(out)) } cl = func() error { - var err error - if err1 := bkclose(); err == nil { - err = err1 + cmd := exec.Command("docker", "container", "rm", "-f", name) + cmd.Env = envs + if err1 := cmd.Run(); err1 != nil { + err = errors.Wrapf(err1, "failed to remove buildkit container %s", bkCtnName) } - cmd := exec.Command("buildx", "rm", "-f", name) - if err1 := cmd.Run(); err == nil { - err = err1 + cmd = exec.Command("buildx", "rm", "-f", name) + cmd.Env = envs + if err1 := cmd.Run(); err1 != nil { + err = errors.Wrapf(err1, "failed to remove buildx instance %s", name) } return err }