automation-tests/common/libimage/layer_tree.go

300 lines
8.0 KiB
Go

package libimage
import (
"context"
"github.com/containers/storage"
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
}
// toOCI returns an OCI image for the specified image.
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() (*layerTree, error) {
layers, err := r.store.Layers()
if err != nil {
return nil, err
}
images, err := r.ListImages(context.Background(), 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 reset`.", 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 {
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 {
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]
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 reset`.", 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 {
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
}
emptyOCI, err := t.toOCI(ctx, empty)
if err != 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 reset`.", 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 {
return nil, err
}
// History check.
if areParentAndChild(parentOCI, childOCI) {
return parent, nil
}
}
return nil, nil
}