github.com/moby/docker@v26.1.3+incompatible/daemon/containerd/cache.go (about) 1 package containerd 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 9 "github.com/containerd/containerd/content" 10 cerrdefs "github.com/containerd/containerd/errdefs" 11 "github.com/containerd/log" 12 "github.com/docker/docker/api/types/backend" 13 "github.com/docker/docker/api/types/container" 14 "github.com/docker/docker/builder" 15 "github.com/docker/docker/errdefs" 16 "github.com/docker/docker/image" 17 "github.com/docker/docker/image/cache" 18 "github.com/docker/docker/internal/multierror" 19 "github.com/docker/docker/layer" 20 "github.com/opencontainers/go-digest" 21 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 22 ) 23 24 // MakeImageCache creates a stateful image cache. 25 func (i *ImageService) MakeImageCache(ctx context.Context, sourceRefs []string) (builder.ImageCache, error) { 26 return cache.New(ctx, cacheAdaptor{i}, sourceRefs) 27 } 28 29 type cacheAdaptor struct { 30 is *ImageService 31 } 32 33 func (c cacheAdaptor) Get(id image.ID) (*image.Image, error) { 34 ctx := context.TODO() 35 ref := id.String() 36 37 outImg, err := c.is.GetImage(ctx, id.String(), backend.GetImageOpts{}) 38 if err != nil { 39 return nil, fmt.Errorf("GetImage: %w", err) 40 } 41 42 c8dImg, err := c.is.resolveImage(ctx, ref) 43 if err != nil { 44 return nil, fmt.Errorf("resolveImage: %w", err) 45 } 46 47 var errFound = errors.New("success") 48 err = c.is.walkImageManifests(ctx, c8dImg, func(img *ImageManifest) error { 49 desc, err := img.Config(ctx) 50 if err != nil { 51 log.G(ctx).WithFields(log.Fields{ 52 "image": img, 53 "error": err, 54 }).Warn("failed to get config descriptor for image") 55 return nil 56 } 57 58 info, err := c.is.content.Info(ctx, desc.Digest) 59 if err != nil { 60 if !cerrdefs.IsNotFound(err) { 61 log.G(ctx).WithFields(log.Fields{ 62 "image": img, 63 "desc": desc, 64 "error": err, 65 }).Warn("failed to get info of image config") 66 } 67 return nil 68 } 69 70 if dgstStr, ok := info.Labels[contentLabelGcRefContainerConfig]; ok { 71 dgst, err := digest.Parse(dgstStr) 72 if err != nil { 73 log.G(ctx).WithFields(log.Fields{ 74 "label": contentLabelClassicBuilderImage, 75 "value": dgstStr, 76 "content": desc.Digest, 77 "error": err, 78 }).Warn("invalid digest in label") 79 return nil 80 } 81 82 configDesc := ocispec.Descriptor{ 83 Digest: dgst, 84 } 85 86 var config container.Config 87 if err := readConfig(ctx, c.is.content, configDesc, &config); err != nil { 88 if !errdefs.IsNotFound(err) { 89 log.G(ctx).WithFields(log.Fields{ 90 "configDigest": dgst, 91 "error": err, 92 }).Warn("failed to read container config") 93 } 94 return nil 95 } 96 97 outImg.ContainerConfig = config 98 99 // We already have the config we looked for, so return an error to 100 // stop walking the image further. This error will be ignored. 101 return errFound 102 } 103 return nil 104 }) 105 if err != nil && err != errFound { 106 return nil, err 107 } 108 109 return outImg, nil 110 } 111 112 func (c cacheAdaptor) GetByRef(ctx context.Context, refOrId string) (*image.Image, error) { 113 return c.is.GetImage(ctx, refOrId, backend.GetImageOpts{}) 114 } 115 116 func (c cacheAdaptor) SetParent(target, parent image.ID) error { 117 ctx := context.TODO() 118 _, imgs, err := c.is.resolveAllReferences(ctx, target.String()) 119 if err != nil { 120 return fmt.Errorf("failed to list images with digest %q", target) 121 } 122 123 var errs []error 124 is := c.is.images 125 for _, img := range imgs { 126 if img.Labels == nil { 127 img.Labels = make(map[string]string) 128 } 129 img.Labels[imageLabelClassicBuilderParent] = parent.String() 130 if _, err := is.Update(ctx, img, "labels."+imageLabelClassicBuilderParent); err != nil { 131 errs = append(errs, fmt.Errorf("failed to update parent label on image %v: %w", img, err)) 132 } 133 } 134 135 return multierror.Join(errs...) 136 } 137 138 func (c cacheAdaptor) GetParent(target image.ID) (image.ID, error) { 139 ctx := context.TODO() 140 value, err := c.is.getImageLabelByDigest(ctx, target.Digest(), imageLabelClassicBuilderParent) 141 if err != nil { 142 return "", fmt.Errorf("failed to read parent image: %w", err) 143 } 144 145 dgst, err := digest.Parse(value) 146 if err != nil { 147 return "", fmt.Errorf("invalid parent value: %q", value) 148 } 149 150 return image.ID(dgst), nil 151 } 152 153 func (c cacheAdaptor) Create(parent *image.Image, target image.Image, extraLayer layer.DiffID) (image.ID, error) { 154 ctx := context.TODO() 155 data, err := json.Marshal(target) 156 if err != nil { 157 return "", fmt.Errorf("failed to marshal image config: %w", err) 158 } 159 160 var layerDigest digest.Digest 161 if extraLayer != "" { 162 info, err := findContentByUncompressedDigest(ctx, c.is.client.ContentStore(), digest.Digest(extraLayer)) 163 if err != nil { 164 return "", fmt.Errorf("failed to find content for diff ID %q: %w", extraLayer, err) 165 } 166 layerDigest = info.Digest 167 } 168 169 var parentRef string 170 if parent != nil { 171 parentRef = parent.ID().String() 172 } 173 img, err := c.is.CreateImage(ctx, data, parentRef, layerDigest) 174 if err != nil { 175 return "", fmt.Errorf("failed to created cached image: %w", err) 176 } 177 178 return image.ID(img.ImageID()), nil 179 } 180 181 func (c cacheAdaptor) IsBuiltLocally(target image.ID) (bool, error) { 182 ctx := context.TODO() 183 value, err := c.is.getImageLabelByDigest(ctx, target.Digest(), imageLabelClassicBuilderContainerConfig) 184 if err != nil { 185 return false, fmt.Errorf("failed to read container config label: %w", err) 186 } 187 return value != "", nil 188 } 189 190 func (c cacheAdaptor) Children(id image.ID) []image.ID { 191 ctx := context.TODO() 192 193 if id.String() == "" { 194 imgs, err := c.is.getImagesWithLabel(ctx, imageLabelClassicBuilderFromScratch, "1") 195 if err != nil { 196 log.G(ctx).WithError(err).Error("failed to get from scratch images") 197 return nil 198 } 199 return imgs 200 } 201 202 imgs, err := c.is.Children(ctx, id) 203 if err != nil { 204 log.G(ctx).WithError(err).Error("failed to get image children") 205 return nil 206 } 207 208 return imgs 209 } 210 211 func findContentByUncompressedDigest(ctx context.Context, cs content.Manager, uncompressed digest.Digest) (content.Info, error) { 212 var out content.Info 213 214 errStopWalk := errors.New("success") 215 err := cs.Walk(ctx, func(i content.Info) error { 216 out = i 217 return errStopWalk 218 }, `labels."containerd.io/uncompressed"==`+uncompressed.String()) 219 220 if err != nil && err != errStopWalk { 221 return out, err 222 } 223 if out.Digest == "" { 224 return out, errdefs.NotFound(errors.New("no content matches this uncompressed digest")) 225 } 226 return out, nil 227 }