Browse code

distribution: fix passing platform struct to puller

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2018/06/27 06:49:33
Showing 19 changed files
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/docker/docker/api/types/filters"
9 9
 	"github.com/docker/docker/api/types/image"
10 10
 	"github.com/docker/docker/api/types/registry"
11
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
11 12
 )
12 13
 
13 14
 // Backend is all the methods that need to be implemented
... ...
@@ -34,7 +35,7 @@ type importExportBackend interface {
34 34
 }
35 35
 
36 36
 type registryBackend interface {
37
-	PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
37
+	PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
38 38
 	PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
39 39
 	SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
40 40
 }
... ...
@@ -35,7 +35,7 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
35 35
 		message  = r.Form.Get("message")
36 36
 		err      error
37 37
 		output   = ioutils.NewWriteFlusher(w)
38
-		platform = &specs.Platform{}
38
+		platform *specs.Platform
39 39
 	)
40 40
 	defer output.Close()
41 41
 
... ...
@@ -72,13 +72,17 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
72 72
 					authConfig = &types.AuthConfig{}
73 73
 				}
74 74
 			}
75
-			err = s.backend.PullImage(ctx, image, tag, platform.OS, metaHeaders, authConfig, output)
75
+			err = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output)
76 76
 		} else { //import
77 77
 			src := r.Form.Get("fromSrc")
78 78
 			// 'err' MUST NOT be defined within this block, we need any error
79 79
 			// generated from the download to be available to the output
80 80
 			// stream processing below
81
-			err = s.backend.ImportImage(src, repo, platform.OS, tag, message, r.Body, output, r.Form["changes"])
81
+			os := ""
82
+			if platform != nil {
83
+				os = platform.OS
84
+			}
85
+			err = s.backend.ImportImage(src, repo, os, tag, message, r.Body, output, r.Form["changes"])
82 86
 		}
83 87
 	}
84 88
 	if err != nil {
... ...
@@ -5,6 +5,7 @@ import (
5 5
 
6 6
 	"github.com/docker/docker/api/types"
7 7
 	"github.com/docker/docker/pkg/streamformatter"
8
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
8 9
 )
9 10
 
10 11
 // PullOption defines different modes for accessing images
... ...
@@ -40,5 +41,5 @@ type GetImageAndLayerOptions struct {
40 40
 	PullOption PullOption
41 41
 	AuthConfig map[string]types.AuthConfig
42 42
 	Output     io.Writer
43
-	OS         string
43
+	Platform   *specs.Platform
44 44
 }
... ...
@@ -96,8 +96,14 @@ func (o *copier) createCopyInstruction(args []string, cmdName string) (copyInstr
96 96
 	last := len(args) - 1
97 97
 
98 98
 	// Work in platform-specific filepath semantics
99
-	inst.dest = fromSlash(args[last], o.platform.OS)
100
-	separator := string(separator(o.platform.OS))
99
+	// TODO: This OS switch for paths is NOT correct and should not be supported.
100
+	// Maintained for backwards compatibility
101
+	pathOS := runtime.GOOS
102
+	if o.platform != nil {
103
+		pathOS = o.platform.OS
104
+	}
105
+	inst.dest = fromSlash(args[last], pathOS)
106
+	separator := string(separator(pathOS))
101 107
 	infos, err := o.getCopyInfosForSourcePaths(args[0:last], inst.dest)
102 108
 	if err != nil {
103 109
 		return inst, errors.Wrapf(err, "%s failed", cmdName)
... ...
@@ -28,6 +28,7 @@ import (
28 28
 	"github.com/moby/buildkit/frontend/dockerfile/instructions"
29 29
 	"github.com/moby/buildkit/frontend/dockerfile/parser"
30 30
 	"github.com/moby/buildkit/frontend/dockerfile/shell"
31
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
31 32
 	"github.com/pkg/errors"
32 33
 )
33 34
 
... ...
@@ -103,7 +104,7 @@ func dispatchAdd(d dispatchRequest, c *instructions.AddCommand) error {
103 103
 	copyInstruction.chownStr = c.Chown
104 104
 	copyInstruction.allowLocalDecompression = true
105 105
 
106
-	return d.builder.performCopy(d.state, copyInstruction)
106
+	return d.builder.performCopy(d, copyInstruction)
107 107
 }
108 108
 
109 109
 // COPY foo /path
... ...
@@ -127,7 +128,7 @@ func dispatchCopy(d dispatchRequest, c *instructions.CopyCommand) error {
127 127
 	}
128 128
 	copyInstruction.chownStr = c.Chown
129 129
 
130
-	return d.builder.performCopy(d.state, copyInstruction)
130
+	return d.builder.performCopy(d, copyInstruction)
131 131
 }
132 132
 
133 133
 func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error) {
... ...
@@ -145,7 +146,7 @@ func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error
145 145
 		imageRefOrID = stage.Image
146 146
 		localOnly = true
147 147
 	}
148
-	return d.builder.imageSources.Get(imageRefOrID, localOnly, d.state.operatingSystem)
148
+	return d.builder.imageSources.Get(imageRefOrID, localOnly, d.builder.options.Platform)
149 149
 }
150 150
 
151 151
 // FROM [--platform=platform] imagename[:tag | @digest] [AS build-stage-name]
... ...
@@ -153,22 +154,21 @@ func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error
153 153
 func initializeStage(d dispatchRequest, cmd *instructions.Stage) error {
154 154
 	d.builder.imageProber.Reset()
155 155
 
156
-	// TODO: pass *platform instead, allow autodetect
157
-	platform := platforms.DefaultSpec()
156
+	var platform *specs.Platform
158 157
 	if v := cmd.Platform; v != "" {
159
-		// TODO:
160
-		// v, err := shlex.ProcessWord(v, toEnvList(metaArgs, nil))
161
-		// if err != nil {
162
-		// 	return nil, nil, errors.Wrapf(err, "failed to process arguments for platform %s", v)
163
-		// }
158
+		v, err := d.getExpandedString(d.shlex, v)
159
+		if err != nil {
160
+			return errors.Wrapf(err, "failed to process arguments for platform %s", v)
161
+		}
164 162
 
165 163
 		p, err := platforms.Parse(v)
166 164
 		if err != nil {
167 165
 			return errors.Wrapf(err, "failed to parse platform %s", v)
168 166
 		}
169
-		platform = p
167
+		platform = &p
170 168
 	}
171
-	image, err := d.getFromImage(d.shlex, cmd.BaseName, platform.OS)
169
+
170
+	image, err := d.getFromImage(d.shlex, cmd.BaseName, platform)
172 171
 	if err != nil {
173 172
 		return err
174 173
 	}
... ...
@@ -214,82 +214,72 @@ func dispatchTriggeredOnBuild(d dispatchRequest, triggers []string) error {
214 214
 	return nil
215 215
 }
216 216
 
217
-func (d *dispatchRequest) getExpandedImageName(shlex *shell.Lex, name string) (string, error) {
217
+func (d *dispatchRequest) getExpandedString(shlex *shell.Lex, str string) (string, error) {
218 218
 	substitutionArgs := []string{}
219 219
 	for key, value := range d.state.buildArgs.GetAllMeta() {
220 220
 		substitutionArgs = append(substitutionArgs, key+"="+value)
221 221
 	}
222 222
 
223
-	name, err := shlex.ProcessWord(name, substitutionArgs)
223
+	name, err := shlex.ProcessWord(str, substitutionArgs)
224 224
 	if err != nil {
225 225
 		return "", err
226 226
 	}
227 227
 	return name, nil
228 228
 }
229 229
 
230
-// getOsFromFlagsAndStage calculates the operating system if we need to pull an image.
231
-// stagePlatform contains the value supplied by optional `--platform=` on
232
-// a current FROM statement. b.builder.options.Platform contains the operating
233
-// system part of the optional flag passed in the API call (or CLI flag
234
-// through `docker build --platform=...`). Precedence is for an explicit
235
-// platform indication in the FROM statement.
236
-func (d *dispatchRequest) getOsFromFlagsAndStage(stageOS string) string {
237
-	switch {
238
-	case stageOS != "":
239
-		return stageOS
240
-	case d.builder.options.Platform.OS != "":
241
-		// Note this is API "platform", but by this point, as the daemon is not
242
-		// multi-arch aware yet, it is guaranteed to only hold the OS part here.
243
-		return d.builder.options.Platform.OS
244
-	default:
245
-		return "" // Auto-select
246
-	}
247
-}
248
-
249
-func (d *dispatchRequest) getImageOrStage(name string, stageOS string) (builder.Image, error) {
230
+func (d *dispatchRequest) getImageOrStage(name string, platform *specs.Platform) (builder.Image, error) {
250 231
 	var localOnly bool
251 232
 	if im, ok := d.stages.getByName(name); ok {
252 233
 		name = im.Image
253 234
 		localOnly = true
254 235
 	}
255 236
 
256
-	os := d.getOsFromFlagsAndStage(stageOS)
237
+	if platform == nil {
238
+		platform = d.builder.options.Platform
239
+	}
257 240
 
258 241
 	// Windows cannot support a container with no base image unless it is LCOW.
259 242
 	if name == api.NoBaseImageSpecifier {
243
+		p := platforms.DefaultSpec()
244
+		if platform != nil {
245
+			p = *platform
246
+		}
260 247
 		imageImage := &image.Image{}
261
-		imageImage.OS = runtime.GOOS
248
+		imageImage.OS = p.OS
249
+
250
+		// old windows scratch handling
251
+		// TODO: scratch should not have an os. It should be nil image.
252
+		// Windows supports scratch. What is not supported is running containers
253
+		// from it.
262 254
 		if runtime.GOOS == "windows" {
263
-			switch os {
264
-			case "windows":
265
-				return nil, errors.New("Windows does not support FROM scratch")
266
-			case "linux", "":
255
+			if platform == nil || platform.OS == "linux" {
267 256
 				if !system.LCOWSupported() {
268 257
 					return nil, errors.New("Linux containers are not supported on this system")
269 258
 				}
270 259
 				imageImage.OS = "linux"
271
-			default:
272
-				return nil, errors.Errorf("operating system %q is not supported", os)
260
+			} else if platform.OS == "windows" {
261
+				return nil, errors.New("Windows does not support FROM scratch")
262
+			} else {
263
+				return nil, errors.Errorf("platform %s is not supported", platforms.Format(p))
273 264
 			}
274 265
 		}
275 266
 		return builder.Image(imageImage), nil
276 267
 	}
277
-	imageMount, err := d.builder.imageSources.Get(name, localOnly, os)
268
+	imageMount, err := d.builder.imageSources.Get(name, localOnly, platform)
278 269
 	if err != nil {
279 270
 		return nil, err
280 271
 	}
281 272
 	return imageMount.Image(), nil
282 273
 }
283
-func (d *dispatchRequest) getFromImage(shlex *shell.Lex, name string, stageOS string) (builder.Image, error) {
284
-	name, err := d.getExpandedImageName(shlex, name)
274
+func (d *dispatchRequest) getFromImage(shlex *shell.Lex, name string, platform *specs.Platform) (builder.Image, error) {
275
+	name, err := d.getExpandedString(shlex, name)
285 276
 	if err != nil {
286 277
 		return nil, err
287 278
 	}
288
-	return d.getImageOrStage(name, stageOS)
279
+	return d.getImageOrStage(name, platform)
289 280
 }
290 281
 
291 282
 func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error {
292
-
293 283
 	d.state.runConfig.OnBuild = append(d.state.runConfig.OnBuild, c.Expression)
294 284
 	return d.builder.commit(d.state, "ONBUILD "+c.Expression)
295 285
 }
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"runtime"
7 7
 	"testing"
8 8
 
9
+	"github.com/containerd/containerd/platforms"
9 10
 	"github.com/docker/docker/api/types"
10 11
 	"github.com/docker/docker/api/types/backend"
11 12
 	"github.com/docker/docker/api/types/container"
... ...
@@ -22,15 +23,17 @@ import (
22 22
 
23 23
 func newBuilderWithMockBackend() *Builder {
24 24
 	mockBackend := &MockBackend{}
25
+	defaultPlatform := platforms.DefaultSpec()
26
+	opts := &types.ImageBuildOptions{Platform: &defaultPlatform}
25 27
 	ctx := context.Background()
26 28
 	b := &Builder{
27
-		options:       &types.ImageBuildOptions{Platform: runtime.GOOS},
29
+		options:       opts,
28 30
 		docker:        mockBackend,
29 31
 		Stdout:        new(bytes.Buffer),
30 32
 		clientCtx:     ctx,
31 33
 		disableCommit: true,
32 34
 		imageSources: newImageSources(ctx, builderOptions{
33
-			Options: &types.ImageBuildOptions{Platform: runtime.GOOS},
35
+			Options: opts,
34 36
 			Backend: mockBackend,
35 37
 		}),
36 38
 		imageProber:      newImageProber(mockBackend, nil, false),
... ...
@@ -7,11 +7,12 @@ import (
7 7
 	"github.com/docker/docker/api/types/backend"
8 8
 	"github.com/docker/docker/builder"
9 9
 	dockerimage "github.com/docker/docker/image"
10
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
10 11
 	"github.com/pkg/errors"
11 12
 	"github.com/sirupsen/logrus"
12 13
 )
13 14
 
14
-type getAndMountFunc func(string, bool, string) (builder.Image, builder.ROLayer, error)
15
+type getAndMountFunc func(string, bool, *specs.Platform) (builder.Image, builder.ROLayer, error)
15 16
 
16 17
 // imageSources mounts images and provides a cache for mounted images. It tracks
17 18
 // all images so they can be unmounted at the end of the build.
... ...
@@ -22,7 +23,7 @@ type imageSources struct {
22 22
 }
23 23
 
24 24
 func newImageSources(ctx context.Context, options builderOptions) *imageSources {
25
-	getAndMount := func(idOrRef string, localOnly bool, osForPull string) (builder.Image, builder.ROLayer, error) {
25
+	getAndMount := func(idOrRef string, localOnly bool, platform *specs.Platform) (builder.Image, builder.ROLayer, error) {
26 26
 		pullOption := backend.PullOptionNoPull
27 27
 		if !localOnly {
28 28
 			if options.Options.PullParent {
... ...
@@ -35,7 +36,7 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
35 35
 			PullOption: pullOption,
36 36
 			AuthConfig: options.Options.AuthConfigs,
37 37
 			Output:     options.ProgressWriter.Output,
38
-			OS:         osForPull,
38
+			Platform:   platform,
39 39
 		})
40 40
 	}
41 41
 
... ...
@@ -45,12 +46,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
45 45
 	}
46 46
 }
47 47
 
48
-func (m *imageSources) Get(idOrRef string, localOnly bool, osForPull string) (*imageMount, error) {
48
+func (m *imageSources) Get(idOrRef string, localOnly bool, platform *specs.Platform) (*imageMount, error) {
49 49
 	if im, ok := m.byImageID[idOrRef]; ok {
50 50
 		return im, nil
51 51
 	}
52 52
 
53
-	image, layer, err := m.getImage(idOrRef, localOnly, osForPull)
53
+	image, layer, err := m.getImage(idOrRef, localOnly, platform)
54 54
 	if err != nil {
55 55
 		return nil, err
56 56
 	}
... ...
@@ -150,7 +150,8 @@ func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, paren
150 150
 	return nil
151 151
 }
152 152
 
153
-func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error {
153
+func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error {
154
+	state := req.state
154 155
 	srcHash := getSourceHashFromInfos(inst.infos)
155 156
 
156 157
 	var chownComment string
... ...
@@ -168,7 +169,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
168 168
 		return err
169 169
 	}
170 170
 
171
-	imageMount, err := b.imageSources.Get(state.imageID, true, state.operatingSystem)
171
+	imageMount, err := b.imageSources.Get(state.imageID, true, req.builder.options.Platform)
172 172
 	if err != nil {
173 173
 		return errors.Wrapf(err, "failed to get destination image %q", state.imageID)
174 174
 	}
... ...
@@ -456,7 +457,7 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf
456 456
 	// is too small for builder scenarios where many users are
457 457
 	// using RUN statements to install large amounts of data.
458 458
 	// Use 127GB as that's the default size of a VHD in Hyper-V.
459
-	if runtime.GOOS == "windows" && options.Platform.OS == "windows" {
459
+	if runtime.GOOS == "windows" && options.Platform != nil && options.Platform.OS == "windows" {
460 460
 		hc.StorageOpt = make(map[string]string)
461 461
 		hc.StorageOpt["size"] = "127GB"
462 462
 	}
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/docker/libnetwork/cluster"
24 24
 	networktypes "github.com/docker/libnetwork/types"
25 25
 	"github.com/docker/swarmkit/agent/exec"
26
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
26 27
 )
27 28
 
28 29
 // Backend defines the executor component for a swarm agent.
... ...
@@ -69,7 +70,7 @@ type VolumeBackend interface {
69 69
 
70 70
 // ImageBackend is used by an executor to perform image operations
71 71
 type ImageBackend interface {
72
-	PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
72
+	PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
73 73
 	GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, bool, error)
74 74
 	LookupImage(name string) (*types.ImageInspect, error)
75 75
 }
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"fmt"
9 9
 	"io"
10 10
 	"os"
11
-	"runtime"
12 11
 	"strings"
13 12
 	"syscall"
14 13
 	"time"
... ...
@@ -97,8 +96,7 @@ func (c *containerAdapter) pullImage(ctx context.Context) error {
97 97
 	go func() {
98 98
 		// TODO @jhowardmsft LCOW Support: This will need revisiting as
99 99
 		// the stack is built up to include LCOW support for swarm.
100
-		platform := runtime.GOOS
101
-		err := c.imageBackend.PullImage(ctx, c.container.image(), "", platform, metaHeaders, authConfig, pw)
100
+		err := c.imageBackend.PullImage(ctx, c.container.image(), "", nil, metaHeaders, authConfig, pw)
102 101
 		pw.CloseWithError(err)
103 102
 	}()
104 103
 
... ...
@@ -3,6 +3,7 @@ package images // import "github.com/docker/docker/daemon/images"
3 3
 import (
4 4
 	"context"
5 5
 	"io"
6
+	"runtime"
6 7
 
7 8
 	"github.com/docker/distribution/reference"
8 9
 	"github.com/docker/docker/api/types"
... ...
@@ -14,6 +15,7 @@ import (
14 14
 	"github.com/docker/docker/pkg/stringid"
15 15
 	"github.com/docker/docker/pkg/system"
16 16
 	"github.com/docker/docker/registry"
17
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
17 18
 	"github.com/pkg/errors"
18 19
 )
19 20
 
... ...
@@ -137,7 +139,7 @@ func newROLayerForImage(img *image.Image, layerStore layer.Store) (builder.ROLay
137 137
 }
138 138
 
139 139
 // TODO: could this use the regular daemon PullImage ?
140
-func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer, os string) (*image.Image, error) {
140
+func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer, platform *specs.Platform) (*image.Image, error) {
141 141
 	ref, err := reference.ParseNormalizedNamed(name)
142 142
 	if err != nil {
143 143
 		return nil, err
... ...
@@ -156,7 +158,7 @@ func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConf
156 156
 		pullRegistryAuth = &resolvedConfig
157 157
 	}
158 158
 
159
-	if err := i.pullImageWithReference(ctx, ref, os, nil, pullRegistryAuth, output); err != nil {
159
+	if err := i.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
160 160
 		return nil, err
161 161
 	}
162 162
 	return i.GetImage(name)
... ...
@@ -167,10 +169,14 @@ func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConf
167 167
 // leaking of layers.
168 168
 func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) {
169 169
 	if refOrID == "" { // ie FROM scratch
170
-		if !system.IsOSSupported(opts.OS) {
170
+		os := runtime.GOOS
171
+		if opts.Platform != nil {
172
+			os = opts.Platform.OS
173
+		}
174
+		if !system.IsOSSupported(os) {
171 175
 			return nil, nil, system.ErrNotSupportedOperatingSystem
172 176
 		}
173
-		layer, err := newROLayerForImage(nil, i.layerStores[opts.OS])
177
+		layer, err := newROLayerForImage(nil, i.layerStores[os])
174 178
 		return nil, layer, err
175 179
 	}
176 180
 
... ...
@@ -189,7 +195,7 @@ func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID s
189 189
 		}
190 190
 	}
191 191
 
192
-	image, err := i.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.OS)
192
+	image, err := i.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
193 193
 	if err != nil {
194 194
 		return nil, nil, err
195 195
 	}
... ...
@@ -15,11 +15,12 @@ import (
15 15
 	"github.com/docker/docker/pkg/progress"
16 16
 	"github.com/docker/docker/registry"
17 17
 	"github.com/opencontainers/go-digest"
18
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
18 19
 )
19 20
 
20 21
 // PullImage initiates a pull operation. image is the repository name to pull, and
21 22
 // tag may be either empty, or indicate a specific tag to pull.
22
-func (i *ImageService) PullImage(ctx context.Context, image, tag, os string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
23
+func (i *ImageService) PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
23 24
 	start := time.Now()
24 25
 	// Special case: "pull -a" may send an image name with a
25 26
 	// trailing :. This is ugly, but let's not break API
... ...
@@ -45,12 +46,12 @@ func (i *ImageService) PullImage(ctx context.Context, image, tag, os string, met
45 45
 		}
46 46
 	}
47 47
 
48
-	err = i.pullImageWithReference(ctx, ref, os, metaHeaders, authConfig, outStream)
48
+	err = i.pullImageWithReference(ctx, ref, platform, metaHeaders, authConfig, outStream)
49 49
 	imageActions.WithValues("pull").UpdateSince(start)
50 50
 	return err
51 51
 }
52 52
 
53
-func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference.Named, os string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
53
+func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference.Named, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
54 54
 	// Include a buffer so that slow client connections don't affect
55 55
 	// transfer performance.
56 56
 	progressChan := make(chan progress.Progress, 100)
... ...
@@ -77,7 +78,7 @@ func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference
77 77
 		},
78 78
 		DownloadManager: i.downloadManager,
79 79
 		Schema2Types:    distribution.ImageTypes,
80
-		OS:              os,
80
+		Platform:        platform,
81 81
 	}
82 82
 
83 83
 	err := distribution.Pull(ctx, ref, imagePullConfig)
... ...
@@ -60,9 +60,8 @@ type ImagePullConfig struct {
60 60
 	// Schema2Types is the valid schema2 configuration types allowed
61 61
 	// by the pull operation.
62 62
 	Schema2Types []string
63
-	// OS is the requested operating system of the image being pulled to ensure it can be validated
64
-	// when the host OS supports multiple image operating systems.
65
-	OS string
63
+	// Platform is the requested platform of the image being pulled
64
+	Platform *specs.Platform
66 65
 }
67 66
 
68 67
 // ImagePushConfig stores push configuration.
... ...
@@ -171,7 +170,7 @@ func (s *imageConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error)
171 171
 	if !system.IsOSSupported(os) {
172 172
 		return nil, system.ErrNotSupportedOperatingSystem
173 173
 	}
174
-	return &specs.Platform{OS: os, OSVersion: unmarshalledConfig.OSVersion}, nil
174
+	return &specs.Platform{OS: os, Architecture: unmarshalledConfig.Architecture, OSVersion: unmarshalledConfig.OSVersion}, nil
175 175
 }
176 176
 
177 177
 type storeLayerProvider struct {
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	refstore "github.com/docker/docker/reference"
12 12
 	"github.com/docker/docker/registry"
13 13
 	"github.com/opencontainers/go-digest"
14
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
14 15
 	"github.com/pkg/errors"
15 16
 	"github.com/sirupsen/logrus"
16 17
 )
... ...
@@ -20,7 +21,7 @@ type Puller interface {
20 20
 	// Pull tries to pull the image referenced by `tag`
21 21
 	// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
22 22
 	//
23
-	Pull(ctx context.Context, ref reference.Named, os string) error
23
+	Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) error
24 24
 }
25 25
 
26 26
 // newPuller returns a Puller interface that will pull from either a v1 or v2
... ...
@@ -114,7 +115,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
114 114
 			continue
115 115
 		}
116 116
 
117
-		if err := puller.Pull(ctx, ref, imagePullConfig.OS); err != nil {
117
+		if err := puller.Pull(ctx, ref, imagePullConfig.Platform); err != nil {
118 118
 			// Was this pull cancelled? If so, don't try to fall
119 119
 			// back.
120 120
 			fallback := false
... ...
@@ -25,6 +25,7 @@ import (
25 25
 	"github.com/docker/docker/pkg/progress"
26 26
 	"github.com/docker/docker/pkg/stringid"
27 27
 	"github.com/docker/docker/registry"
28
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
28 29
 	"github.com/sirupsen/logrus"
29 30
 )
30 31
 
... ...
@@ -36,7 +37,7 @@ type v1Puller struct {
36 36
 	session     *registry.Session
37 37
 }
38 38
 
39
-func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, os string) error {
39
+func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, _ *specs.Platform) error {
40 40
 	if _, isCanonical := ref.(reference.Canonical); isCanonical {
41 41
 		// Allowing fallback, because HTTPS v1 is before HTTP v2
42 42
 		return fallbackError{err: ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"runtime"
12 12
 	"strings"
13 13
 
14
+	"github.com/containerd/containerd/platforms"
14 15
 	"github.com/docker/distribution"
15 16
 	"github.com/docker/distribution/manifest/manifestlist"
16 17
 	"github.com/docker/distribution/manifest/schema1"
... ...
@@ -63,7 +64,7 @@ type v2Puller struct {
63 63
 	confirmedV2 bool
64 64
 }
65 65
 
66
-func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, os string) (err error) {
66
+func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) (err error) {
67 67
 	// TODO(tiborvass): was ReceiveTimeout
68 68
 	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
69 69
 	if err != nil {
... ...
@@ -71,7 +72,7 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, os string) (er
71 71
 		return err
72 72
 	}
73 73
 
74
-	if err = p.pullV2Repository(ctx, ref, os); err != nil {
74
+	if err = p.pullV2Repository(ctx, ref, platform); err != nil {
75 75
 		if _, ok := err.(fallbackError); ok {
76 76
 			return err
77 77
 		}
... ...
@@ -86,10 +87,10 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, os string) (er
86 86
 	return err
87 87
 }
88 88
 
89
-func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named, os string) (err error) {
89
+func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named, platform *specs.Platform) (err error) {
90 90
 	var layersDownloaded bool
91 91
 	if !reference.IsNameOnly(ref) {
92
-		layersDownloaded, err = p.pullV2Tag(ctx, ref, os)
92
+		layersDownloaded, err = p.pullV2Tag(ctx, ref, platform)
93 93
 		if err != nil {
94 94
 			return err
95 95
 		}
... ...
@@ -111,7 +112,7 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named, os
111 111
 			if err != nil {
112 112
 				return err
113 113
 			}
114
-			pulledNew, err := p.pullV2Tag(ctx, tagRef, os)
114
+			pulledNew, err := p.pullV2Tag(ctx, tagRef, platform)
115 115
 			if err != nil {
116 116
 				// Since this is the pull-all-tags case, don't
117 117
 				// allow an error pulling a particular tag to
... ...
@@ -327,7 +328,7 @@ func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) {
327 327
 	ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()})
328 328
 }
329 329
 
330
-func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, os string) (tagUpdated bool, err error) {
330
+func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform *specs.Platform) (tagUpdated bool, err error) {
331 331
 	manSvc, err := p.repo.Manifests(ctx)
332 332
 	if err != nil {
333 333
 		return false, err
... ...
@@ -391,17 +392,17 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, os string
391 391
 		if p.config.RequireSchema2 {
392 392
 			return false, fmt.Errorf("invalid manifest: not schema2")
393 393
 		}
394
-		id, manifestDigest, err = p.pullSchema1(ctx, ref, v, os)
394
+		id, manifestDigest, err = p.pullSchema1(ctx, ref, v, platform)
395 395
 		if err != nil {
396 396
 			return false, err
397 397
 		}
398 398
 	case *schema2.DeserializedManifest:
399
-		id, manifestDigest, err = p.pullSchema2(ctx, ref, v, os)
399
+		id, manifestDigest, err = p.pullSchema2(ctx, ref, v, platform)
400 400
 		if err != nil {
401 401
 			return false, err
402 402
 		}
403 403
 	case *manifestlist.DeserializedManifestList:
404
-		id, manifestDigest, err = p.pullManifestList(ctx, ref, v, os)
404
+		id, manifestDigest, err = p.pullManifestList(ctx, ref, v, platform)
405 405
 		if err != nil {
406 406
 			return false, err
407 407
 		}
... ...
@@ -437,7 +438,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, os string
437 437
 	return true, nil
438 438
 }
439 439
 
440
-func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, requestedOS string) (id digest.Digest, manifestDigest digest.Digest, err error) {
440
+func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
441 441
 	var verifiedManifest *schema1.Manifest
442 442
 	verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
443 443
 	if err != nil {
... ...
@@ -513,7 +514,10 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
513 513
 	// we support the operating system, switch to that operating system.
514 514
 	// eg FROM supertest2014/nyan with no platform specifier, and docker build
515 515
 	// with no --platform= flag under LCOW.
516
-	if requestedOS == "" && system.IsOSSupported(configOS) {
516
+	requestedOS := ""
517
+	if platform != nil {
518
+		requestedOS = platform.OS
519
+	} else if system.IsOSSupported(configOS) {
517 520
 		requestedOS = configOS
518 521
 	}
519 522
 
... ...
@@ -544,7 +548,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
544 544
 	return imageID, manifestDigest, nil
545 545
 }
546 546
 
547
-func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest, requestedOS string) (id digest.Digest, manifestDigest digest.Digest, err error) {
547
+func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
548 548
 	manifestDigest, err = schema2ManifestDigest(ref, mfst)
549 549
 	if err != nil {
550 550
 		return "", "", err
... ...
@@ -600,6 +604,11 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
600 600
 		configPlatform   *specs.Platform // for LCOW when registering downloaded layers
601 601
 	)
602 602
 
603
+	layerStoreOS := runtime.GOOS
604
+	if platform != nil {
605
+		layerStoreOS = platform.OS
606
+	}
607
+
603 608
 	// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
604 609
 	// explicitly blocking images intended for linux from the Windows daemon. On
605 610
 	// Windows, we do this before the attempt to download, effectively serialising
... ...
@@ -623,13 +632,14 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
623 623
 		if len(descriptors) != len(configRootFS.DiffIDs) {
624 624
 			return "", "", errRootFSMismatch
625 625
 		}
626
-
627
-		// Early bath if the requested OS doesn't match that of the configuration.
628
-		// This avoids doing the download, only to potentially fail later.
629
-		if !system.IsOSSupported(configPlatform.OS) {
630
-			return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configPlatform.OS, requestedOS)
626
+		if platform == nil {
627
+			// Early bath if the requested OS doesn't match that of the configuration.
628
+			// This avoids doing the download, only to potentially fail later.
629
+			if !system.IsOSSupported(configPlatform.OS) {
630
+				return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configPlatform.OS, layerStoreOS)
631
+			}
632
+			layerStoreOS = configPlatform.OS
631 633
 		}
632
-		requestedOS = configPlatform.OS
633 634
 
634 635
 		// Populate diff ids in descriptors to avoid downloading foreign layers
635 636
 		// which have been side loaded
... ...
@@ -638,10 +648,6 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
638 638
 		}
639 639
 	}
640 640
 
641
-	if requestedOS == "" {
642
-		requestedOS = runtime.GOOS
643
-	}
644
-
645 641
 	if p.config.DownloadManager != nil {
646 642
 		go func() {
647 643
 			var (
... ...
@@ -649,7 +655,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
649 649
 				rootFS image.RootFS
650 650
 			)
651 651
 			downloadRootFS := *image.NewRootFS()
652
-			rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, requestedOS, descriptors, p.config.ProgressOutput)
652
+			rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, layerStoreOS, descriptors, p.config.ProgressOutput)
653 653
 			if err != nil {
654 654
 				// Intentionally do not cancel the config download here
655 655
 				// as the error from config download (if there is one)
... ...
@@ -735,22 +741,22 @@ func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan
735 735
 
736 736
 // pullManifestList handles "manifest lists" which point to various
737 737
 // platform-specific manifests.
738
-func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, requestedOS string) (id digest.Digest, manifestListDigest digest.Digest, err error) {
738
+func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, pp *specs.Platform) (id digest.Digest, manifestListDigest digest.Digest, err error) {
739 739
 	manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
740 740
 	if err != nil {
741 741
 		return "", "", err
742 742
 	}
743 743
 
744
-	logOS := requestedOS // May be "" indicating any OS
745
-	if logOS == "" {
746
-		logOS = "*"
744
+	var platform specs.Platform
745
+	if pp != nil {
746
+		platform = *pp
747 747
 	}
748
-	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), logOS, runtime.GOARCH)
748
+	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), platforms.Format(platform), runtime.GOARCH)
749 749
 
750
-	manifestMatches := filterManifests(mfstList.Manifests, requestedOS)
750
+	manifestMatches := filterManifests(mfstList.Manifests, platform)
751 751
 
752 752
 	if len(manifestMatches) == 0 {
753
-		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", logOS, runtime.GOARCH)
753
+		errMsg := fmt.Sprintf("no matching manifest for %s in the manifest list entries", platforms.Format(platform))
754 754
 		logrus.Debugf(errMsg)
755 755
 		return "", "", errors.New(errMsg)
756 756
 	}
... ...
@@ -781,12 +787,14 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
781 781
 
782 782
 	switch v := manifest.(type) {
783 783
 	case *schema1.SignedManifest:
784
-		id, _, err = p.pullSchema1(ctx, manifestRef, v, manifestMatches[0].Platform.OS)
784
+		platform := toOCIPlatform(manifestMatches[0].Platform)
785
+		id, _, err = p.pullSchema1(ctx, manifestRef, v, &platform)
785 786
 		if err != nil {
786 787
 			return "", "", err
787 788
 		}
788 789
 	case *schema2.DeserializedManifest:
789
-		id, _, err = p.pullSchema2(ctx, manifestRef, v, manifestMatches[0].Platform.OS)
790
+		platform := toOCIPlatform(manifestMatches[0].Platform)
791
+		id, _, err = p.pullSchema2(ctx, manifestRef, v, &platform)
790 792
 		if err != nil {
791 793
 			return "", "", err
792 794
 		}
... ...
@@ -956,3 +964,13 @@ func fixManifestLayers(m *schema1.Manifest) error {
956 956
 func createDownloadFile() (*os.File, error) {
957 957
 	return ioutil.TempFile("", "GetImageBlob")
958 958
 }
959
+
960
+func toOCIPlatform(p manifestlist.PlatformSpec) specs.Platform {
961
+	return specs.Platform{
962
+		OS:           p.OS,
963
+		Architecture: p.Architecture,
964
+		Variant:      p.Variant,
965
+		OSFeatures:   p.OSFeatures,
966
+		OSVersion:    p.OSVersion,
967
+	}
968
+}
... ...
@@ -4,10 +4,11 @@ package distribution // import "github.com/docker/docker/distribution"
4 4
 
5 5
 import (
6 6
 	"context"
7
-	"runtime"
8 7
 
8
+	"github.com/containerd/containerd/platforms"
9 9
 	"github.com/docker/distribution"
10 10
 	"github.com/docker/distribution/manifest/manifestlist"
11
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
11 12
 	"github.com/sirupsen/logrus"
12 13
 )
13 14
 
... ...
@@ -16,15 +17,27 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
16 16
 	return blobs.Open(ctx, ld.digest)
17 17
 }
18 18
 
19
-func filterManifests(manifests []manifestlist.ManifestDescriptor, _ string) []manifestlist.ManifestDescriptor {
19
+func filterManifests(manifests []manifestlist.ManifestDescriptor, p specs.Platform) []manifestlist.ManifestDescriptor {
20
+	p = withDefault(p)
20 21
 	var matches []manifestlist.ManifestDescriptor
21
-	for _, manifestDescriptor := range manifests {
22
-		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
23
-			matches = append(matches, manifestDescriptor)
22
+	for _, desc := range manifests {
23
+		if compareNormalized(toOCIPlatform(desc.Platform), p) {
24
+			matches = append(matches, desc)
25
+			logrus.Debugf("found match for %s with media type %s, digest %s", platforms.Format(p), desc.MediaType, desc.Digest.String())
26
+		}
27
+	}
24 28
 
25
-			logrus.Debugf("found match for %s/%s with media type %s, digest %s", runtime.GOOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
29
+	// deprecated: backwards compatibility with older versions that didn't compare variant
30
+	if len(matches) == 0 && p.Architecture == "arm" {
31
+		p = normalize(p)
32
+		for _, desc := range manifests {
33
+			if desc.Platform.OS == p.OS && desc.Platform.Architecture == p.Architecture {
34
+				matches = append(matches, desc)
35
+				logrus.Debugf("found deprecated partial match for %s with media type %s, digest %s", platforms.Format(p), desc.MediaType, desc.Digest.String())
36
+			}
26 37
 		}
27 38
 	}
39
+
28 40
 	return matches
29 41
 }
30 42
 
... ...
@@ -32,3 +45,38 @@ func filterManifests(manifests []manifestlist.ManifestDescriptor, _ string) []ma
32 32
 func checkImageCompatibility(imageOS, imageOSVersion string) error {
33 33
 	return nil
34 34
 }
35
+
36
+func withDefault(p specs.Platform) specs.Platform {
37
+	def := platforms.DefaultSpec()
38
+	if p.OS == "" {
39
+		p.OS = def.OS
40
+	}
41
+	if p.Architecture == "" {
42
+		p.Architecture = def.Architecture
43
+		p.Variant = def.Variant
44
+	}
45
+	return p
46
+}
47
+
48
+func compareNormalized(p1, p2 specs.Platform) bool {
49
+	// remove after https://github.com/containerd/containerd/pull/2414
50
+	return p1.OS == p2.OS &&
51
+		p1.Architecture == p2.Architecture &&
52
+		p1.Variant == p2.Variant
53
+}
54
+
55
+func normalize(p specs.Platform) specs.Platform {
56
+	p = platforms.Normalize(p)
57
+	// remove after https://github.com/containerd/containerd/pull/2414
58
+	if p.Architecture == "arm" {
59
+		if p.Variant == "" {
60
+			p.Variant = "v7"
61
+		}
62
+	}
63
+	if p.Architecture == "arm64" {
64
+		if p.Variant == "" {
65
+			p.Variant = "v8"
66
+		}
67
+	}
68
+	return p
69
+}
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/docker/distribution/manifest/schema2"
17 17
 	"github.com/docker/distribution/registry/client/transport"
18 18
 	"github.com/docker/docker/pkg/system"
19
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
19 20
 	"github.com/sirupsen/logrus"
20 21
 )
21 22
 
... ...
@@ -62,7 +63,7 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
62 62
 	return rsc, err
63 63
 }
64 64
 
65
-func filterManifests(manifests []manifestlist.ManifestDescriptor, requestedOS string) []manifestlist.ManifestDescriptor {
65
+func filterManifests(manifests []manifestlist.ManifestDescriptor, p specs.Platform) []manifestlist.ManifestDescriptor {
66 66
 	version := system.GetOSVersion()
67 67
 	osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
68 68
 	logrus.Debugf("will prefer Windows entries with version %s", osVersion)
... ...
@@ -71,8 +72,8 @@ func filterManifests(manifests []manifestlist.ManifestDescriptor, requestedOS st
71 71
 	foundWindowsMatch := false
72 72
 	for _, manifestDescriptor := range manifests {
73 73
 		if (manifestDescriptor.Platform.Architecture == runtime.GOARCH) &&
74
-			((requestedOS != "" && manifestDescriptor.Platform.OS == requestedOS) || // Explicit user request for an OS we know we support
75
-				(requestedOS == "" && system.IsOSSupported(manifestDescriptor.Platform.OS))) { // No user requested OS, but one we can support
74
+			((p.OS != "" && manifestDescriptor.Platform.OS == p.OS) || // Explicit user request for an OS we know we support
75
+				(p.OS == "" && system.IsOSSupported(manifestDescriptor.Platform.OS))) { // No user requested OS, but one we can support
76 76
 			matches = append(matches, manifestDescriptor)
77 77
 			logrus.Debugf("found match %s/%s %s with media type %s, digest %s", manifestDescriptor.Platform.OS, runtime.GOARCH, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
78 78
 			if strings.EqualFold("windows", manifestDescriptor.Platform.OS) {
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"net/http"
6 6
 	"net/http/httptest"
7 7
 	"net/url"
8
-	"runtime"
9 8
 	"strings"
10 9
 	"testing"
11 10
 
... ...
@@ -84,7 +83,7 @@ func testTokenPassThru(t *testing.T, ts *httptest.Server) {
84 84
 	logrus.Debug("About to pull")
85 85
 	// We expect it to fail, since we haven't mock'd the full registry exchange in our handler above
86 86
 	tag, _ := reference.WithTag(n, "tag_goes_here")
87
-	_ = p.pullV2Repository(ctx, tag, runtime.GOOS)
87
+	_ = p.pullV2Repository(ctx, tag, nil)
88 88
 }
89 89
 
90 90
 func TestTokenPassThru(t *testing.T) {