Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -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) {
|