| 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/errdefs" | |
| "github.com/docker/docker/image" | |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" | |
| ) | |
| // 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, platform ocispec.Platform) (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{Platform: &platform}) | |
| if err != nil { | |
| if errdefs.IsNotFound(err) { | |
| return "", 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{Platform: &platform}) | |
| if err != nil { | |
| if errdefs.IsNotFound(err) { | |
| continue | |
| } | |
| 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, " ") | |
| } |