e84365f967
Ideally, this should actually do a lookup across images that have no parent, but I wasn't 100% sure how to accomplish that so I opted for the smaller change of having `FROM scratch` builds not be cached for now.
Signed-off-by: Tianon Gravi <admwiggin@gmail.com>
(cherry picked from commit 1741771b67
)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
93 lines
2.3 KiB
Go
93 lines
2.3 KiB
Go
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
imagetype "github.com/docker/docker/api/types/image"
|
|
"github.com/docker/docker/builder"
|
|
"github.com/docker/docker/image"
|
|
)
|
|
|
|
// MakeImageCache creates a stateful image cache.
|
|
func (i *ImageService) MakeImageCache(ctx context.Context, cacheFrom []string) (builder.ImageCache, error) {
|
|
images := []*image.Image{}
|
|
for _, c := range cacheFrom {
|
|
im, err := i.GetImage(ctx, c, imagetype.GetImageOpts{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
images = append(images, im)
|
|
}
|
|
return &imageCache{images: images, c: i}, nil
|
|
}
|
|
|
|
type imageCache struct {
|
|
images []*image.Image
|
|
c *ImageService
|
|
}
|
|
|
|
func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
|
|
ctx := context.TODO()
|
|
|
|
if parentID == "" {
|
|
// TODO handle "parentless" image cache lookups ("FROM scratch")
|
|
return "", nil
|
|
}
|
|
|
|
parent, err := ic.c.GetImage(ctx, parentID, imagetype.GetImageOpts{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, localCachedImage := range ic.images {
|
|
if isMatch(localCachedImage, parent, cfg) {
|
|
return localCachedImage.ID().String(), nil
|
|
}
|
|
}
|
|
|
|
children, err := ic.c.Children(ctx, parent.ID())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, children := range children {
|
|
childImage, err := ic.c.GetImage(ctx, children.String(), imagetype.GetImageOpts{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if isMatch(childImage, parent, cfg) {
|
|
return children.String(), nil
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
// isMatch checks whether a given target can be used as cache for the given
|
|
// parent image/config combination.
|
|
// A target can only be an immediate child of the given parent image. For
|
|
// a parent image with `n` history entries, a valid target must have `n+1`
|
|
// entries and the extra entry must match the provided config
|
|
func isMatch(target, parent *image.Image, cfg *container.Config) bool {
|
|
if target == nil || parent == nil || cfg == nil {
|
|
return false
|
|
}
|
|
|
|
if len(target.History) != len(parent.History)+1 ||
|
|
len(target.RootFS.DiffIDs) != len(parent.RootFS.DiffIDs)+1 {
|
|
return false
|
|
}
|
|
|
|
for i := range parent.History {
|
|
if !reflect.DeepEqual(parent.History[i], target.History[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
childCreatedBy := target.History[len(target.History)-1].CreatedBy
|
|
return childCreatedBy == strings.Join(cfg.Cmd, " ")
|
|
}
|