diff --git a/pkg/machine/config.go b/pkg/machine/config.go index d59ede3744..dc6b2d1a70 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -181,15 +181,21 @@ func GetMachineDirs(vmType define.VMType) (*define.MachineDirs, error) { return nil, err } + imageCacheDir, err := dataDirFile.AppendToNewVMFile("cache", nil) + if err != nil { + return nil, err + } + rtDirFile, err := define.NewMachineFile(rtDir, nil) if err != nil { return nil, err } dirs := define.MachineDirs{ - ConfigDir: configDirFile, - DataDir: dataDirFile, - RuntimeDir: rtDirFile, + ConfigDir: configDirFile, + DataDir: dataDirFile, + ImageCacheDir: imageCacheDir, + RuntimeDir: rtDirFile, } // make sure all machine dirs are present @@ -199,7 +205,10 @@ func GetMachineDirs(vmType define.VMType) (*define.MachineDirs, error) { if err := os.MkdirAll(configDir, 0755); err != nil { return nil, err } - err = os.MkdirAll(dataDir, 0755) + + // Because this is a mkdirall, we make the image cache dir + // which is a subdir of datadir (so the datadir is made anyway) + err = os.MkdirAll(imageCacheDir.GetPath(), 0755) return &dirs, err } diff --git a/pkg/machine/define/config.go b/pkg/machine/define/config.go index b03da89bdf..245080122c 100644 --- a/pkg/machine/define/config.go +++ b/pkg/machine/define/config.go @@ -17,7 +17,8 @@ type CreateVMOpts struct { } type MachineDirs struct { - ConfigDir *VMFile - DataDir *VMFile - RuntimeDir *VMFile + ConfigDir *VMFile + DataDir *VMFile + ImageCacheDir *VMFile + RuntimeDir *VMFile } diff --git a/pkg/machine/define/vmtype.go b/pkg/machine/define/vmtype.go index fccb709fac..6ae701bacb 100644 --- a/pkg/machine/define/vmtype.go +++ b/pkg/machine/define/vmtype.go @@ -36,6 +36,18 @@ func (v VMType) String() string { return qemu } +func (v VMType) ImageFormat() ImageFormat { + switch v { + case WSLVirt: + return Tar + case AppleHvVirt: + return Raw + case HyperVVirt: + return Vhdx + } + return Qcow +} + func ParseVMType(input string, emptyFallback VMType) (VMType, error) { switch strings.TrimSpace(strings.ToLower(input)) { case qemu: diff --git a/pkg/machine/define/vmtype_test.go b/pkg/machine/define/vmtype_test.go index 77dfd904e9..22c816e65e 100644 --- a/pkg/machine/define/vmtype_test.go +++ b/pkg/machine/define/vmtype_test.go @@ -56,3 +56,39 @@ func TestParseVMType(t *testing.T) { }) } } + +func TestVMType_ImageFormat(t *testing.T) { + tests := []struct { + name string + v VMType + want ImageFormat + }{ + { + name: "wsl", + v: WSLVirt, + want: Tar, + }, + { + name: "applehv", + v: AppleHvVirt, + want: Raw, + }, + { + name: "qemu", + v: QemuVirt, + want: Qcow, + }, + { + name: "hyperv", + v: HyperVVirt, + want: Vhdx, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.v.ImageFormat(); got != tt.want { + t.Errorf("ImageFormat() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/machine/ocipull/oci.go b/pkg/machine/ocipull/oci.go index d8e96f6583..7b2c23c004 100644 --- a/pkg/machine/ocipull/oci.go +++ b/pkg/machine/ocipull/oci.go @@ -15,14 +15,6 @@ import ( "github.com/sirupsen/logrus" ) -// quay.io/libpod/podman-machine-images:4.6 - -const ( - diskImages = "podman-machine-images" - registry = "quay.io" - repo = "libpod" -) - type OSVersion struct { *semver.Version } @@ -71,10 +63,6 @@ func (o *OSVersion) majorMinor() string { return fmt.Sprintf("%d.%d", o.Major, o.Minor) } -func (o *OSVersion) diskImage(vmType string) string { - return fmt.Sprintf("%s/%s/%s:%s-%s", registry, repo, diskImages, o.majorMinor(), vmType) -} - func unpackOCIDir(ociTb, machineImageDir string) (*define.VMFile, error) { imageFileName, err := findTarComponent(ociTb) if err != nil { diff --git a/pkg/machine/ocipull/ociartifact.go b/pkg/machine/ocipull/ociartifact.go new file mode 100644 index 0000000000..a3fff9df90 --- /dev/null +++ b/pkg/machine/ocipull/ociartifact.go @@ -0,0 +1,267 @@ +package ocipull + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/transports/alltransports" + "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" + crc "github.com/crc-org/crc/v2/pkg/os" + "github.com/opencontainers/go-digest" + specV1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" +) + +const ( + // TODO This is temporary until we decide on a proper image name + artifactRegistry = "quay.io" + artifactRepo = "baude" + artifactImageName = "podman-machine-images-art" + artifactOriginalName = "org.opencontainers.image.title" +) + +type OCIArtifactDisk struct { + cachedCompressedDiskPath *define.VMFile + name string + ctx context.Context + dirs *define.MachineDirs + diskArtifactOpts *DiskArtifactOpts + finalPath string + imageEndpoint string + machineVersion *OSVersion + diskArtifactFileName string + pullOptions *PullOptions + vmType define.VMType +} + +type DiskArtifactOpts struct { + arch string + diskType string + os string +} + +/* + + This interface is for automatically pulling a disk artifact(qcow2, raw, vhdx file) from a pre-determined + image location. The logic is tied to vmtypes (applehv, qemu, hyperv) and their understanding of the type of + disk they require. The process can be generally described as: + + * Determine the flavor of artifact we are looking for (arch, compression, type) + * Grab the manifest list for the target + * Walk the artifacts to find a match based on flavor + * Check the hash of the artifact against the hash of our cached image + * If the cached image does not exist or match, pull the latest into an OCI directory + * Read the OCI blob's manifest to determine which blob is the artifact disk + * Rename/move the blob in the OCI directory to the image cache dir and append the type and compression + i.e. 91d1e51ddfac9d4afb1f96df878089cfdb9ab9be5886f8bccac0f0557ed28974.qcow2.xz + * Discard the OCI directory + * Decompress the cached image to the image dir in the form of -. + +*/ + +func NewOCIArtifactPull(ctx context.Context, dirs *define.MachineDirs, vmName string, vmType define.VMType, finalPath *define.VMFile) (*OCIArtifactDisk, error) { + var ( + arch string + ) + + artifactVersion := getVersion() + switch runtime.GOARCH { + case "amd64": + arch = "x86_64" + case "arm64": + arch = "aarch64" + default: + return nil, fmt.Errorf("unsupported machine arch: %s", runtime.GOARCH) + } + + diskOpts := DiskArtifactOpts{ + arch: arch, + diskType: vmType.String(), + os: runtime.GOOS, + } + ociDisk := OCIArtifactDisk{ + ctx: ctx, + dirs: dirs, + diskArtifactOpts: &diskOpts, + finalPath: finalPath.GetPath(), + imageEndpoint: fmt.Sprintf("docker://%s/%s/%s:%s", artifactRegistry, artifactRepo, artifactImageName, artifactVersion.majorMinor()), + machineVersion: artifactVersion, + name: vmName, + pullOptions: &PullOptions{}, + vmType: vmType, + } + return &ociDisk, nil +} + +func (o *OCIArtifactDisk) Get() error { + destRef, artifactDigest, err := o.getDestArtifact() + if err != nil { + return err + } + + // Note: the artifactDigest here is the hash of the most recent disk image available + cachedImagePath, err := o.dirs.ImageCacheDir.AppendToNewVMFile(fmt.Sprintf("%s.%s", artifactDigest.Encoded(), o.vmType.ImageFormat().KindWithCompression()), nil) + if err != nil { + return err + } + // check if we have the latest and greatest disk image + if _, err = os.Stat(cachedImagePath.GetPath()); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("unable to access cached image path %q: %q", cachedImagePath.GetPath(), err) + } + // pull the image down to our local filesystem + if err := o.pull(destRef, artifactDigest); err != nil { + return err + } + // grab the artifact disk out of the cache and lay + // it into our local cache in the format of + // hash + disktype + compression + // + // in cache it will be used until it is "outdated" + // + // i.e. 91d1e51...d28974.qcow2.xz + if err := o.unpack(artifactDigest); err != nil { + return err + } + } else { + logrus.Debugf("cached image exists and is latest: %s", cachedImagePath.GetPath()) + o.cachedCompressedDiskPath = cachedImagePath + } + return o.decompress() +} + +func (o *OCIArtifactDisk) getDestArtifact() (types.ImageReference, digest.Digest, error) { + imgRef, err := alltransports.ParseImageName(o.imageEndpoint) + if err != nil { + return nil, "", err + } + sysCtx := &types.SystemContext{ + DockerInsecureSkipTLSVerify: types.NewOptionalBool(!o.pullOptions.TLSVerify), + } + imgSrc, err := imgRef.NewImageSource(o.ctx, sysCtx) + if err != nil { + return nil, "", err + } + + defer func() { + if err := imgSrc.Close(); err != nil { + logrus.Warn(err) + } + }() + + diskArtifactDigest, err := GetDiskArtifactReference(o.ctx, imgSrc, o.diskArtifactOpts) + if err != nil { + return nil, "", err + } + // create a ref now and return + named := imgRef.DockerReference() + digestedRef, err := reference.WithDigest(reference.TrimNamed(named), diskArtifactDigest) + if err != nil { + return nil, "", err + } + + // Get and "store" the original filename the disk artifact had + originalFileName, err := getOriginalFileName(o.ctx, imgSrc, diskArtifactDigest) + if err != nil { + return nil, "", err + } + o.diskArtifactFileName = originalFileName + + newRef, err := docker.NewReference(digestedRef) + if err != nil { + return nil, "", err + } + return newRef, diskArtifactDigest, err +} + +func (o *OCIArtifactDisk) pull(destRef types.ImageReference, artifactDigest digest.Digest) error { + destFileName := artifactDigest.Encoded() + destFile, err := o.dirs.ImageCacheDir.AppendToNewVMFile(destFileName, nil) + if err != nil { + return err + } + return Pull(o.ctx, destRef, destFile, o.pullOptions) +} + +func (o *OCIArtifactDisk) unpack(diskArtifactHash digest.Digest) error { + finalSuffix := extractKindAndCompression(o.diskArtifactFileName) + blobDir, err := o.dirs.ImageCacheDir.AppendToNewVMFile(diskArtifactHash.Encoded(), nil) + if err != nil { + return err + } + cachedCompressedPath, err := o.dirs.ImageCacheDir.AppendToNewVMFile(diskArtifactHash.Encoded()+finalSuffix, nil) + if err != nil { + return err + } + + o.cachedCompressedDiskPath = cachedCompressedPath + + blobInfo, err := GetLocalBlob(o.ctx, blobDir.GetPath()) + if err != nil { + return fmt.Errorf("unable to get local manifest for %s: %q", blobDir.GetPath(), err) + } + + diskBlobPath := filepath.Join(blobDir.GetPath(), "blobs", "sha256", blobInfo.Digest.Encoded()) + + // Rename and move the hashed blob file to the cache dir. + // If the rename fails, we do a sparsecopy instead + if err := os.Rename(diskBlobPath, cachedCompressedPath.GetPath()); err != nil { + logrus.Errorf("renaming compressed image %q failed: %q", cachedCompressedPath.GetPath(), err) + logrus.Error("trying again using copy") + if err := crc.CopyFileSparse(diskBlobPath, cachedCompressedPath.GetPath()); err != nil { + return err + } + } + + // Clean up the oci dir which is no longer needed + return utils.GuardedRemoveAll(blobDir.GetPath()) +} + +func (o *OCIArtifactDisk) decompress() error { + return compression.Decompress(o.cachedCompressedDiskPath, o.finalPath) +} + +func getOriginalFileName(ctx context.Context, imgSrc types.ImageSource, artifactDigest digest.Digest) (string, error) { + 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 an 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[artifactOriginalName] + 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 originalFileName, nil +} + +// extractKindAndCompression extracts the vmimage type and the compression type +// this is used for when we rename the blob from its hash to something real +// i.e. fedora-coreos-39.20240128.2.2-qemu.x86_64.qcow2.xz would return qcow2.xz +func extractKindAndCompression(name string) string { + compressAlgo := filepath.Ext(name) + compressStrippedName := strings.TrimSuffix(name, compressAlgo) + kind := filepath.Ext(compressStrippedName) + return kind + compressAlgo +} diff --git a/pkg/machine/ocipull/ociartifact_test.go b/pkg/machine/ocipull/ociartifact_test.go new file mode 100644 index 0000000000..6608cb534f --- /dev/null +++ b/pkg/machine/ocipull/ociartifact_test.go @@ -0,0 +1,57 @@ +package ocipull + +import "testing" + +func Test_extractKindAndCompression(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "qcow2", + args: args{name: "foo.qcow2.xz"}, + want: ".qcow2.xz", + }, + { + name: "vhdx", + args: args{name: "foo.vhdx.zip"}, + want: ".vhdx.zip", + }, + { + name: "applehv", + args: args{name: "foo.raw.gz"}, + want: ".raw.gz", + }, + { + name: "lots of extensions with type and compression", + args: args{name: "foo.bar.homer.simpson.qcow2.xz"}, + want: ".qcow2.xz", + }, + { + name: "lots of extensions", + args: args{name: "foo.bar.homer.simpson"}, + want: ".homer.simpson", + }, + { + name: "no extensions", + args: args{name: "foobar"}, + want: "", + }, + { + name: "one extension", + args: args{name: "foobar.zip"}, + want: ".zip", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := extractKindAndCompression(tt.args.name); got != tt.want { + t.Errorf("extractKindAndCompression() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/machine/ocipull/pull.go b/pkg/machine/ocipull/pull.go index 1f0b97a562..2a14a12899 100644 --- a/pkg/machine/ocipull/pull.go +++ b/pkg/machine/ocipull/pull.go @@ -12,6 +12,7 @@ import ( "github.com/containers/image/v5/signature" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" + "github.com/containers/podman/v5/pkg/machine/define" specV1 "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -27,16 +28,12 @@ type PullOptions struct { } // Pull `imageInput` from a container registry to `sourcePath`. -func Pull(ctx context.Context, imageInput string, sourcePath string, options PullOptions) error { - if _, err := os.Stat(sourcePath); err == nil { - return fmt.Errorf("%q already exists", sourcePath) +func Pull(ctx context.Context, imageInput types.ImageReference, localDestPath *define.VMFile, options *PullOptions) error { + if _, err := os.Stat(localDestPath.GetPath()); err == nil { + return fmt.Errorf("%q already exists", localDestPath.GetPath()) } - srcRef, err := stringToImageReference(imageInput) - if err != nil { - return err - } - destRef, err := layout.ParseReference(sourcePath) + destRef, err := layout.ParseReference(localDestPath.GetPath()) if err != nil { return err } @@ -52,7 +49,7 @@ func Pull(ctx context.Context, imageInput string, sourcePath string, options Pul sysCtx.DockerAuthConfig = authConf } - if err := validateSourceImageReference(ctx, srcRef, sysCtx); err != nil { + if err := validateSourceImageReference(ctx, imageInput, sysCtx); err != nil { return err } @@ -71,14 +68,14 @@ func Pull(ctx context.Context, imageInput string, sourcePath string, options Pul if !options.Quiet { copyOpts.ReportWriter = os.Stderr } - if _, err := copy.Image(ctx, policyContext, destRef, srcRef, ©Opts); err != nil { + if _, err := copy.Image(ctx, policyContext, destRef, imageInput, ©Opts); err != nil { return fmt.Errorf("pulling source image: %w", err) } return nil } -func stringToImageReference(imageInput string) (types.ImageReference, error) { +func stringToImageReference(imageInput string) (types.ImageReference, error) { //nolint:unused if shortnames.IsShortName(imageInput) { return nil, fmt.Errorf("pulling source images by short name (%q) is not supported, please use a fully-qualified name", imageInput) } @@ -105,6 +102,5 @@ func validateSourceImageReference(ctx context.Context, ref types.ImageReference, if ociManifest.Config.MediaType != specV1.MediaTypeImageConfig { return fmt.Errorf("invalid media type of image config %q (expected: %q)", ociManifest.Config.MediaType, specV1.MediaTypeImageConfig) } - return nil } diff --git a/pkg/machine/ocipull/source.go b/pkg/machine/ocipull/source.go index 9be0dd9f5f..abb181614a 100644 --- a/pkg/machine/ocipull/source.go +++ b/pkg/machine/ocipull/source.go @@ -8,10 +8,12 @@ import ( "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 @@ -25,31 +27,13 @@ func readManifestFromImageSource(ctx context.Context, src types.ImageSource) (*s return nil, nil, -1, fmt.Errorf("image %q is of type %q (expected: %q)", strings.TrimPrefix(src.Reference().StringWithinTransport(), "//"), mimeType, specV1.MediaTypeImageManifest) } - manifest := specV1.Manifest{} - if err := json.Unmarshal(rawData, &manifest); err != nil { + 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 &manifest, &manifestDigest, int64(len(rawData)), nil -} - -// readManifestFromOCIPath returns the manifest of the specified source image -// at `sourcePath` along with its digest. The digest can later on be used to -// locate the manifest on the file system. -func readManifestFromOCIPath(ctx context.Context, sourcePath string) (*specV1.Manifest, *digest.Digest, int64, error) { - ociRef, err := layout.ParseReference(sourcePath) - if err != nil { - return nil, nil, -1, err - } - - ociSource, err := ociRef.NewImageSource(ctx, &types.SystemContext{}) - if err != nil { - return nil, nil, -1, err - } - defer ociSource.Close() - - return readManifestFromImageSource(ctx, ociSource) + return &mannyFest, &manifestDigest, int64(len(rawData)), nil } func GetLocalBlob(ctx context.Context, path string) (*types.BlobInfo, error) { @@ -82,7 +66,7 @@ func GetLocalBlob(ctx context.Context, path string) (*types.BlobInfo, error) { return &blobs[0], nil } -func GetRemoteManifest(ctx context.Context, dest string) (*specV1.Manifest, error) { +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 @@ -103,18 +87,73 @@ func GetRemoteManifest(ctx context.Context, dest string) (*specV1.Manifest, erro return &remoteManifest, err } -func GetRemoteDescriptor(ctx context.Context, dest string) (*specV1.Descriptor, error) { - remoteManifest, err := GetRemoteManifest(ctx, dest) +func GetDiskArtifactReference(ctx context.Context, imgSrc types.ImageSource, opts *DiskArtifactOpts) (digest.Digest, error) { + rawMannyFest, mannyType, err := imgSrc.GetManifest(ctx, nil) if err != nil { - return nil, err + return "", err } - if len(remoteManifest.Layers) != 1 { - return nil, errors.New("invalid remote disk image") - } - return &remoteManifest.Layers[0], nil -} -func ReadImageManifestFromOCIPath(ctx context.Context, ociImagePath string) (*specV1.Manifest, error) { - imageManifest, _, _, err := readManifestFromOCIPath(ctx, ociImagePath) - return imageManifest, 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 } diff --git a/pkg/machine/ocipull/versioned.go b/pkg/machine/ocipull/versioned.go deleted file mode 100644 index b062f1fcbb..0000000000 --- a/pkg/machine/ocipull/versioned.go +++ /dev/null @@ -1,169 +0,0 @@ -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) -} diff --git a/pkg/machine/shim/diskpull/diskpull.go b/pkg/machine/shim/diskpull/diskpull.go index 302c40d155..ce768ad593 100644 --- a/pkg/machine/shim/diskpull/diskpull.go +++ b/pkg/machine/shim/diskpull/diskpull.go @@ -16,7 +16,7 @@ func GetDisk(userInputPath string, dirs *define.MachineDirs, imagePath *define.V ) if userInputPath == "" { - mydisk, err = ocipull.NewVersioned(context.Background(), dirs.DataDir, name, vmType.String(), imagePath) + mydisk, err = ocipull.NewOCIArtifactPull(context.Background(), dirs, name, vmType, imagePath) } else { if strings.HasPrefix(userInputPath, "http") { // TODO probably should use tempdir instead of datadir diff --git a/pkg/machine/shim/host.go b/pkg/machine/shim/host.go index 44a51c0247..7c4d470416 100644 --- a/pkg/machine/shim/host.go +++ b/pkg/machine/shim/host.go @@ -135,9 +135,7 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M // "/path // "docker://quay.io/something/someManifest - // TODO Ideally this changes into some way better ... - err = mp.GetDisk(opts.ImagePath, dirs, mc) - if err != nil { + if err := mp.GetDisk(opts.ImagePath, dirs, mc); err != nil { return nil, err }