Skip to content

Commit

Permalink
don't push images at the end of multi-arch build (and simplify e2e te…
Browse files Browse the repository at this point in the history
…sts)

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>
  • Loading branch information
glours committed Aug 31, 2022
1 parent 03810d4 commit f900a64
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 43 deletions.
38 changes: 26 additions & 12 deletions pkg/compose/build.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions pkg/compose/build_buildkit.go
Expand Up @@ -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
Expand Down
51 changes: 30 additions & 21 deletions pkg/e2e/build_test.go
Expand Up @@ -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) {
Expand All @@ -275,33 +268,38 @@ 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"})

})

t.Run("multi-arch multi service builds ok", func(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) {
res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms", "up", "--build")
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) {
Expand Down Expand Up @@ -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"`,
})
})
}
2 changes: 1 addition & 1 deletion pkg/e2e/fixtures/build-test/platforms/Dockerfile
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion 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:
Expand Down
Expand Up @@ -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
Expand Up @@ -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
Expand Up @@ -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 changes: 30 additions & 0 deletions 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
}
94 changes: 94 additions & 0 deletions pkg/utils/slices_test.go
@@ -0,0 +1,94 @@
/*
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 (
specs "github.com/opencontainers/image-spec/specs-go/v1"
"testing"
)

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)
}
})
}
}

0 comments on commit f900a64

Please sign in to comment.