ソースを参照

image/cache: Check image platform

Make sure the cache candidate platform matches the requested.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 877ebbe038ee914e4243d5b7e198eda00494d757)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Gronowski 1 年間 前
コミット
f3f5327b48

+ 2 - 1
builder/builder.go

@@ -14,6 +14,7 @@ import (
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 )
 )
 
 
 const (
 const (
@@ -85,7 +86,7 @@ type ImageCacheBuilder interface {
 type ImageCache interface {
 type ImageCache interface {
 	// GetCache returns a reference to a cached image whose parent equals `parent`
 	// GetCache returns a reference to a cached image whose parent equals `parent`
 	// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error.
 	// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error.
-	GetCache(parentID string, cfg *container.Config) (imageID string, err error)
+	GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error)
 }
 }
 
 
 // Image represents a Docker image used by the builder.
 // Image represents a Docker image used by the builder.

+ 2 - 15
builder/dockerfile/copy.go

@@ -8,7 +8,6 @@ import (
 	"net/url"
 	"net/url"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
-	"runtime"
 	"sort"
 	"sort"
 	"strings"
 	"strings"
 	"time"
 	"time"
@@ -74,7 +73,7 @@ type copier struct {
 	source      builder.Source
 	source      builder.Source
 	pathCache   pathCache
 	pathCache   pathCache
 	download    sourceDownloader
 	download    sourceDownloader
-	platform    *ocispec.Platform
+	platform    ocispec.Platform
 	// for cleanup. TODO: having copier.cleanup() is error prone and hard to
 	// for cleanup. TODO: having copier.cleanup() is error prone and hard to
 	// follow. Code calling performCopy should manage the lifecycle of its params.
 	// follow. Code calling performCopy should manage the lifecycle of its params.
 	// Copier should take override source as input, not imageMount.
 	// Copier should take override source as input, not imageMount.
@@ -83,19 +82,7 @@ type copier struct {
 }
 }
 
 
 func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, imageSource *imageMount) copier {
 func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, imageSource *imageMount) copier {
-	platform := req.builder.platform
-	if platform == nil {
-		// May be nil if not explicitly set in API/dockerfile
-		platform = &ocispec.Platform{}
-	}
-	if platform.OS == "" {
-		// Default to the dispatch requests operating system if not explicit in API/dockerfile
-		platform.OS = req.state.operatingSystem
-	}
-	if platform.OS == "" {
-		// This is a failsafe just in case. Shouldn't be hit.
-		platform.OS = runtime.GOOS
-	}
+	platform := req.builder.getPlatform(req.state)
 
 
 	return copier{
 	return copier{
 		source:      req.source,
 		source:      req.source,

+ 5 - 4
builder/dockerfile/imageprobe.go

@@ -6,13 +6,14 @@ import (
 	"github.com/containerd/log"
 	"github.com/containerd/log"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder"
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 )
 )
 
 
 // ImageProber exposes an Image cache to the Builder. It supports resetting a
 // ImageProber exposes an Image cache to the Builder. It supports resetting a
 // cache.
 // cache.
 type ImageProber interface {
 type ImageProber interface {
 	Reset(ctx context.Context) error
 	Reset(ctx context.Context) error
-	Probe(parentID string, runConfig *container.Config) (string, error)
+	Probe(parentID string, runConfig *container.Config, platform ocispec.Platform) (string, error)
 }
 }
 
 
 type resetFunc func(context.Context) (builder.ImageCache, error)
 type resetFunc func(context.Context) (builder.ImageCache, error)
@@ -51,11 +52,11 @@ func (c *imageProber) Reset(ctx context.Context) error {
 
 
 // Probe checks if cache match can be found for current build instruction.
 // Probe checks if cache match can be found for current build instruction.
 // It returns the cachedID if there is a hit, and the empty string on miss
 // It returns the cachedID if there is a hit, and the empty string on miss
-func (c *imageProber) Probe(parentID string, runConfig *container.Config) (string, error) {
+func (c *imageProber) Probe(parentID string, runConfig *container.Config, platform ocispec.Platform) (string, error) {
 	if c.cacheBusted {
 	if c.cacheBusted {
 		return "", nil
 		return "", nil
 	}
 	}
-	cacheID, err := c.cache.GetCache(parentID, runConfig)
+	cacheID, err := c.cache.GetCache(parentID, runConfig, platform)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -74,6 +75,6 @@ func (c *nopProber) Reset(ctx context.Context) error {
 	return nil
 	return nil
 }
 }
 
 
-func (c *nopProber) Probe(_ string, _ *container.Config) (string, error) {
+func (c *nopProber) Probe(_ string, _ *container.Config, _ ocispec.Platform) (string, error) {
 	return "", nil
 	return "", nil
 }
 }

+ 16 - 1
builder/dockerfile/internals.go

@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
 
 
+	"github.com/containerd/containerd/platforms"
 	"github.com/containerd/log"
 	"github.com/containerd/log"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
@@ -328,7 +329,7 @@ func getShell(c *container.Config, os string) []string {
 }
 }
 
 
 func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
 func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
-	cachedID, err := b.imageProber.Probe(dispatchState.imageID, runConfig)
+	cachedID, err := b.imageProber.Probe(dispatchState.imageID, runConfig, b.getPlatform(dispatchState))
 	if cachedID == "" || err != nil {
 	if cachedID == "" || err != nil {
 		return false, err
 		return false, err
 	}
 	}
@@ -388,3 +389,17 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf
 	}
 	}
 	return hc
 	return hc
 }
 }
+
+func (b *Builder) getPlatform(state *dispatchState) ocispec.Platform {
+	// May be nil if not explicitly set in API/dockerfile
+	out := platforms.DefaultSpec()
+	if b.platform != nil {
+		out = *b.platform
+	}
+
+	if state.operatingSystem != "" {
+		out.OS = state.operatingSystem
+	}
+
+	return out
+}

+ 2 - 1
builder/dockerfile/mockbackend_test.go

@@ -13,6 +13,7 @@ import (
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 )
 )
 
 
 // MockBackend implements the builder.Backend interface for unit testing
 // MockBackend implements the builder.Backend interface for unit testing
@@ -106,7 +107,7 @@ type mockImageCache struct {
 	getCacheFunc func(parentID string, cfg *container.Config) (string, error)
 	getCacheFunc func(parentID string, cfg *container.Config) (string, error)
 }
 }
 
 
-func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config) (string, error) {
+func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config, _ ocispec.Platform) (string, error) {
 	if mic.getCacheFunc != nil {
 	if mic.getCacheFunc != nil {
 		return mic.getCacheFunc(parentID, cfg)
 		return mic.getCacheFunc(parentID, cfg)
 	}
 	}

+ 8 - 5
daemon/containerd/cache.go

@@ -54,7 +54,7 @@ type localCache struct {
 	imageService *ImageService
 	imageService *ImageService
 }
 }
 
 
-func (ic *localCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
+func (ic *localCache) GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error) {
 	ctx := context.TODO()
 	ctx := context.TODO()
 
 
 	var children []image.ID
 	var children []image.ID
@@ -100,8 +100,11 @@ func (ic *localCache) GetCache(parentID string, cfg *container.Config) (imageID
 		}
 		}
 
 
 		if cache.CompareConfig(&cc, cfg) {
 		if cache.CompareConfig(&cc, cfg) {
-			childImage, err := ic.imageService.GetImage(ctx, child.String(), imagetype.GetImageOpts{})
+			childImage, err := ic.imageService.GetImage(ctx, child.String(), imagetype.GetImageOpts{Platform: &platform})
 			if err != nil {
 			if err != nil {
+				if errdefs.IsNotFound(err) {
+					continue
+				}
 				return "", err
 				return "", err
 			}
 			}
 
 
@@ -124,10 +127,10 @@ type imageCache struct {
 	lc           *localCache
 	lc           *localCache
 }
 }
 
 
-func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
+func (ic *imageCache) GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error) {
 	ctx := context.TODO()
 	ctx := context.TODO()
 
 
-	imgID, err := ic.lc.GetCache(parentID, cfg)
+	imgID, err := ic.lc.GetCache(parentID, cfg, platform)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -143,7 +146,7 @@ func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID
 	lenHistory := 0
 	lenHistory := 0
 
 
 	if parentID != "" {
 	if parentID != "" {
-		parent, err = ic.imageService.GetImage(ctx, parentID, imagetype.GetImageOpts{})
+		parent, err = ic.imageService.GetImage(ctx, parentID, imagetype.GetImageOpts{Platform: &platform})
 		if err != nil {
 		if err != nil {
 			return "", err
 			return "", err
 		}
 		}

+ 16 - 5
image/cache/cache.go

@@ -7,11 +7,13 @@ import (
 	"reflect"
 	"reflect"
 	"strings"
 	"strings"
 
 
+	"github.com/containerd/containerd/platforms"
 	"github.com/containerd/log"
 	"github.com/containerd/log"
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/dockerversion"
 	"github.com/docker/docker/dockerversion"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
 
 
@@ -28,8 +30,8 @@ type LocalImageCache struct {
 }
 }
 
 
 // GetCache returns the image id found in the cache
 // GetCache returns the image id found in the cache
-func (lic *LocalImageCache) GetCache(imgID string, config *containertypes.Config) (string, error) {
-	return getImageIDAndError(getLocalCachedImage(lic.store, image.ID(imgID), config))
+func (lic *LocalImageCache) GetCache(imgID string, config *containertypes.Config, platform ocispec.Platform) (string, error) {
+	return getImageIDAndError(getLocalCachedImage(lic.store, image.ID(imgID), config, platform))
 }
 }
 
 
 // New returns an image cache, based on history objects
 // New returns an image cache, based on history objects
@@ -53,8 +55,8 @@ func (ic *ImageCache) Populate(image *image.Image) {
 }
 }
 
 
 // GetCache returns the image id found in the cache
 // GetCache returns the image id found in the cache
-func (ic *ImageCache) GetCache(parentID string, cfg *containertypes.Config) (string, error) {
-	imgID, err := ic.localImageCache.GetCache(parentID, cfg)
+func (ic *ImageCache) GetCache(parentID string, cfg *containertypes.Config, platform ocispec.Platform) (string, error) {
+	imgID, err := ic.localImageCache.GetCache(parentID, cfg, platform)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -217,7 +219,7 @@ func getImageIDAndError(img *image.Image, err error) (string, error) {
 // of the image with imgID, that had the same config when it was
 // of the image with imgID, that had the same config when it was
 // created. nil is returned if a child cannot be found. An error is
 // created. nil is returned if a child cannot be found. An error is
 // returned if the parent image cannot be found.
 // returned if the parent image cannot be found.
-func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *containertypes.Config) (*image.Image, error) {
+func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *containertypes.Config, platform ocispec.Platform) (*image.Image, error) {
 	if config == nil {
 	if config == nil {
 		return nil, nil
 		return nil, nil
 	}
 	}
@@ -247,6 +249,15 @@ func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *contain
 				continue
 				continue
 			}
 			}
 
 
+			imgPlatform := img.Platform()
+			// Discard old linux/amd64 images with empty platform.
+			if imgPlatform.OS == "" && imgPlatform.Architecture == "" {
+				continue
+			}
+			if !platforms.OnlyStrict(platform).Match(imgPlatform) {
+				continue
+			}
+
 			if compare(&img.ContainerConfig, config) {
 			if compare(&img.ContainerConfig, config) {
 				// check for the most up to date match
 				// check for the most up to date match
 				if img.Created != nil && (match == nil || match.Created.Before(*img.Created)) {
 				if img.Created != nil && (match == nil || match.Created.Before(*img.Created)) {