diff --git a/Dockerfile b/Dockerfile index 0dfa1c908094..0e648dbbf95b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ ARG DOCKER_CLI_VERSION=${DOCKER_VERSION} ARG GOTESTSUM_VERSION=v1.12.0 ARG REGISTRY_VERSION=3.0.0 ARG BUILDKIT_VERSION=v0.23.2 +ARG COMPOSE_VERSION=v2.39.0 ARG UNDOCK_VERSION=0.9.0 FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx @@ -24,6 +25,7 @@ FROM dockereng/cli-bin:$DOCKER_VERSION_ALT_27 AS docker-cli-alt27 FROM dockereng/cli-bin:$DOCKER_VERSION_ALT_26 AS docker-cli-alt26 FROM registry:$REGISTRY_VERSION AS registry FROM moby/buildkit:$BUILDKIT_VERSION AS buildkit +FROM docker/compose-bin:$COMPOSE_VERSION AS compose FROM crazymax/undock:$UNDOCK_VERSION AS undock FROM golatest AS gobase @@ -137,8 +139,10 @@ COPY --link --from=docker-cli-alt27 / /opt/docker-alt-27/ COPY --link --from=docker-cli-alt26 / /opt/docker-alt-26/ COPY --link --from=buildkit /usr/bin/buildkitd /usr/bin/ COPY --link --from=buildkit /usr/bin/buildctl /usr/bin/ +COPY --link --from=compose /docker-compose /usr/bin/compose COPY --link --from=undock /usr/local/bin/undock /usr/bin/ COPY --link --from=binaries /buildx /usr/bin/ +RUN mkdir -p /usr/local/lib/docker/cli-plugins && ln -s /usr/bin/buildx /usr/local/lib/docker/cli-plugins/docker-buildx ENV TEST_DOCKER_EXTRA="docker@27.5=/opt/docker-alt-27,docker@26.1=/opt/docker-alt-26" FROM integration-test-base AS integration-test diff --git a/tests/bake.go b/tests/bake.go index d929701ce081..c96819889564 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -72,9 +72,9 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){ testBakeMetadataWarningsDedup, testBakeMultiExporters, testBakeLoadPush, - testListTargets, - testListVariables, - testListTypedVariables, + testBakeListTargets, + testBakeListVariables, + testBakeListTypedVariables, testBakeCallCheck, testBakeCallCheckFlag, testBakeCallMetadata, @@ -1691,7 +1691,7 @@ target "default" { // TODO: test metadata file when supported by multi exporters https://github.com/docker/buildx/issues/2181 } -func testListTargets(t *testing.T, sb integration.Sandbox) { +func testBakeListTargets(t *testing.T, sb integration.Sandbox) { bakefile := []byte(` target "foo" { description = "This builds foo" @@ -1714,7 +1714,7 @@ target "abc" { require.Equal(t, "TARGET\tDESCRIPTION\nabc\t\nfoo\tThis builds foo", strings.TrimSpace(out)) } -func testListVariables(t *testing.T, sb integration.Sandbox) { +func testBakeListVariables(t *testing.T, sb integration.Sandbox) { bakefile := []byte(` variable "foo" { default = "bar" @@ -1743,7 +1743,7 @@ target "default" { require.Equal(t, "VARIABLE\tTYPE\tVALUE\tDESCRIPTION\nabc\t\t\t\t\ndef\t\t\t\t\nfoo\t\t\tbar\tThis is foo", strings.TrimSpace(out)) } -func testListTypedVariables(t *testing.T, sb integration.Sandbox) { +func testBakeListTypedVariables(t *testing.T, sb integration.Sandbox) { bakefile := []byte(` variable "abc" { type = string diff --git a/tests/build.go b/tests/build.go index f494907a648b..adbdd6f4f0c0 100644 --- a/tests/build.go +++ b/tests/build.go @@ -76,7 +76,7 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){ testBuildSecret, testBuildDefaultLoad, testBuildCall, - testCheckCallOutput, + testBuildCheckCallOutput, testBuildExtraHosts, } @@ -1241,7 +1241,7 @@ COPy --from=base \ }) } -func testCheckCallOutput(t *testing.T, sb integration.Sandbox) { +func testBuildCheckCallOutput(t *testing.T, sb integration.Sandbox) { t.Run("check for warning count msg in check without warnings", func(t *testing.T) { dockerfile := []byte(` FROM busybox AS base diff --git a/tests/compose.go b/tests/compose.go new file mode 100644 index 000000000000..549a573904f5 --- /dev/null +++ b/tests/compose.go @@ -0,0 +1,172 @@ +package tests + +import ( + "fmt" + "os" + "testing" + + "github.com/containerd/continuity/fs/fstest" + "github.com/moby/buildkit/identity" + "github.com/moby/buildkit/util/contentutil" + "github.com/moby/buildkit/util/testutil" + "github.com/moby/buildkit/util/testutil/integration" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +var composeTests = []func(t *testing.T, sb integration.Sandbox){ + testComposeBuildLocalStore, + testComposeBuildRegistry, + testComposeBuildMultiPlatform, + testComposeBuildCheck, +} + +func testComposeBuildLocalStore(t *testing.T, sb integration.Sandbox) { + if !isDockerWorker(sb) && !isDockerContainerWorker(sb) { + t.Skip("only testing with docker and docker-container worker") + } + + target := "buildx:local-" + identity.NewID() + dir := composeTestProject(target, t) + + t.Cleanup(func() { + cmd := dockerCmd(sb, withArgs("image", "rm", target)) + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Run()) + }) + + cmd := composeCmd(sb, withDir(dir), withArgs("build")) + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + + cmd = dockerCmd(sb, withArgs("image", "inspect", target)) + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Run()) +} + +func testComposeBuildRegistry(t *testing.T, sb integration.Sandbox) { + registry, err := sb.NewRegistry() + if errors.Is(err, integration.ErrRequirements) { + t.Skip(err.Error()) + } + require.NoError(t, err) + + target := registry + "/buildx/registry:latest" + dir := composeTestProject(target, t) + + cmd := composeCmd(sb, withDir(dir), withArgs("build", "--push")) + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + + desc, provider, err := contentutil.ProviderFromRef(target) + require.NoError(t, err) + _, err = testutil.ReadImages(sb.Context(), provider, desc) + require.NoError(t, err) +} + +func testComposeBuildMultiPlatform(t *testing.T, sb integration.Sandbox) { + registry, err := sb.NewRegistry() + if errors.Is(err, integration.ErrRequirements) { + t.Skip(err.Error()) + } + require.NoError(t, err) + + target := registry + "/buildx/registry:latest" + + dockerfile := []byte(` +FROM busybox:latest +COPY foo /etc/foo +`) + composefile := fmt.Appendf([]byte{}, ` +services: + bar: + build: + context: . + platforms: + - linux/amd64 + - linux/arm64 + image: %s +`, target) + + dir := tmpdir( + t, + fstest.CreateFile("compose.yml", composefile, 0600), + fstest.CreateFile("Dockerfile", dockerfile, 0600), + fstest.CreateFile("foo", []byte("foo"), 0600), + ) + + cmd := composeCmd(sb, withDir(dir), withArgs("build", "--push")) + out, err := cmd.CombinedOutput() + + if !isMobyWorker(sb) { + require.NoError(t, err, string(out)) + + desc, provider, err := contentutil.ProviderFromRef(target) + require.NoError(t, err) + imgs, err := testutil.ReadImages(sb.Context(), provider, desc) + require.NoError(t, err) + + img := imgs.Find("linux/amd64") + require.NotNil(t, img) + img = imgs.Find("linux/arm64") + require.NotNil(t, img) + } else { + require.Error(t, err, string(out)) + require.Contains(t, string(out), "Multi-platform build is not supported") + } +} + +func testComposeBuildCheck(t *testing.T, sb integration.Sandbox) { + dockerfile := []byte(` +frOM busybox as base +cOpy Dockerfile . +from scratch +COPy --from=base \ + /Dockerfile \ + / + `) + + composefile := []byte(` +services: + bar: + build: + context: . +`) + + dir := tmpdir( + t, + fstest.CreateFile("compose.yml", composefile, 0600), + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + + cmd := composeCmd(sb, withDir(dir), withArgs("build", "--check")) + out, err := cmd.CombinedOutput() + require.Error(t, err, string(out)) + require.Contains(t, string(out), "Check complete, 3 warnings have been found!") +} + +func composeTestProject(imageName string, t *testing.T) string { + dockerfile := []byte(` +FROM busybox:latest AS base +COPY foo /etc/foo +RUN cp /etc/foo /etc/bar + +FROM scratch +COPY --from=base /etc/bar /bar +`) + + composefile := fmt.Appendf([]byte{}, ` +services: + bar: + build: + context: . + image: %s +`, imageName) + + return tmpdir( + t, + fstest.CreateFile("compose.yml", composefile, 0600), + fstest.CreateFile("Dockerfile", dockerfile, 0600), + fstest.CreateFile("foo", []byte("foo"), 0600), + ) +} diff --git a/tests/integration.go b/tests/integration.go index 574672a7ee79..444e74df814f 100644 --- a/tests/integration.go +++ b/tests/integration.go @@ -75,6 +75,30 @@ func buildxCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd { return cmd } +func composeCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd { + cmd := exec.Command("compose") + cmd.Env = os.Environ() + for _, opt := range opts { + opt(cmd) + } + + if builder := sb.Address(); builder != "" { + cmd.Env = append(cmd.Env, + "BUILDX_CONFIG="+buildxConfig(sb), + "BUILDX_BUILDER="+builder, + ) + } + if context := sb.DockerAddress(); context != "" { + cmd.Env = append(cmd.Env, "DOCKER_CONTEXT="+context) + } + if v := os.Getenv("GO_TEST_COVERPROFILE"); v != "" { + coverDir := filepath.Join(filepath.Dir(v), "helpers") + cmd.Env = append(cmd.Env, "GOCOVERDIR="+coverDir) + } + cmd.Env = append(cmd.Env, "COMPOSE_BAKE=true") + return cmd +} + func dockerCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd { cmd := exec.Command("docker") cmd.Env = os.Environ() diff --git a/tests/integration_test.go b/tests/integration_test.go index 3884ca684430..44375b45a4c7 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -32,6 +32,7 @@ func TestIntegration(t *testing.T) { tests = append(tests, createTests...) tests = append(tests, rmTests...) tests = append(tests, dialstdioTests...) + tests = append(tests, composeTests...) testIntegration(t, tests...) }