cache.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. package containerd
  2. import (
  3. "context"
  4. "reflect"
  5. "strings"
  6. "github.com/docker/docker/api/types/container"
  7. imagetype "github.com/docker/docker/api/types/image"
  8. "github.com/docker/docker/builder"
  9. "github.com/docker/docker/image"
  10. )
  11. // MakeImageCache creates a stateful image cache.
  12. func (i *ImageService) MakeImageCache(ctx context.Context, cacheFrom []string) (builder.ImageCache, error) {
  13. images := []*image.Image{}
  14. for _, c := range cacheFrom {
  15. im, err := i.GetImage(ctx, c, imagetype.GetImageOpts{})
  16. if err != nil {
  17. return nil, err
  18. }
  19. images = append(images, im)
  20. }
  21. return &imageCache{images: images, c: i}, nil
  22. }
  23. type imageCache struct {
  24. images []*image.Image
  25. c *ImageService
  26. }
  27. func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
  28. ctx := context.TODO()
  29. if parentID == "" {
  30. // TODO handle "parentless" image cache lookups ("FROM scratch")
  31. return "", nil
  32. }
  33. parent, err := ic.c.GetImage(ctx, parentID, imagetype.GetImageOpts{})
  34. if err != nil {
  35. return "", err
  36. }
  37. for _, localCachedImage := range ic.images {
  38. if isMatch(localCachedImage, parent, cfg) {
  39. return localCachedImage.ID().String(), nil
  40. }
  41. }
  42. children, err := ic.c.Children(ctx, parent.ID())
  43. if err != nil {
  44. return "", err
  45. }
  46. for _, children := range children {
  47. childImage, err := ic.c.GetImage(ctx, children.String(), imagetype.GetImageOpts{})
  48. if err != nil {
  49. return "", err
  50. }
  51. if isMatch(childImage, parent, cfg) {
  52. return children.String(), nil
  53. }
  54. }
  55. return "", nil
  56. }
  57. // isMatch checks whether a given target can be used as cache for the given
  58. // parent image/config combination.
  59. // A target can only be an immediate child of the given parent image. For
  60. // a parent image with `n` history entries, a valid target must have `n+1`
  61. // entries and the extra entry must match the provided config
  62. func isMatch(target, parent *image.Image, cfg *container.Config) bool {
  63. if target == nil || parent == nil || cfg == nil {
  64. return false
  65. }
  66. if len(target.History) != len(parent.History)+1 ||
  67. len(target.RootFS.DiffIDs) != len(parent.RootFS.DiffIDs)+1 {
  68. return false
  69. }
  70. for i := range parent.History {
  71. if !reflect.DeepEqual(parent.History[i], target.History[i]) {
  72. return false
  73. }
  74. }
  75. childCreatedBy := target.History[len(target.History)-1].CreatedBy
  76. return childCreatedBy == strings.Join(cfg.Cmd, " ")
  77. }