382 lines
11 KiB
Go
382 lines
11 KiB
Go
//go:build !remote
|
|
|
|
package libimage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/containers/storage"
|
|
storageTypes "github.com/containers/storage/types"
|
|
digest "github.com/opencontainers/go-digest"
|
|
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// layerTree is an internal representation of local layers.
|
|
type layerTree struct {
|
|
// nodes is the actual layer tree with layer IDs being keys.
|
|
nodes map[string]*layerNode
|
|
// ociCache is a cache for Image.ID -> OCI Image. Translations are done
|
|
// on-demand.
|
|
ociCache map[string]*ociv1.Image
|
|
// emptyImages do not have any top-layer so we cannot create a
|
|
// *layerNode for them.
|
|
emptyImages []*Image
|
|
// manifestList keep track of images based on their digest.
|
|
// Library will use this map when checking if a image is dangling.
|
|
// If an image is used in a manifestList it is NOT dangling
|
|
manifestListDigests map[digest.Digest]struct{}
|
|
}
|
|
|
|
// node returns a layerNode for the specified layerID.
|
|
func (t *layerTree) node(layerID string) *layerNode {
|
|
node, exists := t.nodes[layerID]
|
|
if !exists {
|
|
node = &layerNode{}
|
|
t.nodes[layerID] = node
|
|
}
|
|
return node
|
|
}
|
|
|
|
// ErrorIsImageUnknown returns true if the specified error indicates that an
|
|
// image is unknown or has been partially removed (e.g., a missing layer).
|
|
func ErrorIsImageUnknown(err error) bool {
|
|
return errors.Is(err, storage.ErrImageUnknown) ||
|
|
errors.Is(err, storageTypes.ErrLayerUnknown) ||
|
|
errors.Is(err, storageTypes.ErrSizeUnknown) ||
|
|
errors.Is(err, storage.ErrNotAnImage)
|
|
}
|
|
|
|
// toOCI returns an OCI image for the specified image.
|
|
//
|
|
// WARNING: callers are responsible for handling cases where the target image
|
|
// has been (partially) removed and can use `ErrorIsImageUnknown` to detect it.
|
|
func (t *layerTree) toOCI(ctx context.Context, i *Image) (*ociv1.Image, error) {
|
|
var err error
|
|
oci, exists := t.ociCache[i.ID()]
|
|
if !exists {
|
|
oci, err = i.toOCI(ctx)
|
|
if err == nil {
|
|
t.ociCache[i.ID()] = oci
|
|
}
|
|
}
|
|
return oci, err
|
|
}
|
|
|
|
// layerNode is a node in a layerTree. It's ID is the key in a layerTree.
|
|
type layerNode struct {
|
|
children []*layerNode
|
|
images []*Image
|
|
parent *layerNode
|
|
layer *storage.Layer
|
|
}
|
|
|
|
// repoTags assemble all repo tags all of images of the layer node.
|
|
func (l *layerNode) repoTags() ([]string, error) {
|
|
orderedTags := []string{}
|
|
visitedTags := make(map[string]bool)
|
|
|
|
for _, image := range l.images {
|
|
repoTags, err := image.RepoTags()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, tag := range repoTags {
|
|
if _, visited := visitedTags[tag]; visited {
|
|
continue
|
|
}
|
|
visitedTags[tag] = true
|
|
orderedTags = append(orderedTags, tag)
|
|
}
|
|
}
|
|
|
|
return orderedTags, nil
|
|
}
|
|
|
|
// newFreshLayerTree extracts a layerTree from consistent layers and images in the local storage.
|
|
func (r *Runtime) newFreshLayerTree() (*layerTree, error) {
|
|
images, layers, err := r.getImagesAndLayers()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.newLayerTreeFromData(images, layers, false)
|
|
}
|
|
|
|
// newLayerTreeFromData extracts a layerTree from the given the layers and images.
|
|
// The caller is responsible for (layers, images) being consistent.
|
|
func (r *Runtime) newLayerTreeFromData(images []*Image, layers []storage.Layer, generateManifestDigestList bool) (*layerTree, error) {
|
|
tree := layerTree{
|
|
nodes: make(map[string]*layerNode),
|
|
ociCache: make(map[string]*ociv1.Image),
|
|
manifestListDigests: make(map[digest.Digest]struct{}),
|
|
}
|
|
|
|
// First build a tree purely based on layer information.
|
|
for i := range layers {
|
|
node := tree.node(layers[i].ID)
|
|
node.layer = &layers[i]
|
|
if layers[i].Parent == "" {
|
|
continue
|
|
}
|
|
parent := tree.node(layers[i].Parent)
|
|
node.parent = parent
|
|
parent.children = append(parent.children, node)
|
|
}
|
|
|
|
// Now assign the images to each (top) layer.
|
|
for i := range images {
|
|
img := images[i] // do not leak loop variable outside the scope
|
|
topLayer := img.TopLayer()
|
|
if topLayer == "" {
|
|
tree.emptyImages = append(tree.emptyImages, img)
|
|
// When img is a manifest list, cache the lists of
|
|
// digests refereenced in manifest list. Digests can
|
|
// be used to check for dangling images.
|
|
if !generateManifestDigestList {
|
|
continue
|
|
}
|
|
// ignore errors, common errors are
|
|
// - image is not manifest
|
|
// - image has been removed from the store in the meantime
|
|
// In all cases we should ensure image listing still works and not error out.
|
|
mlist, err := img.ToManifestList()
|
|
if err != nil {
|
|
// If it is not a manifest it likely is a regular image so just ignore it.
|
|
// If the image is unknown that likely means there was a race where the image/manifest
|
|
// was removed after out MultiList() call so we ignore that as well.
|
|
if errors.Is(err, ErrNotAManifestList) || errors.Is(err, storageTypes.ErrImageUnknown) {
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
for _, digest := range mlist.list.Instances() {
|
|
tree.manifestListDigests[digest] = struct{}{}
|
|
}
|
|
continue
|
|
}
|
|
node, exists := tree.nodes[topLayer]
|
|
if !exists {
|
|
// Note: erroring out in this case has turned out having been a
|
|
// mistake. Users may not be able to recover, so we're now
|
|
// throwing a warning to guide them to resolve the issue and
|
|
// turn the errors non-fatal.
|
|
logrus.Warnf("Top layer %s of image %s not found in layer tree. The storage may be corrupted, consider running `podman system check`.", topLayer, img.ID())
|
|
continue
|
|
}
|
|
node.images = append(node.images, img)
|
|
}
|
|
|
|
return &tree, nil
|
|
}
|
|
|
|
// children returns the child images of parent. Child images are images with
|
|
// either the same top layer as parent or parent being the true parent layer.
|
|
// Furthermore, the history of the parent and child images must match with the
|
|
// parent having one history item less. If all is true, all images are
|
|
// returned. Otherwise, the first image is returned. Note that manifest lists
|
|
// do not have children.
|
|
func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]*Image, error) {
|
|
if parent.TopLayer() == "" {
|
|
if isManifestList, _ := parent.IsManifestList(ctx); isManifestList {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
parentID := parent.ID()
|
|
parentOCI, err := t.toOCI(ctx, parent)
|
|
if err != nil {
|
|
if ErrorIsImageUnknown(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// checkParent returns true if child and parent are in such a relation.
|
|
checkParent := func(child *Image) (bool, error) {
|
|
if parentID == child.ID() {
|
|
return false, nil
|
|
}
|
|
childOCI, err := t.toOCI(ctx, child)
|
|
if err != nil {
|
|
if ErrorIsImageUnknown(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
// History check.
|
|
return areParentAndChild(parentOCI, childOCI), nil
|
|
}
|
|
|
|
var children []*Image
|
|
|
|
// Empty images are special in that they do not have any physical layer
|
|
// but yet can have a parent-child relation. Hence, compare the
|
|
// "parent" image to all other known empty images.
|
|
if parent.TopLayer() == "" {
|
|
for i := range t.emptyImages {
|
|
empty := t.emptyImages[i]
|
|
isManifest, err := empty.IsManifestList(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isManifest {
|
|
// If this is a manifest list and is already
|
|
// marked as empty then no instance can be
|
|
// selected from this list therefore its
|
|
// better to skip this.
|
|
continue
|
|
}
|
|
isParent, err := checkParent(empty)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isParent {
|
|
children = append(children, empty)
|
|
if !all {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return children, nil
|
|
}
|
|
|
|
parentNode, exists := t.nodes[parent.TopLayer()]
|
|
if !exists {
|
|
// Note: erroring out in this case has turned out having been a
|
|
// mistake. Users may not be able to recover, so we're now
|
|
// throwing a warning to guide them to resolve the issue and
|
|
// turn the errors non-fatal.
|
|
logrus.Warnf("Layer %s not found in layer tree. The storage may be corrupted, consider running `podman system check`.", parent.TopLayer())
|
|
return children, nil
|
|
}
|
|
|
|
// addChildrenFrom adds child images of parent to children. Returns
|
|
// true if any image is a child of parent.
|
|
addChildrenFromNode := func(node *layerNode) (bool, error) {
|
|
foundChildren := false
|
|
for i, childImage := range node.images {
|
|
isChild, err := checkParent(childImage)
|
|
if err != nil {
|
|
return foundChildren, err
|
|
}
|
|
if isChild {
|
|
foundChildren = true
|
|
children = append(children, node.images[i])
|
|
if all {
|
|
return foundChildren, nil
|
|
}
|
|
}
|
|
}
|
|
return foundChildren, nil
|
|
}
|
|
|
|
// First check images where parent's top layer is also the parent
|
|
// layer.
|
|
for _, childNode := range parentNode.children {
|
|
found, err := addChildrenFromNode(childNode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if found && all {
|
|
return children, nil
|
|
}
|
|
}
|
|
|
|
// Now check images with the same top layer.
|
|
if _, err := addChildrenFromNode(parentNode); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return children, nil
|
|
}
|
|
|
|
// parent returns the parent image or nil if no parent image could be found.
|
|
// Note that manifest lists do not have parents.
|
|
func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) {
|
|
if child.TopLayer() == "" {
|
|
if isManifestList, _ := child.IsManifestList(ctx); isManifestList {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
childID := child.ID()
|
|
childOCI, err := t.toOCI(ctx, child)
|
|
if err != nil {
|
|
if ErrorIsImageUnknown(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Empty images are special in that they do not have any physical layer
|
|
// but yet can have a parent-child relation. Hence, compare the
|
|
// "child" image to all other known empty images.
|
|
if child.TopLayer() == "" {
|
|
for _, empty := range t.emptyImages {
|
|
if childID == empty.ID() {
|
|
continue
|
|
}
|
|
isManifest, err := empty.IsManifestList(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isManifest {
|
|
// If this is a manifest list and is already
|
|
// marked as empty then no instance can be
|
|
// selected from this list therefore its
|
|
// better to skip this.
|
|
continue
|
|
}
|
|
emptyOCI, err := t.toOCI(ctx, empty)
|
|
if err != nil {
|
|
if ErrorIsImageUnknown(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
// History check.
|
|
if areParentAndChild(emptyOCI, childOCI) {
|
|
return empty, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
node, exists := t.nodes[child.TopLayer()]
|
|
if !exists {
|
|
// Note: erroring out in this case has turned out having been a
|
|
// mistake. Users may not be able to recover, so we're now
|
|
// throwing a warning to guide them to resolve the issue and
|
|
// turn the errors non-fatal.
|
|
logrus.Warnf("Layer %s not found in layer tree. The storage may be corrupted, consider running `podman system check`.", child.TopLayer())
|
|
return nil, nil
|
|
}
|
|
|
|
// Check images from the parent node (i.e., parent layer) and images
|
|
// with the same layer (i.e., same top layer).
|
|
images := node.images
|
|
if node.parent != nil {
|
|
images = append(images, node.parent.images...)
|
|
}
|
|
for _, parent := range images {
|
|
if parent.ID() == childID {
|
|
continue
|
|
}
|
|
parentOCI, err := t.toOCI(ctx, parent)
|
|
if err != nil {
|
|
if ErrorIsImageUnknown(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
// History check.
|
|
if areParentAndChild(parentOCI, childOCI) {
|
|
return parent, nil
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|