Explorar el Código

don't push images at the end of multi-arch build (and simplify e2e tests)
support DOCKER_DEFAULT_PLATFORM when 'compose up --build'
add tests to check behaviour when DOCKER_DEFAULT_PLATFORM is defined

Signed-off-by: Guillaume Lours <[email protected]>

Guillaume Lours hace 3 años
padre
commit
e016faac33

+ 26 - 12
pkg/compose/build.go

@@ -83,10 +83,8 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 		}
 		if len(buildOptions.Platforms) > 1 {
 			buildOptions.Exports = []bclient.ExportEntry{{
-				Type: "image",
-				Attrs: map[string]string{
-					"push": "true",
-				},
+				Type:  "image",
+				Attrs: map[string]string{},
 			}}
 		}
 		opts[imageName] = buildOptions
@@ -177,7 +175,9 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
 						"load": "true",
 					},
 				}}
-				opt.Platforms = []specs.Platform{}
+				if opt.Platforms, err = useDockerDefaultPlatform(project, service.Build.Platforms); err != nil {
+					opt.Platforms = []specs.Platform{}
+				}
 			}
 			opts[imageName] = opt
 			continue
@@ -360,14 +360,11 @@ func addSecretsConfig(project *types.Project, service types.ServiceConfig) (sess
 }
 
 func addPlatforms(project *types.Project, service types.ServiceConfig) ([]specs.Platform, error) {
-	var plats []specs.Platform
-	if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
-		p, err := platforms.Parse(platform)
-		if err != nil {
-			return nil, err
-		}
-		plats = append(plats, p)
+	plats, err := useDockerDefaultPlatform(project, service.Build.Platforms)
+	if err != nil {
+		return nil, err
 	}
+
 	if service.Platform != "" && !utils.StringContains(service.Build.Platforms, service.Platform) {
 		return nil, fmt.Errorf("service.platform should be part of the service.build.platforms: %q", service.Platform)
 	}
@@ -377,6 +374,23 @@ func addPlatforms(project *types.Project, service types.ServiceConfig) ([]specs.
 		if err != nil {
 			return nil, err
 		}
+		if !utils.Contains(plats, p) {
+			plats = append(plats, p)
+		}
+	}
+	return plats, nil
+}
+
+func useDockerDefaultPlatform(project *types.Project, platformList types.StringList) ([]specs.Platform, error) {
+	var plats []specs.Platform
+	if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
+		if !utils.StringContains(platformList, platform) {
+			return nil, fmt.Errorf("the DOCKER_DEFAULT_PLATFORM value should be part of the service.build.platforms: %q", platform)
+		}
+		p, err := platforms.Parse(platform)
+		if err != nil {
+			return nil, err
+		}
 		plats = append(plats, p)
 	}
 	return plats, nil

+ 3 - 2
pkg/compose/build_buildkit.go

@@ -102,9 +102,10 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
 				continue
 			}
 		}
-		f = driver.GetFactory(ng.Driver, true)
 		if f == nil {
-			return nil, fmt.Errorf("failed to find buildx driver %q", ng.Driver)
+			if f = driver.GetFactory(ng.Driver, true); f == nil {
+				return nil, fmt.Errorf("failed to find buildx driver %q", ng.Driver)
+			}
 		}
 	} else {
 		ep := ng.Nodes[0].Endpoint

+ 30 - 21
pkg/e2e/build_test.go

@@ -248,19 +248,12 @@ func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
 	c := NewParallelCLI(t)
 
 	// declare builder
-	result := c.RunDockerCmd(t, "buildx", "create", "--name", "build-platform", "--use", "--bootstrap", "--driver-opt",
-		"network=host", "--buildkitd-flags", "--allow-insecure-entitlement network.host")
-	assert.NilError(t, result.Error)
-
-	// start local registry
-	result = c.RunDockerCmd(t, "run", "-d", "-p", "5001:5000", "--restart=always",
-		"--name", "registry", "registry:2")
+	result := c.RunDockerCmd(t, "buildx", "create", "--name", "build-platform", "--use", "--bootstrap")
 	assert.NilError(t, result.Error)
 
 	t.Cleanup(func() {
 		c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test/platforms", "down")
 		_ = c.RunDockerCmd(t, "buildx", "rm", "-f", "build-platform")
-		_ = c.RunDockerCmd(t, "rm", "-f", "registry")
 	})
 
 	t.Run("platform not supported by builder", func(t *testing.T) {
@@ -275,9 +268,8 @@ func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
 	t.Run("multi-arch build ok", func(t *testing.T) {
 		res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms", "build")
 		assert.NilError(t, res.Error, res.Stderr())
-		res = c.RunDockerCmd(t, "manifest", "inspect", "--insecure", "localhost:5001/build-test-platform:test")
-		res.Assert(t, icmd.Expected{Out: `"architecture": "amd64",`})
-		res.Assert(t, icmd.Expected{Out: `"architecture": "arm64",`})
+		res.Assert(t, icmd.Expected{Out: "I am building for linux/arm64"})
+		res.Assert(t, icmd.Expected{Out: "I am building for linux/amd64"})
 
 	})
 
@@ -285,16 +277,12 @@ func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
 		res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms",
 			"-f", "fixtures/build-test/platforms/compose-multiple-platform-builds.yaml", "build")
 		assert.NilError(t, res.Error, res.Stderr())
-		res = c.RunDockerCmd(t, "manifest", "inspect", "--insecure", "localhost:5001/build-test-platform-a:test")
-		res.Assert(t, icmd.Expected{Out: `"architecture": "amd64",`})
-		res.Assert(t, icmd.Expected{Out: `"architecture": "arm64",`})
-		res = c.RunDockerCmd(t, "manifest", "inspect", "--insecure", "localhost:5001/build-test-platform-b:test")
-		res.Assert(t, icmd.Expected{Out: `"architecture": "amd64",`})
-		res.Assert(t, icmd.Expected{Out: `"architecture": "arm64",`})
-		res = c.RunDockerCmd(t, "manifest", "inspect", "--insecure", "localhost:5001/build-test-platform-c:test")
-		res.Assert(t, icmd.Expected{Out: `"architecture": "amd64",`})
-		res.Assert(t, icmd.Expected{Out: `"architecture": "arm64",`})
-
+		res.Assert(t, icmd.Expected{Out: "I'm Service A and I am building for linux/arm64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service A and I am building for linux/amd64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service B and I am building for linux/arm64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service B and I am building for linux/amd64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service C and I am building for linux/arm64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service C and I am building for linux/amd64"})
 	})
 
 	t.Run("multi-arch up --build", func(t *testing.T) {
@@ -302,6 +290,16 @@ func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
 		assert.NilError(t, res.Error, res.Stderr())
 		res.Assert(t, icmd.Expected{Out: "platforms-platforms-1 exited with code 0"})
 	})
+
+	t.Run("use DOCKER_DEFAULT_PLATFORM value when up --build", func(t *testing.T) {
+		cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/platforms", "up", "--build")
+		res := icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
+			cmd.Env = append(cmd.Env, "DOCKER_DEFAULT_PLATFORM=linux/amd64")
+		})
+		assert.NilError(t, res.Error, res.Stderr())
+		res.Assert(t, icmd.Expected{Out: "I am building for linux/amd64"})
+		assert.Assert(t, !strings.Contains(res.Stdout(), "I am building for linux/arm64"))
+	})
 }
 
 func TestBuildPlatformsStandardErrors(t *testing.T) {
@@ -335,4 +333,15 @@ func TestBuildPlatformsStandardErrors(t *testing.T) {
 			Err:      `service.platform should be part of the service.build.platforms: "linux/riscv64"`,
 		})
 	})
+
+	t.Run("DOCKER_DEFAULT_PLATFORM value not defined in platforms build section", func(t *testing.T) {
+		cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/platforms", "build")
+		res := icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
+			cmd.Env = append(cmd.Env, "DOCKER_DEFAULT_PLATFORM=windows/amd64")
+		})
+		res.Assert(t, icmd.Expected{
+			ExitCode: 1,
+			Err:      `DOCKER_DEFAULT_PLATFORM value should be part of the service.build.platforms: "windows/amd64"`,
+		})
+	})
 }

+ 1 - 1
pkg/e2e/fixtures/build-test/platforms/Dockerfile

@@ -16,7 +16,7 @@ FROM --platform=$BUILDPLATFORM golang:alpine AS build
 
 ARG TARGETPLATFORM
 ARG BUILDPLATFORM
-RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
+RUN echo "I am building for $TARGETPLATFORM, running on $BUILDPLATFORM" > /log
 
 FROM alpine
 COPY --from=build /log /log

+ 3 - 3
pkg/e2e/fixtures/build-test/platforms/compose-multiple-platform-builds.yaml

@@ -1,20 +1,20 @@
 services:
   serviceA:
-    image: localhost:5001/build-test-platform-a:test
+    image: build-test-platform-a:test
     build:
       context: ./contextServiceA
       platforms:
         - linux/amd64
         - linux/arm64
   serviceB:
-    image: localhost:5001/build-test-platform-b:test
+    image: build-test-platform-b:test
     build:
       context: ./contextServiceB
       platforms:
         - linux/amd64
         - linux/arm64
   serviceC:
-    image: localhost:5001/build-test-platform-c:test
+    image: build-test-platform-c:test
     build:
       context: ./contextServiceC
       platforms:

+ 1 - 1
pkg/e2e/fixtures/build-test/platforms/compose.yaml

@@ -1,6 +1,6 @@
 services:
   platforms:
-    image: localhost:5001/build-test-platform:test
+    image: build-test-platform:test
     build:
       context: .
       platforms:

+ 1 - 1
pkg/e2e/fixtures/build-test/platforms/contextServiceA/Dockerfile

@@ -16,7 +16,7 @@ FROM --platform=$BUILDPLATFORM golang:alpine AS build
 
 ARG TARGETPLATFORM
 ARG BUILDPLATFORM
-RUN echo "I'm Service A and I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
+RUN echo "I'm Service A and I am building for $TARGETPLATFORM, running on $BUILDPLATFORM" > /log
 
 FROM alpine
 COPY --from=build /log /log

+ 1 - 1
pkg/e2e/fixtures/build-test/platforms/contextServiceB/Dockerfile

@@ -16,7 +16,7 @@ FROM --platform=$BUILDPLATFORM golang:alpine AS build
 
 ARG TARGETPLATFORM
 ARG BUILDPLATFORM
-RUN echo "I'm Service B and I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
+RUN echo "I'm Service B and I am building for $TARGETPLATFORM, running on $BUILDPLATFORM" > /log
 
 FROM alpine
 COPY --from=build /log /log

+ 1 - 1
pkg/e2e/fixtures/build-test/platforms/contextServiceC/Dockerfile

@@ -16,7 +16,7 @@ FROM --platform=$BUILDPLATFORM golang:alpine AS build
 
 ARG TARGETPLATFORM
 ARG BUILDPLATFORM
-RUN echo "I'm Service C and I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
+RUN echo "I'm Service C and I am building for $TARGETPLATFORM, running on $BUILDPLATFORM" > /log
 
 FROM alpine
 COPY --from=build /log /log

+ 30 - 0
pkg/utils/slices.go

@@ -0,0 +1,30 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package utils
+
+import "reflect"
+
+// Contains helps to detect if a non-comparable struct is part of an array
+// only use this method if you can't rely on existing golang Contains function of slices (https://pkg.go.dev/golang.org/x/exp/slices#Contains)
+func Contains[T any](origin []T, element T) bool {
+	for _, v := range origin {
+		if reflect.DeepEqual(v, element) {
+			return true
+		}
+	}
+	return false
+}

+ 95 - 0
pkg/utils/slices_test.go

@@ -0,0 +1,95 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package utils
+
+import (
+	"testing"
+
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+func TestContains(t *testing.T) {
+	source := []specs.Platform{
+		{
+			Architecture: "linux/amd64",
+			OS:           "darwin",
+			OSVersion:    "",
+			OSFeatures:   nil,
+			Variant:      "",
+		},
+		{
+			Architecture: "linux/arm64",
+			OS:           "linux",
+			OSVersion:    "12",
+			OSFeatures:   nil,
+			Variant:      "v8",
+		},
+		{
+			Architecture: "",
+			OS:           "",
+			OSVersion:    "",
+			OSFeatures:   nil,
+			Variant:      "",
+		},
+	}
+
+	type args struct {
+		origin  []specs.Platform
+		element specs.Platform
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "element found",
+			args: args{
+				origin: source,
+				element: specs.Platform{
+					Architecture: "linux/arm64",
+					OS:           "linux",
+					OSVersion:    "12",
+					OSFeatures:   nil,
+					Variant:      "v8",
+				},
+			},
+			want: true,
+		},
+		{
+			name: "element not found",
+			args: args{
+				origin: source,
+				element: specs.Platform{
+					Architecture: "linux/arm64",
+					OS:           "darwin",
+					OSVersion:    "12",
+					OSFeatures:   nil,
+					Variant:      "v8",
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := Contains(tt.args.origin, tt.args.element); got != tt.want {
+				t.Errorf("Contains() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}