From 17af50f46bfdbcb3af9d1f327adcbc8ce73e539d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Fri, 1 Dec 2023 10:24:27 +0100 Subject: [PATCH] image/cache: Restrict cache candidates to locally built images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restrict cache candidates only to images that were built locally. This doesn't affect builds using `--cache-from`. Signed-off-by: Paweł Gronowski (cherry picked from commit 96ac22768a8a745b1fb5e4531eaca8d2ad7327ad) Signed-off-by: Paweł Gronowski --- image/cache/cache.go | 56 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/image/cache/cache.go b/image/cache/cache.go index 6d3f4c57b5..77cb9d1706 100644 --- a/image/cache/cache.go +++ b/image/cache/cache.go @@ -1,11 +1,13 @@ package cache // import "github.com/docker/docker/image/cache" import ( + "context" "encoding/json" "fmt" "reflect" "strings" + "github.com/containerd/log" containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/dockerversion" "github.com/docker/docker/image" @@ -216,6 +218,22 @@ func getImageIDAndError(img *image.Image, err error) (string, error) { // created. nil is returned if a child cannot be found. An error is // returned if the parent image cannot be found. func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *containertypes.Config) (*image.Image, error) { + if config == nil { + return nil, nil + } + + isBuiltLocally := func(id image.ID) bool { + builtLocally, err := imageStore.IsBuiltLocally(id) + if err != nil { + log.G(context.TODO()).WithFields(log.Fields{ + "error": err, + "id": id, + }).Warn("failed to check if image was built locally") + return false + } + return builtLocally + } + // Loop on the children of the given image and check the config getMatch := func(siblings []image.ID) (*image.Image, error) { var match *image.Image @@ -225,6 +243,10 @@ func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *contain return nil, fmt.Errorf("unable to find image %q", id) } + if !isBuiltLocally(id) { + continue + } + if compare(&img.ContainerConfig, config) { // check for the most up to date match if match == nil || match.Created.Before(img.Created) { @@ -238,11 +260,29 @@ func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *contain // In this case, this is `FROM scratch`, which isn't an actual image. if imgID == "" { images := imageStore.Map() + var siblings []image.ID for id, img := range images { - if img.Parent == imgID { - siblings = append(siblings, id) + if img.Parent != "" { + continue } + + if !isBuiltLocally(id) { + continue + } + + // Do a quick initial filter on the Cmd to avoid adding all + // non-local images with empty parent to the siblings slice and + // performing a full config compare. + // + // config.Cmd is set to the current Dockerfile instruction so we + // check it against the img.ContainerConfig.Cmd which is the + // command of the last layer. + if !strSliceEqual(img.ContainerConfig.Cmd, config.Cmd) { + continue + } + + siblings = append(siblings, id) } return getMatch(siblings) } @@ -251,3 +291,15 @@ func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *contain siblings := imageStore.Children(imgID) return getMatch(siblings) } + +func strSliceEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +}