Skip to content

Commit

Permalink
LCOW: Auto-select OS
Browse files Browse the repository at this point in the history
Signed-off-by: John Howard <[email protected]>

Addresses moby/moby#35089 (comment).
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
  • Loading branch information
John Howard authored and tonistiigi committed Jun 26, 2018
1 parent 8e610b2 commit 35193c0
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 40 deletions.
6 changes: 3 additions & 3 deletions builder/dockerfile/dispatchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
return d.builder.options.Platform
default:
return runtime.GOOS
return "" // Auto-select
}
}

Expand All @@ -247,9 +247,9 @@ func (d *dispatchRequest) getImageOrStage(name string, stageOS string) (builder.
imageImage.OS = runtime.GOOS
if runtime.GOOS == "windows" {
switch os {
case "windows", "":
case "windows":
return nil, errors.New("Windows does not support FROM scratch")
case "linux":
case "linux", "":
if !system.LCOWSupported() {
return nil, errors.New("Linux containers are not supported on this system")
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/images/image_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConf
// Every call to GetImageAndReleasableLayer MUST call releasableLayer.Release() to prevent
// leaking of layers.
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) {
return nil, nil, system.ErrNotSupportedOperatingSystem
}
Expand Down
6 changes: 0 additions & 6 deletions daemon/images/image_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package images // import "github.com/docker/docker/daemon/images"
import (
"context"
"io"
"runtime"
"strings"
"time"

Expand Down Expand Up @@ -65,11 +64,6 @@ func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference
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{
Config: distribution.Config{
MetaHeaders: metaHeaders,
Expand Down
6 changes: 0 additions & 6 deletions distribution/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package distribution // import "github.com/docker/docker/distribution"
import (
"context"
"fmt"
"runtime"

"github.com/docker/distribution/reference"
"github.com/docker/docker/api"
Expand Down Expand Up @@ -115,11 +114,6 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
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 {
// Was this pull cancelled? If so, don't try to fall
// back.
Expand Down
31 changes: 24 additions & 7 deletions distribution/pull_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
// This avoids doing the download, only to potentially fail later.
if !strings.EqualFold(configOS, requestedOS) {
Expand Down Expand Up @@ -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.
// 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)
}
requestedOS = configPlatform.OS

// Populate diff ids in descriptors to avoid downloading foreign layers
// which have been side loaded
Expand All @@ -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 {
go func() {
var (
Expand Down Expand Up @@ -722,18 +735,22 @@ func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan

// pullManifestList handles "manifest lists" which point to various
// 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)
if err != nil {
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 {
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)
return "", "", errors.New(errMsg)
}
Expand Down Expand Up @@ -764,12 +781,12 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf

switch v := manifest.(type) {
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 {
return "", "", err
}
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 {
return "", "", err
}
Expand Down
6 changes: 3 additions & 3 deletions distribution/pull_v2_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
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
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)

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
Expand Down
25 changes: 14 additions & 11 deletions distribution/pull_v2_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,27 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
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
foundWindowsMatch := false
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)
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 {
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})
}
return matches
Expand Down
3 changes: 2 additions & 1 deletion image/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ func (is *store) restore() error {
var l layer.Layer
if chainID := img.RootFS.ChainID(); chainID != "" {
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)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/system/lcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ func ParsePlatform(in string) *specs.Platform {

// IsOSSupported determines if an operating system is supported by the host
func IsOSSupported(os string) bool {
if runtime.GOOS == os {
if strings.EqualFold(runtime.GOOS, os) {
return true
}
if LCOWSupported() && os == "linux" {
if LCOWSupported() && strings.EqualFold(os, "linux") {
return true
}
return false
Expand Down

0 comments on commit 35193c0

Please sign in to comment.