github.com/rish1988/moby@v25.0.2+incompatible/daemon/containerd/cache.go (about) 1 package containerd 2 3 import ( 4 "context" 5 "reflect" 6 "strings" 7 8 "github.com/containerd/log" 9 "github.com/docker/docker/api/types/backend" 10 "github.com/docker/docker/api/types/container" 11 "github.com/docker/docker/builder" 12 "github.com/docker/docker/errdefs" 13 "github.com/docker/docker/image" 14 "github.com/opencontainers/go-digest" 15 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 16 ) 17 18 // MakeImageCache creates a stateful image cache. 19 func (i *ImageService) MakeImageCache(ctx context.Context, cacheFrom []string) (builder.ImageCache, error) { 20 images := []*image.Image{} 21 if len(cacheFrom) == 0 { 22 return &localCache{ 23 imageService: i, 24 }, nil 25 } 26 27 for _, c := range cacheFrom { 28 h, err := i.ImageHistory(ctx, c) 29 if err != nil { 30 continue 31 } 32 for _, hi := range h { 33 if hi.ID != "<missing>" { 34 im, err := i.GetImage(ctx, hi.ID, backend.GetImageOpts{}) 35 if err != nil { 36 return nil, err 37 } 38 images = append(images, im) 39 } 40 } 41 } 42 43 return &imageCache{ 44 lc: &localCache{ 45 imageService: i, 46 }, 47 images: images, 48 imageService: i, 49 }, nil 50 } 51 52 type localCache struct { 53 imageService *ImageService 54 } 55 56 func (ic *localCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) { 57 ctx := context.TODO() 58 59 var children []image.ID 60 61 // FROM scratch 62 if parentID == "" { 63 c, err := ic.imageService.getImagesWithLabel(ctx, imageLabelClassicBuilderFromScratch, "1") 64 if err != nil { 65 return "", err 66 } 67 children = c 68 } else { 69 c, err := ic.imageService.Children(ctx, image.ID(parentID)) 70 if err != nil { 71 return "", err 72 } 73 children = c 74 } 75 76 var match *image.Image 77 for _, child := range children { 78 ccDigestStr, err := ic.imageService.getImageLabelByDigest(ctx, child.Digest(), imageLabelClassicBuilderContainerConfig) 79 if err != nil { 80 return "", err 81 } 82 if ccDigestStr == "" { 83 continue 84 } 85 86 dgst, err := digest.Parse(ccDigestStr) 87 if err != nil { 88 log.G(ctx).WithError(err).Warnf("invalid container config digest: %q", ccDigestStr) 89 continue 90 } 91 92 var cc container.Config 93 if err := readConfig(ctx, ic.imageService.content, ocispec.Descriptor{Digest: dgst}, &cc); err != nil { 94 if errdefs.IsNotFound(err) { 95 log.G(ctx).WithError(err).WithField("image", child).Warnf("missing container config: %q", ccDigestStr) 96 continue 97 } 98 return "", err 99 } 100 101 if isMatch(&cc, cfg) { 102 childImage, err := ic.imageService.GetImage(ctx, child.String(), backend.GetImageOpts{}) 103 if err != nil { 104 return "", err 105 } 106 107 if childImage.Created != nil && (match == nil || match.Created.Before(*childImage.Created)) { 108 match = childImage 109 } 110 } 111 } 112 113 if match == nil { 114 return "", nil 115 } 116 117 return match.ID().String(), nil 118 } 119 120 type imageCache struct { 121 images []*image.Image 122 imageService *ImageService 123 lc *localCache 124 } 125 126 func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) { 127 ctx := context.TODO() 128 129 imgID, err := ic.lc.GetCache(parentID, cfg) 130 if err != nil { 131 return "", err 132 } 133 if imgID != "" { 134 for _, s := range ic.images { 135 if ic.isParent(ctx, s, image.ID(imgID)) { 136 return imgID, nil 137 } 138 } 139 } 140 141 var parent *image.Image 142 lenHistory := 0 143 144 if parentID != "" { 145 parent, err = ic.imageService.GetImage(ctx, parentID, backend.GetImageOpts{}) 146 if err != nil { 147 return "", err 148 } 149 lenHistory = len(parent.History) 150 } 151 for _, target := range ic.images { 152 if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) { 153 continue 154 } 155 return target.ID().String(), nil 156 } 157 158 return "", nil 159 } 160 161 func isValidConfig(cfg *container.Config, h image.History) bool { 162 // todo: make this format better than join that loses data 163 return strings.Join(cfg.Cmd, " ") == h.CreatedBy 164 } 165 166 func isValidParent(img, parent *image.Image) bool { 167 if len(img.History) == 0 { 168 return false 169 } 170 if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 { 171 return true 172 } 173 if len(parent.History) >= len(img.History) { 174 return false 175 } 176 if len(parent.RootFS.DiffIDs) > len(img.RootFS.DiffIDs) { 177 return false 178 } 179 180 for i, h := range parent.History { 181 if !reflect.DeepEqual(h, img.History[i]) { 182 return false 183 } 184 } 185 for i, d := range parent.RootFS.DiffIDs { 186 if d != img.RootFS.DiffIDs[i] { 187 return false 188 } 189 } 190 return true 191 } 192 193 func (ic *imageCache) isParent(ctx context.Context, img *image.Image, parentID image.ID) bool { 194 ii, err := ic.imageService.resolveImage(ctx, img.ImageID()) 195 if err != nil { 196 return false 197 } 198 parent, ok := ii.Labels[imageLabelClassicBuilderParent] 199 if ok { 200 return parent == parentID.String() 201 } 202 203 p, err := ic.imageService.GetImage(ctx, parentID.String(), backend.GetImageOpts{}) 204 if err != nil { 205 return false 206 } 207 return ic.isParent(ctx, p, parentID) 208 } 209 210 // compare two Config struct. Do not compare the "Image" nor "Hostname" fields 211 // If OpenStdin is set, then it differs 212 func isMatch(a, b *container.Config) bool { 213 if a == nil || b == nil || 214 a.OpenStdin || b.OpenStdin { 215 return false 216 } 217 if a.AttachStdout != b.AttachStdout || 218 a.AttachStderr != b.AttachStderr || 219 a.User != b.User || 220 a.OpenStdin != b.OpenStdin || 221 a.Tty != b.Tty { 222 return false 223 } 224 225 if len(a.Cmd) != len(b.Cmd) || 226 len(a.Env) != len(b.Env) || 227 len(a.Labels) != len(b.Labels) || 228 len(a.ExposedPorts) != len(b.ExposedPorts) || 229 len(a.Entrypoint) != len(b.Entrypoint) || 230 len(a.Volumes) != len(b.Volumes) { 231 return false 232 } 233 234 for i := 0; i < len(a.Cmd); i++ { 235 if a.Cmd[i] != b.Cmd[i] { 236 return false 237 } 238 } 239 for i := 0; i < len(a.Env); i++ { 240 if a.Env[i] != b.Env[i] { 241 return false 242 } 243 } 244 for k, v := range a.Labels { 245 if v != b.Labels[k] { 246 return false 247 } 248 } 249 for k := range a.ExposedPorts { 250 if _, exists := b.ExposedPorts[k]; !exists { 251 return false 252 } 253 } 254 255 for i := 0; i < len(a.Entrypoint); i++ { 256 if a.Entrypoint[i] != b.Entrypoint[i] { 257 return false 258 } 259 } 260 for key := range a.Volumes { 261 if _, exists := b.Volumes[key]; !exists { 262 return false 263 } 264 } 265 return true 266 }