mirror of
				https://github.com/containers/podman.git
				synced 2025-11-04 08:56:05 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			368 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			368 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
//go:build !remote
 | 
						|
 | 
						|
package libimage
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
 | 
						|
	"github.com/containers/storage"
 | 
						|
	storageTypes "github.com/containers/storage/types"
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
}
 | 
						|
 | 
						|
// layerTree extracts a layerTree from the layers in the local storage and
 | 
						|
// relates them to the specified images.
 | 
						|
func (r *Runtime) layerTree(ctx context.Context, images []*Image) (*layerTree, error) {
 | 
						|
	layers, err := r.store.Layers()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if images == nil {
 | 
						|
		images, err = r.ListImages(ctx, nil, nil)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	tree := layerTree{
 | 
						|
		nodes:    make(map[string]*layerNode),
 | 
						|
		ociCache: make(map[string]*ociv1.Image),
 | 
						|
	}
 | 
						|
 | 
						|
	// 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)
 | 
						|
			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
 | 
						|
}
 | 
						|
 | 
						|
// layersOf returns all storage layers of the specified image.
 | 
						|
func (t *layerTree) layersOf(image *Image) []*storage.Layer {
 | 
						|
	var layers []*storage.Layer
 | 
						|
	node := t.node(image.TopLayer())
 | 
						|
	for node != nil {
 | 
						|
		if node.layer != nil {
 | 
						|
			layers = append(layers, node.layer)
 | 
						|
		}
 | 
						|
		node = node.parent
 | 
						|
	}
 | 
						|
	return layers
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
}
 |