package ocipull import ( "context" "fmt" "os" "path/filepath" "github.com/containers/image/v5/types" "github.com/containers/podman/v5/pkg/machine/compression" "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/utils" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) type Versioned struct { blob *types.BlobInfo blobDirPath string cacheDir string ctx context.Context imageName string machineImageDir string machineVersion *OSVersion vmName string vmType string finalPath *define.VMFile } func NewVersioned(ctx context.Context, machineImageDir *define.VMFile, vmName string, vmType string, finalPath *define.VMFile) (*Versioned, error) { imageCacheDir := filepath.Join(machineImageDir.GetPath(), "cache") if err := os.MkdirAll(imageCacheDir, 0777); err != nil { return nil, err } o := getVersion() return &Versioned{ctx: ctx, cacheDir: imageCacheDir, machineImageDir: machineImageDir.GetPath(), machineVersion: o, vmName: vmName, vmType: vmType, finalPath: finalPath}, nil } func (d *Versioned) LocalBlob() *types.BlobInfo { return d.blob } func (d *Versioned) DiskEndpoint() string { return d.machineVersion.diskImage(d.vmType) } func (d *Versioned) versionedOCICacheDir() string { return filepath.Join(d.cacheDir, d.machineVersion.majorMinor()) } func (d *Versioned) identifyImageNameFromOCIDir() (string, error) { imageManifest, err := ReadImageManifestFromOCIPath(d.ctx, d.versionedOCICacheDir()) if err != nil { return "", err } if len(imageManifest.Layers) > 1 { return "", fmt.Errorf("podman machine images can have only one layer: %d found", len(imageManifest.Layers)) } path := filepath.Join(d.versionedOCICacheDir(), "blobs", "sha256", imageManifest.Layers[0].Digest.Hex()) return findTarComponent(path) } func (d *Versioned) pull(path string) error { fmt.Printf("Pulling %s\n", d.DiskEndpoint()) logrus.Debugf("pulling %s to %s", d.DiskEndpoint(), path) return Pull(d.ctx, d.DiskEndpoint(), path, PullOptions{}) } func (d *Versioned) Pull() error { var ( err error isUpdatable bool localBlob *types.BlobInfo remoteDescriptor *v1.Descriptor ) remoteDiskImage := d.machineVersion.diskImage(d.vmType) logrus.Debugf("podman disk image name: %s", remoteDiskImage) // is there a valid oci dir in our cache hasCache := d.localOCIDirExists() if hasCache { logrus.Debug("checking remote registry") remoteDescriptor, err = GetRemoteDescriptor(d.ctx, remoteDiskImage) if err != nil { return err } logrus.Debugf("working with local cache: %s", d.versionedOCICacheDir()) localBlob, err = GetLocalBlob(d.ctx, d.versionedOCICacheDir()) if err != nil { return err } // determine if the local is same as remote if remoteDescriptor.Digest.Hex() != localBlob.Digest.Hex() { logrus.Debugf("new image is available: %s", remoteDescriptor.Digest.Hex()) isUpdatable = true } } if !hasCache || isUpdatable { if hasCache { if err := utils.GuardedRemoveAll(d.versionedOCICacheDir()); err != nil { return err } } if err := d.pull(d.versionedOCICacheDir()); err != nil { return err } } imageName, err := d.identifyImageNameFromOCIDir() if err != nil { return err } logrus.Debugf("image name: %s", imageName) d.imageName = imageName if localBlob == nil { localBlob, err = GetLocalBlob(d.ctx, d.versionedOCICacheDir()) if err != nil { return err } } d.blob = localBlob d.blobDirPath = d.versionedOCICacheDir() logrus.Debugf("local oci disk image blob: %s", d.localOCIDiskImageDir(localBlob)) return nil } func (d *Versioned) Unpack() (*define.VMFile, error) { tbPath := localOCIDiskImageDir(d.blobDirPath, d.blob) unpackedFile, err := unpackOCIDir(tbPath, d.machineImageDir) if err != nil { return nil, err } d.imageName = unpackedFile.GetPath() return unpackedFile, nil } func (d *Versioned) Decompress(compressedFile *define.VMFile) error { return compression.Decompress(compressedFile, d.finalPath.GetPath()) } func (d *Versioned) localOCIDiskImageDir(localBlob *types.BlobInfo) string { return filepath.Join(d.versionedOCICacheDir(), "blobs", "sha256", localBlob.Digest.Hex()) } func (d *Versioned) localOCIDirExists() bool { _, indexErr := os.Stat(filepath.Join(d.versionedOCICacheDir(), "index.json")) return indexErr == nil } func (d *Versioned) Get() error { if err := d.Pull(); err != nil { return err } unpacked, err := d.Unpack() if err != nil { return err } defer func() { logrus.Debugf("cleaning up %q", unpacked.GetPath()) if err := unpacked.Delete(); err != nil { logrus.Errorf("unable to delete local compressed file %q:%v", unpacked.GetPath(), err) } }() return d.Decompress(unpacked) }