Files
Brent Baude 2430fc71a0 Allow podman machine to download from oci registry
this pr represents a shift in how we download podman machine images.
these images will now be stored in oci registry and will replace the
default method of downloading an image.  you can still use a reference
to a disk image as a path or url too with the --image-path switch.

the final registry and location of the images has not been determined;
and will need to be updated in podman as well.

i dont think we need to allow --image-path to accept a registry/image
for the podman 5.0 release.  i do think there will be demand for this.

upgrades also need to be plumbed.  for example, updating from an oci
registry.

once we make decisions on final image locations/registrties as well as
some behaviors of init and the oci pull, we must update the machine-init
documentation.

Signed-off-by: Brent Baude <bbaude@redhat.com>
2024-02-12 15:19:34 -06:00

160 lines
4.4 KiB
Go

package ocipull
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
specV1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
// readManifestFromImageSource reads the manifest from the specified image
// source. Note that the manifest is expected to be an OCI v1 manifest.
func readManifestFromImageSource(ctx context.Context, src types.ImageSource) (*specV1.Manifest, *digest.Digest, int64, error) {
rawData, mimeType, err := src.GetManifest(ctx, nil)
if err != nil {
return nil, nil, -1, err
}
if mimeType != specV1.MediaTypeImageManifest {
return nil, nil, -1, fmt.Errorf("image %q is of type %q (expected: %q)", strings.TrimPrefix(src.Reference().StringWithinTransport(), "//"), mimeType, specV1.MediaTypeImageManifest)
}
mannyFest := specV1.Manifest{}
if err := json.Unmarshal(rawData, &mannyFest); err != nil {
return nil, nil, -1, fmt.Errorf("reading manifest: %w", err)
}
manifestDigest := digest.FromBytes(rawData)
return &mannyFest, &manifestDigest, int64(len(rawData)), nil
}
func GetLocalBlob(ctx context.Context, path string) (*types.BlobInfo, error) {
ociRef, err := layout.ParseReference(path)
if err != nil {
return nil, err
}
img, err := ociRef.NewImage(ctx, &types.SystemContext{})
if err != nil {
return nil, err
}
b, _, err := img.Manifest(ctx)
if err != nil {
return nil, err
}
localManifest := specV1.Manifest{}
if err := json.Unmarshal(b, &localManifest); err != nil {
return nil, err
}
blobs := img.LayerInfos()
if err != nil {
return nil, err
}
if len(blobs) != 1 {
return nil, errors.New("invalid disk image")
}
fmt.Println(blobs[0].Digest.Hex())
return &blobs[0], nil
}
func GetRemoteManifest(ctx context.Context, dest string) (*specV1.Manifest, error) { //nolint:unused
ref, err := docker.ParseReference(fmt.Sprintf("//%s", dest))
if err != nil {
return nil, err
}
imgSrc, err := ref.NewImage(ctx, &types.SystemContext{})
if err != nil {
return nil, err
}
b, _, err := imgSrc.Manifest(ctx)
if err != nil {
return nil, err
}
remoteManifest := specV1.Manifest{}
err = json.Unmarshal(b, &remoteManifest)
return &remoteManifest, err
}
func GetDiskArtifactReference(ctx context.Context, imgSrc types.ImageSource, opts *DiskArtifactOpts) (digest.Digest, error) {
rawMannyFest, mannyType, err := imgSrc.GetManifest(ctx, nil)
if err != nil {
return "", err
}
if !manifest.MIMETypeIsMultiImage(mannyType) { // if not true
return "", fmt.Errorf("wrong manifest type for disk artifact: %s", mannyType)
}
mannyFestList, err := manifest.ListFromBlob(rawMannyFest, mannyType)
if err != nil {
return "", fmt.Errorf("failed to parse manifest list from blob: %q", err)
}
var (
artifactDigest digest.Digest
)
for _, d := range mannyFestList.Instances() {
bar, err := mannyFestList.Instance(d)
if err != nil {
return "", err
}
val, ok := bar.ReadOnly.Annotations["disktype"]
if !ok { // quick exit, no type match
continue
}
// wrong arch
if bar.ReadOnly.Platform.Architecture != opts.arch {
continue
}
// wrong os
if bar.ReadOnly.Platform.OS != opts.os {
continue
}
// wrong disktype
if val != opts.diskType {
continue
}
// ok, we have a match
artifactDigest = d
logrus.Debugf("found image in digest: %q", artifactDigest.String())
break
}
if artifactDigest == "" {
return "", fmt.Errorf("no valid disk artifact found")
}
v1RawMannyfest, _, err := imgSrc.GetManifest(ctx, &artifactDigest)
if err != nil {
return "", err
}
v1MannyFest := specV1.Manifest{}
if err := json.Unmarshal(v1RawMannyfest, &v1MannyFest); err != nil {
return "", err
}
if layerLen := len(v1MannyFest.Layers); layerLen > 1 {
return "", fmt.Errorf("podman-machine images should only have 1 layer: %d found", layerLen)
}
// podman-machine-images should have a original file name
// stored in the annotations under org.opencontainers.image.title
// i.e. fedora-coreos-39.20240128.2.2-qemu.x86_64.qcow2.xz
originalFileName, ok := v1MannyFest.Layers[0].Annotations["org.opencontainers.image.title"]
if !ok {
return "", fmt.Errorf("unable to determine original artifact name: missing required annotation 'org.opencontainers.image.title'")
}
logrus.Debugf("original artifact file name: %s", originalFileName)
return artifactDigest, err
}