mirror of https://github.com/docker/compose.git
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 <705411+glours@users.noreply.github.com>
This commit is contained in:
parent
8ed2d8ad07
commit
e016faac33
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,6 +1,6 @@
|
|||
services:
|
||||
platforms:
|
||||
image: localhost:5001/build-test-platform:test
|
||||
image: build-test-platform:test
|
||||
build:
|
||||
context: .
|
||||
platforms:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue