300 lines
8.0 KiB
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
|
|
}
|