package image
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
const (
ImageKindLabel = "io.conch.image.kind"
ImageKindOCIImage = "oci-image"
ImageKindBootIndexCold = "boot-index-cold"
ImageKindBootIndexResume = "boot-index-resume"
ImageKindBootComponentRootfs = "boot-component-rootfs"
ImageKindBootComponentSandbox = "boot-component-sandbox"
ImageKindBootComponentMemory = "boot-component-memory"
)
func DetectImageKind(ctx context.Context, store content.Store, target ocispec.Descriptor) (string, error) {
if target.MediaType != ocispec.MediaTypeImageIndex {
return ImageKindOCIImage, nil
}
if store == nil {
return "", fmt.Errorf("content store is required")
}
raw, err := content.ReadBlob(ctx, store, target)
if err != nil {
return "", fmt.Errorf("read image index %s: %w", target.Digest, err)
}
var index ocispec.Index
if err := json.Unmarshal(raw, &index); err != nil {
return "", ErrInvalidContent.Wrap(fmt.Errorf("unmarshal image index %s: %w", target.Digest, err))
}
if index.MediaType != "" && index.MediaType != ocispec.MediaTypeImageIndex {
return "", ErrInvalidContent.Wrap(fmt.Errorf("image index %s declares media type %q", target.Digest, index.MediaType))
}
if !hasConchIndexMetadata(index) {
return ImageKindOCIImage, nil
}
info, err := inspectBootIndexMetadata(target, index)
if err != nil {
return "", ErrInvalidContent.Wrap(fmt.Errorf("invalid Conch Boot Index %s: %w", target.Digest, err))
}
if info.Resume {
return ImageKindBootIndexResume, nil
}
return ImageKindBootIndexCold, nil
}
func hasConchIndexMetadata(index ocispec.Index) bool {
for key := range index.Annotations {
if strings.HasPrefix(key, "io.conch.") {
return true
}
}
for _, manifest := range index.Manifests {
for key := range manifest.Annotations {
if strings.HasPrefix(key, "io.conch.") {
return true
}
}
}
return false
}
func SetImageKindLabel(ctx context.Context, store images.Store, imageName, kind string) error {
if store == nil {
return fmt.Errorf("image store is required")
}
imageName = strings.TrimSpace(imageName)
if imageName == "" {
return fmt.Errorf("image name is required")
}
if !validImageKind(kind) {
return fmt.Errorf("invalid image kind %q", kind)
}
_, err := store.Update(ctx, images.Image{
Name: imageName,
Labels: map[string]string{
ImageKindLabel: kind,
},
}, "labels."+ImageKindLabel)
if err != nil {
return fmt.Errorf("label image %s as %s: %w", imageName, kind, err)
}
return nil
}
func componentImageKind(componentKind string) string {
switch componentKind {
case KindRootfs:
return ImageKindBootComponentRootfs
case KindSandbox:
return ImageKindBootComponentSandbox
case KindMemSnapshot:
return ImageKindBootComponentMemory
default:
return ImageKindOCIImage
}
}
func validImageKind(kind string) bool {
switch kind {
case ImageKindOCIImage,
ImageKindBootIndexCold,
ImageKindBootIndexResume,
ImageKindBootComponentRootfs,
ImageKindBootComponentSandbox,
ImageKindBootComponentMemory:
return true
default:
return false
}
}