瀏覽代碼

LCOW: Auto-select OS

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
John Howard 7 年之前
父節點
當前提交
35193c0e7d

+ 3 - 3
builder/dockerfile/dispatchers.go

@@ -228,7 +228,7 @@ func (d *dispatchRequest) getOsFromFlagsAndStage(stageOS string) string {
 		// multi-arch aware yet, it is guaranteed to only hold the OS part here.
 		// multi-arch aware yet, it is guaranteed to only hold the OS part here.
 		return d.builder.options.Platform
 		return d.builder.options.Platform
 	default:
 	default:
-		return runtime.GOOS
+		return "" // Auto-select
 	}
 	}
 }
 }
 
 
@@ -247,9 +247,9 @@ func (d *dispatchRequest) getImageOrStage(name string, stageOS string) (builder.
 		imageImage.OS = runtime.GOOS
 		imageImage.OS = runtime.GOOS
 		if runtime.GOOS == "windows" {
 		if runtime.GOOS == "windows" {
 			switch os {
 			switch os {
-			case "windows", "":
+			case "windows":
 				return nil, errors.New("Windows does not support FROM scratch")
 				return nil, errors.New("Windows does not support FROM scratch")
-			case "linux":
+			case "linux", "":
 				if !system.LCOWSupported() {
 				if !system.LCOWSupported() {
 					return nil, errors.New("Linux containers are not supported on this system")
 					return nil, errors.New("Linux containers are not supported on this system")
 				}
 				}

+ 1 - 1
daemon/images/image_builder.go

@@ -166,7 +166,7 @@ func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConf
 // Every call to GetImageAndReleasableLayer MUST call releasableLayer.Release() to prevent
 // Every call to GetImageAndReleasableLayer MUST call releasableLayer.Release() to prevent
 // leaking of layers.
 // leaking of layers.
 func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) {
 func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) {
-	if refOrID == "" {
+	if refOrID == "" { // ie FROM scratch
 		if !system.IsOSSupported(opts.OS) {
 		if !system.IsOSSupported(opts.OS) {
 			return nil, nil, system.ErrNotSupportedOperatingSystem
 			return nil, nil, system.ErrNotSupportedOperatingSystem
 		}
 		}

+ 0 - 6
daemon/images/image_pull.go

@@ -3,7 +3,6 @@ package images // import "github.com/docker/docker/daemon/images"
 import (
 import (
 	"context"
 	"context"
 	"io"
 	"io"
-	"runtime"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -65,11 +64,6 @@ func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference
 		close(writesDone)
 		close(writesDone)
 	}()
 	}()
 
 
-	// Default to the host OS platform in case it hasn't been populated with an explicit value.
-	if os == "" {
-		os = runtime.GOOS
-	}
-
 	imagePullConfig := &distribution.ImagePullConfig{
 	imagePullConfig := &distribution.ImagePullConfig{
 		Config: distribution.Config{
 		Config: distribution.Config{
 			MetaHeaders:      metaHeaders,
 			MetaHeaders:      metaHeaders,

+ 0 - 6
distribution/pull.go

@@ -3,7 +3,6 @@ package distribution // import "github.com/docker/docker/distribution"
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
-	"runtime"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
@@ -115,11 +114,6 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
 			continue
 			continue
 		}
 		}
 
 
-		// Make sure we default the OS if it hasn't been supplied
-		if imagePullConfig.OS == "" {
-			imagePullConfig.OS = runtime.GOOS
-		}
-
 		if err := puller.Pull(ctx, ref, imagePullConfig.OS); err != nil {
 		if err := puller.Pull(ctx, ref, imagePullConfig.OS); err != nil {
 			// Was this pull cancelled? If so, don't try to fall
 			// Was this pull cancelled? If so, don't try to fall
 			// back.
 			// back.

+ 24 - 7
distribution/pull_v2.go

@@ -509,6 +509,14 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
 		}
 		}
 	}
 	}
 
 
+	// In the situation that the API call didn't specify an OS explicitly, but
+	// we support the operating system, switch to that operating system.
+	// eg FROM supertest2014/nyan with no platform specifier, and docker build
+	// with no --platform= flag under LCOW.
+	if requestedOS == "" && system.IsOSSupported(configOS) {
+		requestedOS = configOS
+	}
+
 	// Early bath if the requested OS doesn't match that of the configuration.
 	// Early bath if the requested OS doesn't match that of the configuration.
 	// This avoids doing the download, only to potentially fail later.
 	// This avoids doing the download, only to potentially fail later.
 	if !strings.EqualFold(configOS, requestedOS) {
 	if !strings.EqualFold(configOS, requestedOS) {
@@ -618,9 +626,10 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 
 
 		// Early bath if the requested OS doesn't match that of the configuration.
 		// Early bath if the requested OS doesn't match that of the configuration.
 		// This avoids doing the download, only to potentially fail later.
 		// This avoids doing the download, only to potentially fail later.
-		if !strings.EqualFold(configPlatform.OS, requestedOS) {
+		if !system.IsOSSupported(configPlatform.OS) {
 			return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configPlatform.OS, requestedOS)
 			return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configPlatform.OS, requestedOS)
 		}
 		}
+		requestedOS = configPlatform.OS
 
 
 		// Populate diff ids in descriptors to avoid downloading foreign layers
 		// Populate diff ids in descriptors to avoid downloading foreign layers
 		// which have been side loaded
 		// which have been side loaded
@@ -629,6 +638,10 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 		}
 		}
 	}
 	}
 
 
+	if requestedOS == "" {
+		requestedOS = runtime.GOOS
+	}
+
 	if p.config.DownloadManager != nil {
 	if p.config.DownloadManager != nil {
 		go func() {
 		go func() {
 			var (
 			var (
@@ -722,18 +735,22 @@ func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan
 
 
 // pullManifestList handles "manifest lists" which point to various
 // pullManifestList handles "manifest lists" which point to various
 // platform-specific manifests.
 // platform-specific manifests.
-func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, os string) (id digest.Digest, manifestListDigest digest.Digest, err error) {
+func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, requestedOS string) (id digest.Digest, manifestListDigest digest.Digest, err error) {
 	manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
 	manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
 	if err != nil {
 	if err != nil {
 		return "", "", err
 		return "", "", err
 	}
 	}
 
 
-	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), os, runtime.GOARCH)
+	logOS := requestedOS // May be "" indicating any OS
+	if logOS == "" {
+		logOS = "*"
+	}
+	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), logOS, runtime.GOARCH)
 
 
-	manifestMatches := filterManifests(mfstList.Manifests, os)
+	manifestMatches := filterManifests(mfstList.Manifests, requestedOS)
 
 
 	if len(manifestMatches) == 0 {
 	if len(manifestMatches) == 0 {
-		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)
+		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", logOS, runtime.GOARCH)
 		logrus.Debugf(errMsg)
 		logrus.Debugf(errMsg)
 		return "", "", errors.New(errMsg)
 		return "", "", errors.New(errMsg)
 	}
 	}
@@ -764,12 +781,12 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
 
 
 	switch v := manifest.(type) {
 	switch v := manifest.(type) {
 	case *schema1.SignedManifest:
 	case *schema1.SignedManifest:
-		id, _, err = p.pullSchema1(ctx, manifestRef, v, os)
+		id, _, err = p.pullSchema1(ctx, manifestRef, v, manifestMatches[0].Platform.OS)
 		if err != nil {
 		if err != nil {
 			return "", "", err
 			return "", "", err
 		}
 		}
 	case *schema2.DeserializedManifest:
 	case *schema2.DeserializedManifest:
-		id, _, err = p.pullSchema2(ctx, manifestRef, v, os)
+		id, _, err = p.pullSchema2(ctx, manifestRef, v, manifestMatches[0].Platform.OS)
 		if err != nil {
 		if err != nil {
 			return "", "", err
 			return "", "", err
 		}
 		}

+ 3 - 3
distribution/pull_v2_unix.go

@@ -16,13 +16,13 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
 	return blobs.Open(ctx, ld.digest)
 	return blobs.Open(ctx, ld.digest)
 }
 }
 
 
-func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
+func filterManifests(manifests []manifestlist.ManifestDescriptor, _ string) []manifestlist.ManifestDescriptor {
 	var matches []manifestlist.ManifestDescriptor
 	var matches []manifestlist.ManifestDescriptor
 	for _, manifestDescriptor := range manifests {
 	for _, manifestDescriptor := range manifests {
-		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
+		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
 			matches = append(matches, manifestDescriptor)
 			matches = append(matches, manifestDescriptor)
 
 
-			logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
+			logrus.Debugf("found match for %s/%s with media type %s, digest %s", runtime.GOOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
 		}
 		}
 	}
 	}
 	return matches
 	return matches

+ 14 - 11
distribution/pull_v2_windows.go

@@ -62,24 +62,27 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
 	return rsc, err
 	return rsc, err
 }
 }
 
 
-func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
-	osVersion := ""
-	if os == "windows" {
-		version := system.GetOSVersion()
-		osVersion = fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
-		logrus.Debugf("will prefer entries with version %s", osVersion)
-	}
+func filterManifests(manifests []manifestlist.ManifestDescriptor, requestedOS string) []manifestlist.ManifestDescriptor {
+	version := system.GetOSVersion()
+	osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
+	logrus.Debugf("will prefer Windows entries with version %s", osVersion)
 
 
 	var matches []manifestlist.ManifestDescriptor
 	var matches []manifestlist.ManifestDescriptor
+	foundWindowsMatch := false
 	for _, manifestDescriptor := range manifests {
 	for _, manifestDescriptor := range manifests {
-		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
+		if (manifestDescriptor.Platform.Architecture == runtime.GOARCH) &&
+			((requestedOS != "" && manifestDescriptor.Platform.OS == requestedOS) || // Explicit user request for an OS we know we support
+				(requestedOS == "" && system.IsOSSupported(manifestDescriptor.Platform.OS))) { // No user requested OS, but one we can support
 			matches = append(matches, manifestDescriptor)
 			matches = append(matches, manifestDescriptor)
-			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())
+			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())
+			if strings.EqualFold("windows", manifestDescriptor.Platform.OS) {
+				foundWindowsMatch = true
+			}
 		} else {
 		} else {
-			logrus.Debugf("ignoring %s/%s %s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
+			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())
 		}
 		}
 	}
 	}
-	if os == "windows" {
+	if foundWindowsMatch {
 		sort.Stable(manifestsByVersion{osVersion, matches})
 		sort.Stable(manifestsByVersion{osVersion, matches})
 	}
 	}
 	return matches
 	return matches

+ 2 - 1
image/store.go

@@ -76,7 +76,8 @@ func (is *store) restore() error {
 		var l layer.Layer
 		var l layer.Layer
 		if chainID := img.RootFS.ChainID(); chainID != "" {
 		if chainID := img.RootFS.ChainID(); chainID != "" {
 			if !system.IsOSSupported(img.OperatingSystem()) {
 			if !system.IsOSSupported(img.OperatingSystem()) {
-				return system.ErrNotSupportedOperatingSystem
+				logrus.Errorf("not restoring image with unsupported operating system %v, %v, %s", dgst, chainID, img.OperatingSystem())
+				return nil
 			}
 			}
 			l, err = is.lss[img.OperatingSystem()].Get(chainID)
 			l, err = is.lss[img.OperatingSystem()].Get(chainID)
 			if err != nil {
 			if err != nil {

+ 2 - 2
pkg/system/lcow.go

@@ -59,10 +59,10 @@ func ParsePlatform(in string) *specs.Platform {
 
 
 // IsOSSupported determines if an operating system is supported by the host
 // IsOSSupported determines if an operating system is supported by the host
 func IsOSSupported(os string) bool {
 func IsOSSupported(os string) bool {
-	if runtime.GOOS == os {
+	if strings.EqualFold(runtime.GOOS, os) {
 		return true
 		return true
 	}
 	}
-	if LCOWSupported() && os == "linux" {
+	if LCOWSupported() && strings.EqualFold(os, "linux") {
 		return true
 		return true
 	}
 	}
 	return false
 	return false