Signed-off-by: John Howard <jhoward@microsoft.com>
Addresses https://github.com/moby/moby/pull/35089#issuecomment-367802698.
This change enables the daemon to automatically select an image under LCOW
that can be used if the API doesn't specify an explicit platform.
For example:
FROM supertest2014/nyan
ADD Dockerfile /
And docker build . will download the linux image (not a multi-manifest image)
And similarly docker pull ubuntu will match linux/amd64
| ... | ... |
@@ -228,7 +228,7 @@ func (d *dispatchRequest) getOsFromFlagsAndStage(stageOS string) string {
|
| 228 | 228 |
// multi-arch aware yet, it is guaranteed to only hold the OS part here. |
| 229 | 229 |
return d.builder.options.Platform |
| 230 | 230 |
default: |
| 231 |
- return runtime.GOOS |
|
| 231 |
+ return "" // Auto-select |
|
| 232 | 232 |
} |
| 233 | 233 |
} |
| 234 | 234 |
|
| ... | ... |
@@ -247,9 +247,9 @@ func (d *dispatchRequest) getImageOrStage(name string, stageOS string) (builder. |
| 247 | 247 |
imageImage.OS = runtime.GOOS |
| 248 | 248 |
if runtime.GOOS == "windows" {
|
| 249 | 249 |
switch os {
|
| 250 |
- case "windows", "": |
|
| 250 |
+ case "windows": |
|
| 251 | 251 |
return nil, errors.New("Windows does not support FROM scratch")
|
| 252 |
- case "linux": |
|
| 252 |
+ case "linux", "": |
|
| 253 | 253 |
if !system.LCOWSupported() {
|
| 254 | 254 |
return nil, errors.New("Linux containers are not supported on this system")
|
| 255 | 255 |
} |
| ... | ... |
@@ -166,7 +166,7 @@ func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConf |
| 166 | 166 |
// Every call to GetImageAndReleasableLayer MUST call releasableLayer.Release() to prevent |
| 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 |
- if refOrID == "" {
|
|
| 169 |
+ if refOrID == "" { // ie FROM scratch
|
|
| 170 | 170 |
if !system.IsOSSupported(opts.OS) {
|
| 171 | 171 |
return nil, nil, system.ErrNotSupportedOperatingSystem |
| 172 | 172 |
} |
| ... | ... |
@@ -3,7 +3,6 @@ package images // import "github.com/docker/docker/daemon/images" |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"io" |
| 6 |
- "runtime" |
|
| 7 | 6 |
"strings" |
| 8 | 7 |
"time" |
| 9 | 8 |
|
| ... | ... |
@@ -65,11 +64,6 @@ func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference |
| 65 | 65 |
close(writesDone) |
| 66 | 66 |
}() |
| 67 | 67 |
|
| 68 |
- // Default to the host OS platform in case it hasn't been populated with an explicit value. |
|
| 69 |
- if os == "" {
|
|
| 70 |
- os = runtime.GOOS |
|
| 71 |
- } |
|
| 72 |
- |
|
| 73 | 68 |
imagePullConfig := &distribution.ImagePullConfig{
|
| 74 | 69 |
Config: distribution.Config{
|
| 75 | 70 |
MetaHeaders: metaHeaders, |
| ... | ... |
@@ -3,7 +3,6 @@ package distribution // import "github.com/docker/docker/distribution" |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"fmt" |
| 6 |
- "runtime" |
|
| 7 | 6 |
|
| 8 | 7 |
"github.com/docker/distribution/reference" |
| 9 | 8 |
"github.com/docker/docker/api" |
| ... | ... |
@@ -115,11 +114,6 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo |
| 115 | 115 |
continue |
| 116 | 116 |
} |
| 117 | 117 |
|
| 118 |
- // Make sure we default the OS if it hasn't been supplied |
|
| 119 |
- if imagePullConfig.OS == "" {
|
|
| 120 |
- imagePullConfig.OS = runtime.GOOS |
|
| 121 |
- } |
|
| 122 |
- |
|
| 123 | 118 |
if err := puller.Pull(ctx, ref, imagePullConfig.OS); err != nil {
|
| 124 | 119 |
// Was this pull cancelled? If so, don't try to fall |
| 125 | 120 |
// back. |
| ... | ... |
@@ -509,6 +509,14 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv |
| 509 | 509 |
} |
| 510 | 510 |
} |
| 511 | 511 |
|
| 512 |
+ // In the situation that the API call didn't specify an OS explicitly, but |
|
| 513 |
+ // we support the operating system, switch to that operating system. |
|
| 514 |
+ // eg FROM supertest2014/nyan with no platform specifier, and docker build |
|
| 515 |
+ // with no --platform= flag under LCOW. |
|
| 516 |
+ if requestedOS == "" && system.IsOSSupported(configOS) {
|
|
| 517 |
+ requestedOS = configOS |
|
| 518 |
+ } |
|
| 519 |
+ |
|
| 512 | 520 |
// Early bath if the requested OS doesn't match that of the configuration. |
| 513 | 521 |
// This avoids doing the download, only to potentially fail later. |
| 514 | 522 |
if !strings.EqualFold(configOS, requestedOS) {
|
| ... | ... |
@@ -618,9 +626,10 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s |
| 618 | 618 |
|
| 619 | 619 |
// Early bath if the requested OS doesn't match that of the configuration. |
| 620 | 620 |
// This avoids doing the download, only to potentially fail later. |
| 621 |
- if !strings.EqualFold(configPlatform.OS, requestedOS) {
|
|
| 621 |
+ if !system.IsOSSupported(configPlatform.OS) {
|
|
| 622 | 622 |
return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configPlatform.OS, requestedOS)
|
| 623 | 623 |
} |
| 624 |
+ requestedOS = configPlatform.OS |
|
| 624 | 625 |
|
| 625 | 626 |
// Populate diff ids in descriptors to avoid downloading foreign layers |
| 626 | 627 |
// which have been side loaded |
| ... | ... |
@@ -629,6 +638,10 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s |
| 629 | 629 |
} |
| 630 | 630 |
} |
| 631 | 631 |
|
| 632 |
+ if requestedOS == "" {
|
|
| 633 |
+ requestedOS = runtime.GOOS |
|
| 634 |
+ } |
|
| 635 |
+ |
|
| 632 | 636 |
if p.config.DownloadManager != nil {
|
| 633 | 637 |
go func() {
|
| 634 | 638 |
var ( |
| ... | ... |
@@ -722,18 +735,22 @@ func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan |
| 722 | 722 |
|
| 723 | 723 |
// pullManifestList handles "manifest lists" which point to various |
| 724 | 724 |
// platform-specific manifests. |
| 725 |
-func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, os string) (id digest.Digest, manifestListDigest digest.Digest, err error) {
|
|
| 725 |
+func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, requestedOS string) (id digest.Digest, manifestListDigest digest.Digest, err error) {
|
|
| 726 | 726 |
manifestListDigest, err = schema2ManifestDigest(ref, mfstList) |
| 727 | 727 |
if err != nil {
|
| 728 | 728 |
return "", "", err |
| 729 | 729 |
} |
| 730 | 730 |
|
| 731 |
- logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), os, runtime.GOARCH)
|
|
| 731 |
+ logOS := requestedOS // May be "" indicating any OS |
|
| 732 |
+ if logOS == "" {
|
|
| 733 |
+ logOS = "*" |
|
| 734 |
+ } |
|
| 735 |
+ logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), logOS, runtime.GOARCH)
|
|
| 732 | 736 |
|
| 733 |
- manifestMatches := filterManifests(mfstList.Manifests, os) |
|
| 737 |
+ manifestMatches := filterManifests(mfstList.Manifests, requestedOS) |
|
| 734 | 738 |
|
| 735 | 739 |
if len(manifestMatches) == 0 {
|
| 736 |
- errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)
|
|
| 740 |
+ errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", logOS, runtime.GOARCH)
|
|
| 737 | 741 |
logrus.Debugf(errMsg) |
| 738 | 742 |
return "", "", errors.New(errMsg) |
| 739 | 743 |
} |
| ... | ... |
@@ -764,12 +781,12 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf |
| 764 | 764 |
|
| 765 | 765 |
switch v := manifest.(type) {
|
| 766 | 766 |
case *schema1.SignedManifest: |
| 767 |
- id, _, err = p.pullSchema1(ctx, manifestRef, v, os) |
|
| 767 |
+ id, _, err = p.pullSchema1(ctx, manifestRef, v, manifestMatches[0].Platform.OS) |
|
| 768 | 768 |
if err != nil {
|
| 769 | 769 |
return "", "", err |
| 770 | 770 |
} |
| 771 | 771 |
case *schema2.DeserializedManifest: |
| 772 |
- id, _, err = p.pullSchema2(ctx, manifestRef, v, os) |
|
| 772 |
+ id, _, err = p.pullSchema2(ctx, manifestRef, v, manifestMatches[0].Platform.OS) |
|
| 773 | 773 |
if err != nil {
|
| 774 | 774 |
return "", "", err |
| 775 | 775 |
} |
| ... | ... |
@@ -16,13 +16,13 @@ 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, os string) []manifestlist.ManifestDescriptor {
|
|
| 19 |
+func filterManifests(manifests []manifestlist.ManifestDescriptor, _ string) []manifestlist.ManifestDescriptor {
|
|
| 20 | 20 |
var matches []manifestlist.ManifestDescriptor |
| 21 | 21 |
for _, manifestDescriptor := range manifests {
|
| 22 |
- if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
|
|
| 22 |
+ if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
|
|
| 23 | 23 |
matches = append(matches, manifestDescriptor) |
| 24 | 24 |
|
| 25 |
- logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
|
|
| 25 |
+ logrus.Debugf("found match for %s/%s with media type %s, digest %s", runtime.GOOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
|
|
| 26 | 26 |
} |
| 27 | 27 |
} |
| 28 | 28 |
return matches |
| ... | ... |
@@ -62,24 +62,27 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo |
| 62 | 62 |
return rsc, err |
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 |
-func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
|
|
| 66 |
- osVersion := "" |
|
| 67 |
- if os == "windows" {
|
|
| 68 |
- version := system.GetOSVersion() |
|
| 69 |
- osVersion = fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
|
|
| 70 |
- logrus.Debugf("will prefer entries with version %s", osVersion)
|
|
| 71 |
- } |
|
| 65 |
+func filterManifests(manifests []manifestlist.ManifestDescriptor, requestedOS string) []manifestlist.ManifestDescriptor {
|
|
| 66 |
+ version := system.GetOSVersion() |
|
| 67 |
+ osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
|
|
| 68 |
+ logrus.Debugf("will prefer Windows entries with version %s", osVersion)
|
|
| 72 | 69 |
|
| 73 | 70 |
var matches []manifestlist.ManifestDescriptor |
| 71 |
+ foundWindowsMatch := false |
|
| 74 | 72 |
for _, manifestDescriptor := range manifests {
|
| 75 |
- if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
|
|
| 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
|
|
| 76 | 76 |
matches = append(matches, manifestDescriptor) |
| 77 |
- logrus.Debugf("found match for %s/%s %s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
|
|
| 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 |
+ if strings.EqualFold("windows", manifestDescriptor.Platform.OS) {
|
|
| 79 |
+ foundWindowsMatch = true |
|
| 80 |
+ } |
|
| 78 | 81 |
} else {
|
| 79 |
- logrus.Debugf("ignoring %s/%s %s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
|
|
| 82 |
+ logrus.Debugf("ignoring %s/%s %s with media type %s, digest %s", manifestDescriptor.Platform.OS, manifestDescriptor.Platform.Architecture, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
|
|
| 80 | 83 |
} |
| 81 | 84 |
} |
| 82 |
- if os == "windows" {
|
|
| 85 |
+ if foundWindowsMatch {
|
|
| 83 | 86 |
sort.Stable(manifestsByVersion{osVersion, matches})
|
| 84 | 87 |
} |
| 85 | 88 |
return matches |
| ... | ... |
@@ -76,7 +76,8 @@ func (is *store) restore() error {
|
| 76 | 76 |
var l layer.Layer |
| 77 | 77 |
if chainID := img.RootFS.ChainID(); chainID != "" {
|
| 78 | 78 |
if !system.IsOSSupported(img.OperatingSystem()) {
|
| 79 |
- return system.ErrNotSupportedOperatingSystem |
|
| 79 |
+ logrus.Errorf("not restoring image with unsupported operating system %v, %v, %s", dgst, chainID, img.OperatingSystem())
|
|
| 80 |
+ return nil |
|
| 80 | 81 |
} |
| 81 | 82 |
l, err = is.lss[img.OperatingSystem()].Get(chainID) |
| 82 | 83 |
if err != nil {
|
| ... | ... |
@@ -59,10 +59,10 @@ func ParsePlatform(in string) *specs.Platform {
|
| 59 | 59 |
|
| 60 | 60 |
// IsOSSupported determines if an operating system is supported by the host |
| 61 | 61 |
func IsOSSupported(os string) bool {
|
| 62 |
- if runtime.GOOS == os {
|
|
| 62 |
+ if strings.EqualFold(runtime.GOOS, os) {
|
|
| 63 | 63 |
return true |
| 64 | 64 |
} |
| 65 |
- if LCOWSupported() && os == "linux" {
|
|
| 65 |
+ if LCOWSupported() && strings.EqualFold(os, "linux") {
|
|
| 66 | 66 |
return true |
| 67 | 67 |
} |
| 68 | 68 |
return false |