summaryrefslogtreecommitdiff
path: root/daemon/containerd/cache.go
blob: 8bd01768f8f9adefca09295bc7235409eef460e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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()
	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, " ")
}