| package containerd | |
| import ( | |
| "context" | |
| "github.com/containerd/containerd/content" | |
| cerrdefs "github.com/containerd/containerd/errdefs" | |
| containerdimages "github.com/containerd/containerd/images" | |
| "github.com/containerd/containerd/platforms" | |
| "github.com/docker/docker/errdefs" | |
| "github.com/docker/docker/image" | |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" | |
| "github.com/pkg/errors" | |
| "github.com/sirupsen/logrus" | |
| ) | |
| // Children returns a slice of image ID which rootfs is a superset of the | |
| // rootfs of the given image ID, excluding images with exactly the same rootfs. | |
| // Called from list.go to filter containers. | |
| func (i *ImageService) Children(ctx context.Context, id image.ID) ([]image.ID, error) { | |
| target, err := i.resolveDescriptor(ctx, id.String()) | |
| if err != nil { | |
| return []image.ID{}, errors.Wrap(err, "failed to get parent image") | |
| } | |
| cs := i.client.ContentStore() | |
| allPlatforms, err := containerdimages.Platforms(ctx, cs, target) | |
| if err != nil { | |
| return []image.ID{}, errdefs.System(errors.Wrap(err, "failed to list platforms supported by image")) | |
| } | |
| parentRootFS := []ocispec.RootFS{} | |
| for _, platform := range allPlatforms { | |
| rootfs, err := platformRootfs(ctx, cs, target, platform) | |
| if err != nil { | |
| if !cerrdefs.IsNotFound(err) { | |
| logrus.WithFields(logrus.Fields{ | |
| logrus.ErrorKey: err, | |
| "image": target.Digest, | |
| "platform": platform, | |
| }).Warning("failed to get platform-specific rootfs") | |
| } | |
| continue | |
| } | |
| parentRootFS = append(parentRootFS, rootfs) | |
| } | |
| imgs, err := i.client.ImageService().List(ctx) | |
| if err != nil { | |
| return []image.ID{}, errdefs.System(errors.Wrap(err, "failed to list all images")) | |
| } | |
| children := []image.ID{} | |
| for _, img := range imgs { | |
| nextImage: | |
| for _, platform := range allPlatforms { | |
| rootfs, err := platformRootfs(ctx, cs, img.Target, platform) | |
| if err != nil { | |
| if !cerrdefs.IsNotFound(err) { | |
| logrus.WithFields(logrus.Fields{ | |
| logrus.ErrorKey: err, | |
| "image": img.Target.Digest, | |
| "platform": platform, | |
| }).Warning("failed to get platform-specific rootfs") | |
| } | |
| continue | |
| } | |
| for _, parentRoot := range parentRootFS { | |
| if isRootfsChildOf(rootfs, parentRoot) { | |
| children = append(children, image.ID(img.Target.Digest)) | |
| break nextImage | |
| } | |
| } | |
| } | |
| } | |
| return children, nil | |
| } | |
| // platformRootfs returns a rootfs for a specified platform. | |
| func platformRootfs(ctx context.Context, store content.Store, desc ocispec.Descriptor, platform ocispec.Platform) (ocispec.RootFS, error) { | |
| empty := ocispec.RootFS{} | |
| configDesc, err := containerdimages.Config(ctx, store, desc, platforms.OnlyStrict(platform)) | |
| if err != nil { | |
| return empty, errors.Wrapf(err, "failed to get config for platform %s", platforms.Format(platform)) | |
| } | |
| diffs, err := containerdimages.RootFS(ctx, store, configDesc) | |
| if err != nil { | |
| return empty, errors.Wrapf(err, "failed to obtain rootfs") | |
| } | |
| return ocispec.RootFS{ | |
| Type: "layers", | |
| DiffIDs: diffs, | |
| }, nil | |
| } | |
| // isRootfsChildOf checks if all layers from parent rootfs are child's first layers | |
| // and child has at least one more layer (to make it not commutative). | |
| // Example: | |
| // A with layers [X, Y], | |
| // B with layers [X, Y, Z] | |
| // C with layers [Y, Z] | |
| // | |
| // Only isRootfsChildOf(B, A) is true. | |
| // Which means that B is considered a children of A. B and C has no children. | |
| // See more examples in TestIsRootfsChildOf. | |
| func isRootfsChildOf(child ocispec.RootFS, parent ocispec.RootFS) bool { | |
| childLen := len(child.DiffIDs) | |
| parentLen := len(parent.DiffIDs) | |
| if childLen <= parentLen { | |
| return false | |
| } | |
| for i := 0; i < parentLen; i++ { | |
| if child.DiffIDs[i] != parent.DiffIDs[i] { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| // parents returns a slice of image IDs whose entire rootfs contents match, | |
| // in order, the childs first layers, excluding images with the exact same | |
| // rootfs. | |
| // | |
| // Called from image_delete.go to prune dangling parents. | |
| func (i *ImageService) parents(ctx context.Context, id image.ID) ([]imageWithRootfs, error) { | |
| target, err := i.resolveDescriptor(ctx, id.String()) | |
| if err != nil { | |
| return nil, errors.Wrap(err, "failed to get child image") | |
| } | |
| cs := i.client.ContentStore() | |
| allPlatforms, err := containerdimages.Platforms(ctx, cs, target) | |
| if err != nil { | |
| return nil, errdefs.System(errors.Wrap(err, "failed to list platforms supported by image")) | |
| } | |
| var childRootFS []ocispec.RootFS | |
| for _, platform := range allPlatforms { | |
| rootfs, err := platformRootfs(ctx, cs, target, platform) | |
| if err != nil { | |
| if cerrdefs.IsNotFound(err) { | |
| continue | |
| } | |
| return nil, errdefs.System(errors.Wrap(err, "failed to get platform-specific rootfs")) | |
| } | |
| childRootFS = append(childRootFS, rootfs) | |
| } | |
| imgs, err := i.client.ImageService().List(ctx) | |
| if err != nil { | |
| return nil, errdefs.System(errors.Wrap(err, "failed to list all images")) | |
| } | |
| var parents []imageWithRootfs | |
| for _, img := range imgs { | |
| nextImage: | |
| for _, platform := range allPlatforms { | |
| rootfs, err := platformRootfs(ctx, cs, img.Target, platform) | |
| if err != nil { | |
| if cerrdefs.IsNotFound(err) { | |
| continue | |
| } | |
| return nil, errdefs.System(errors.Wrap(err, "failed to get platform-specific rootfs")) | |
| } | |
| for _, childRoot := range childRootFS { | |
| if isRootfsChildOf(childRoot, rootfs) { | |
| parents = append(parents, imageWithRootfs{ | |
| img: img, | |
| rootfs: rootfs, | |
| }) | |
| break nextImage | |
| } | |
| } | |
| } | |
| } | |
| return parents, nil | |
| } | |
| type imageWithRootfs struct { | |
| img containerdimages.Image | |
| rootfs ocispec.RootFS | |
| } |