Browse code

Builder: Check remote when local img platform doesn't match

This fixes an issue where if a build requests an image that already
exists in Docker's image store but does not match the specified build
platform, instead of building using the wrong platform go ahead and
check the remote for the correct platform.

Steps to reproduce issue:

```terminal
$ docker pull --platform=amd64 debian:buster
<output supressed>
$ cat Dockerfile
FROM debian:buster
RUN echo hello
$ docker build --platform=armhf -< Dockerfile
<output supressed>
```

Without this fix, the build invokcation will build using the amd64 image
since it is already tagged locally, but this is clearly not what we
want.

With the fix the local image is not used and instead we pull the correct
image.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2020/03/06 15:09:10
Showing 1 changed files
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"fmt"
7 7
 	"io"
8 8
 	"io/ioutil"
9
+	"path"
9 10
 	"runtime"
10 11
 	"sync"
11 12
 	"sync/atomic"
... ...
@@ -41,6 +42,7 @@ import (
41 41
 	"github.com/opencontainers/image-spec/identity"
42 42
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
43 43
 	"github.com/pkg/errors"
44
+	"github.com/sirupsen/logrus"
44 45
 	"golang.org/x/time/rate"
45 46
 )
46 47
 
... ...
@@ -114,7 +116,7 @@ func (is *imageSource) getCredentialsFromSession(ctx context.Context, sm *sessio
114 114
 	}
115 115
 }
116 116
 
117
-func (is *imageSource) resolveLocal(refStr string) ([]byte, error) {
117
+func (is *imageSource) resolveLocal(refStr string) (*image.Image, error) {
118 118
 	ref, err := distreference.ParseNormalizedNamed(refStr)
119 119
 	if err != nil {
120 120
 		return nil, err
... ...
@@ -127,7 +129,7 @@ func (is *imageSource) resolveLocal(refStr string) ([]byte, error) {
127 127
 	if err != nil {
128 128
 		return nil, err
129 129
 	}
130
-	return img.RawJSON(), nil
130
+	return img, nil
131 131
 }
132 132
 
133 133
 func (is *imageSource) resolveRemote(ctx context.Context, ref string, platform *ocispec.Platform, sm *session.Manager) (digest.Digest, []byte, error) {
... ...
@@ -174,9 +176,16 @@ func (is *imageSource) ResolveImageConfig(ctx context.Context, ref string, opt l
174 174
 		// default == prefer local, but in the future could be smarter
175 175
 		fallthrough
176 176
 	case source.ResolveModePreferLocal:
177
-		dt, err := is.resolveLocal(ref)
177
+		img, err := is.resolveLocal(ref)
178 178
 		if err == nil {
179
-			return "", dt, err
179
+			if img.Architecture != opt.Platform.Architecture || img.Variant != opt.Platform.Variant || img.OS != opt.Platform.OS {
180
+				logrus.WithField("ref", ref).Debugf("Requested build platform %s does not match local image platform %s, checking remote",
181
+					path.Join(opt.Platform.OS, opt.Platform.Architecture, opt.Platform.Variant),
182
+					path.Join(img.OS, img.Architecture, img.Variant),
183
+				)
184
+			} else {
185
+				return "", img.RawJSON(), err
186
+			}
180 187
 		}
181 188
 		// fallback to remote
182 189
 		return is.resolveRemote(ctx, ref, opt.Platform, sm)
... ...
@@ -261,9 +270,17 @@ func (p *puller) resolveLocal() {
261 261
 		}
262 262
 
263 263
 		if p.src.ResolveMode == source.ResolveModeDefault || p.src.ResolveMode == source.ResolveModePreferLocal {
264
-			dt, err := p.is.resolveLocal(p.src.Reference.String())
264
+			ref := p.src.Reference.String()
265
+			img, err := p.is.resolveLocal(ref)
265 266
 			if err == nil {
266
-				p.config = dt
267
+				if img.Architecture != p.platform.Architecture || img.Variant != p.platform.Variant || img.OS != p.platform.OS {
268
+					logrus.WithField("ref", ref).Debugf("Requested build platform %s does not match local image platform %s, not resolving",
269
+						path.Join(p.platform.OS, p.platform.Architecture, p.platform.Variant),
270
+						path.Join(img.OS, img.Architecture, img.Variant),
271
+					)
272
+				} else {
273
+					p.config = img.RawJSON()
274
+				}
267 275
 			}
268 276
 		}
269 277
 	})