mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 18:08:51 +08:00 
			
		
		
		
	 c6fe5e5395
			
		
	
	c6fe5e5395
	
	
	
		
			
			This commit vendor pre-release version of `c/common:8483ef6022b4`. It also adapts the code to the new `c/common/libimage` API, which fixes an image listing race that was listing false warnings. fixes: #23331 Signed-off-by: Jan Rodák <hony.com@seznam.cz>
		
			
				
	
	
		
			365 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
		
			10 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
 | |
| }
 | |
| 
 | |
| // 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)
 | |
| }
 | |
| 
 | |
| // 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) (*layerTree, error) {
 | |
| 	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
 | |
| }
 |