diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index 612e3ae9a6..1903061daf 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -38,6 +38,7 @@ func stop(cmd *cobra.Command, args []string) error { err error vm machine.VM ) + vmName := defaultMachineName if len(args) > 0 && len(args[0]) > 0 { vmName = args[0] diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 5acab5c3f8..b65e8d581e 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -585,6 +585,7 @@ func (dl Download) AcquireVMImage(imagePath string) (*VMFile, FCOSStream, error) imageLocation *VMFile fcosStream FCOSStream ) + switch imagePath { // TODO these need to be re-typed as FCOSStreams case Testing.String(), Next.String(), Stable.String(), "": diff --git a/pkg/machine/default.go b/pkg/machine/default.go new file mode 100644 index 0000000000..6d0298d2e6 --- /dev/null +++ b/pkg/machine/default.go @@ -0,0 +1,154 @@ +package machine + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/image/v5/types" + "github.com/containers/podman/v4/pkg/machine/ocipull" + 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 + imageFormat ImageFormat + imageName string + machineImageDir string + machineVersion *OSVersion + vmName string +} + +func newVersioned(ctx context.Context, machineImageDir, vmName string) (*Versioned, error) { + imageCacheDir := filepath.Join(machineImageDir, "cache") + if err := os.MkdirAll(imageCacheDir, 0777); err != nil { + return nil, err + } + o := getVersion() + return &Versioned{ctx: ctx, cacheDir: imageCacheDir, machineImageDir: machineImageDir, machineVersion: o, vmName: vmName}, nil +} + +func (d *Versioned) LocalBlob() *types.BlobInfo { + return d.blob +} + +func (d *Versioned) DiskEndpoint() string { + return d.machineVersion.diskImage(d.imageFormat) +} + +func (d *Versioned) versionedOCICacheDir() string { + return filepath.Join(d.cacheDir, d.machineVersion.majorMinor()) +} + +func (d *Versioned) identifyImageNameFromOCIDir() (string, error) { + imageManifest, err := ocipull.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 ocipull.Pull(d.ctx, d.DiskEndpoint(), path, ocipull.PullOptions{}) +} + +func (d *Versioned) Pull() error { + var ( + err error + isUpdatable bool + localBlob *types.BlobInfo + remoteDescriptor *v1.Descriptor + ) + + remoteDiskImage := d.machineVersion.diskImage(Qcow) + 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 = ocipull.GetRemoteDescriptor(d.ctx, remoteDiskImage) + if err != nil { + return err + } + logrus.Debugf("working with local cache: %s", d.versionedOCICacheDir()) + localBlob, err = ocipull.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 := 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 = ocipull.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() (*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 *VMFile) (*VMFile, error) { + imageCompression := compressionFromFile(d.imageName) + strippedImageName := strings.TrimSuffix(d.imageName, fmt.Sprintf(".%s", imageCompression.String())) + finalName := finalFQImagePathName(d.vmName, strippedImageName) + if err := Decompress(compressedFile, finalName); err != nil { + return nil, err + } + return NewMachineFile(finalName, nil) +} + +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 +} diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go index b1da69bd92..ad26a42147 100644 --- a/pkg/machine/e2e/machine_test.go +++ b/pkg/machine/e2e/machine_test.go @@ -77,7 +77,11 @@ var _ = BeforeSuite(func() { Fail(fmt.Sprintf("unable to download machine image: %q", err)) } GinkgoWriter.Println("Download took: ", time.Since(now).String()) - if err := machine.Decompress(fqImageName+compressionExtension, fqImageName); err != nil { + diskImage, err := machine.NewMachineFile(fqImageName+compressionExtension, nil) + if err != nil { + Fail(fmt.Sprintf("unable to create vmfile %q: %v", fqImageName+compressionExtension, err)) + } + if err := machine.Decompress(diskImage, fqImageName); err != nil { Fail(fmt.Sprintf("unable to decompress image file: %q", err)) } } else { diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index 4512b6e74c..a7cd8eb1c4 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -78,6 +78,18 @@ func (imf ImageFormat) String() string { return "qcow2.xz" } +func (imf ImageFormat) string() string { + switch imf { + case Vhdx: + return "vhdx" + case Tar: + return "tar" + case Raw: + return "raw" + } + return "qcow2" +} + func (c ImageCompression) String() string { switch c { case Gz: diff --git a/pkg/machine/oci.go b/pkg/machine/oci.go new file mode 100644 index 0000000000..7d59c5325f --- /dev/null +++ b/pkg/machine/oci.go @@ -0,0 +1,136 @@ +package machine + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/image/v5/types" + + "github.com/containers/image/v5/pkg/compression" + "github.com/containers/storage/pkg/archive" + "github.com/sirupsen/logrus" + + "github.com/blang/semver/v4" + "github.com/containers/podman/v4/version" +) + +// quay.io/libpod/podman-machine-images:4.6 + +const ( + diskImages = "podman-machine-images" + registry = "quay.io" + repo = "libpod" +) + +type OSVersion struct { + *semver.Version +} + +type Disker interface { + Pull() error + Decompress(compressedFile *VMFile) (*VMFile, error) + DiskEndpoint() string + Unpack() (*VMFile, error) +} + +type OCIOpts struct { + Scheme *OCIKind + Dir *string +} + +type OCIKind string + +var ( + OCIDir OCIKind = "oci-dir" + OCIRegistry OCIKind = "docker" + OCIUnknown OCIKind = "unknown" +) + +func (o OCIKind) String() string { + switch o { + case OCIDir: + return string(OCIDir) + case OCIRegistry: + return string(OCIRegistry) + } + return string(OCIUnknown) +} + +func (o OCIKind) IsOCIDir() bool { + return o == OCIDir +} + +func StripOCIReference(input string) string { + return strings.TrimPrefix(input, "docker://") +} + +func getVersion() *OSVersion { + v := version.Version + + // OVERRIDES FOR DEV ONLY + v.Minor = 6 + v.Pre = nil + // OVERRIDES FOR DEV ONLY + + return &OSVersion{&v} +} + +func (o *OSVersion) majorMinor() string { + return fmt.Sprintf("%d.%d", o.Major, o.Minor) +} + +func (o *OSVersion) diskImage(diskFlavor ImageFormat) string { + return fmt.Sprintf("%s/%s/%s:%s-%s", registry, repo, diskImages, o.majorMinor(), diskFlavor.string()) +} + +func unpackOCIDir(ociTb, machineImageDir string) (*VMFile, error) { + imageFileName, err := findTarComponent(ociTb) + if err != nil { + return nil, err + } + + unpackedFileName := filepath.Join(machineImageDir, imageFileName) + + f, err := os.Open(ociTb) + if err != nil { + return nil, err + } + defer func() { + if err := f.Close(); err != nil { + logrus.Error(err) + } + }() + + uncompressedReader, _, err := compression.AutoDecompress(f) + if err != nil { + return nil, err + } + + defer func() { + if err := uncompressedReader.Close(); err != nil { + logrus.Error(err) + } + }() + + logrus.Debugf("untarring %q to %q", ociTb, machineImageDir) + if err := archive.Untar(uncompressedReader, machineImageDir, &archive.TarOptions{ + NoLchown: true, + }); err != nil { + return nil, err + } + + return NewMachineFile(unpackedFileName, nil) +} + +func localOCIDiskImageDir(blobDirPath string, localBlob *types.BlobInfo) string { + return filepath.Join(blobDirPath, "blobs", "sha256", localBlob.Digest.Hex()) +} + +func finalFQImagePathName(vmName, imageName string) string { + // imageName here is fully qualified. we need to break + // it apart and add the vmname + baseDir, filename := filepath.Split(imageName) + return filepath.Join(baseDir, fmt.Sprintf("%s-%s", vmName, filename)) +} diff --git a/pkg/machine/ocidir.go b/pkg/machine/ocidir.go new file mode 100644 index 0000000000..b513bf119d --- /dev/null +++ b/pkg/machine/ocidir.go @@ -0,0 +1,116 @@ +package machine + +import ( + "archive/tar" + "context" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/containers/image/v5/pkg/compression" + "github.com/containers/image/v5/types" + "github.com/containers/podman/v4/pkg/machine/ocipull" + "github.com/sirupsen/logrus" +) + +type LocalBlobDir struct { + blob *types.BlobInfo + blobDirPath string + ctx context.Context + imageName string + machineImageDir string + vmName string +} + +func NewOCIDir(ctx context.Context, inputDir, machineImageDir, vmName string) *LocalBlobDir { + strippedInputDir := strings.TrimPrefix(inputDir, fmt.Sprintf("%s:/", OCIDir.String())) + l := LocalBlobDir{ + blob: nil, + blobDirPath: strippedInputDir, + ctx: ctx, + imageName: "", + machineImageDir: machineImageDir, + vmName: vmName, + } + return &l +} + +func (l *LocalBlobDir) Pull() error { + localBlob, err := ocipull.GetLocalBlob(l.ctx, l.DiskEndpoint()) + if err != nil { + return err + } + l.blob = localBlob + return nil +} + +func (l *LocalBlobDir) Decompress(compressedFile *VMFile) (*VMFile, error) { + finalName := finalFQImagePathName(l.vmName, l.imageName) + if err := Decompress(compressedFile, finalName); err != nil { + return nil, err + } + return NewMachineFile(finalName, nil) +} + +func (l *LocalBlobDir) Unpack() (*VMFile, error) { + tbPath := localOCIDiskImageDir(l.blobDirPath, l.blob) + unPackedFile, err := unpackOCIDir(tbPath, l.machineImageDir) + if err != nil { + return nil, err + } + l.imageName = unPackedFile.GetPath() + return unPackedFile, err +} + +func (l *LocalBlobDir) DiskEndpoint() string { + return l.blobDirPath +} + +func (l *LocalBlobDir) LocalBlob() *types.BlobInfo { + return l.blob +} + +// findTarComponent returns a header and a reader matching componentPath within inputFile, +// or (nil, nil, nil) if not found. +func findTarComponent(pathToTar string) (string, error) { + f, err := os.Open(pathToTar) + if err != nil { + return "", err + } + defer func() { + if err := f.Close(); err != nil { + logrus.Error(err) + } + }() + uncompressedReader, _, err := compression.AutoDecompress(f) + if err != nil { + return "", err + } + defer func() { + if err := uncompressedReader.Close(); err != nil { + logrus.Error(err) + } + }() + var ( + filename string + headerCount uint + ) + t := tar.NewReader(uncompressedReader) + for { + h, err := t.Next() + if err == io.EOF { + break + } + if err != nil { + return "", err + } + filename = h.Name + headerCount++ + } + if headerCount != 1 { + return "", errors.New("invalid oci machine image") + } + return filename, nil +} diff --git a/pkg/machine/ocipull/pull.go b/pkg/machine/ocipull/pull.go new file mode 100644 index 0000000000..1f0b97a562 --- /dev/null +++ b/pkg/machine/ocipull/pull.go @@ -0,0 +1,110 @@ +package ocipull + +import ( + "context" + "fmt" + "os" + + "github.com/containers/buildah/pkg/parse" + "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/pkg/shortnames" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + specV1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// PullOptions includes data to alter certain knobs when pulling a source +// image. +type PullOptions struct { + // Require HTTPS and verify certificates when accessing the registry. + TLSVerify bool + // [username[:password] to use when connecting to the registry. + Credentials string + // Quiet the progress bars when pushing. + Quiet bool +} + +// 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) + } + + srcRef, err := stringToImageReference(imageInput) + if err != nil { + return err + } + destRef, err := layout.ParseReference(sourcePath) + if err != nil { + return err + } + + sysCtx := &types.SystemContext{ + DockerInsecureSkipTLSVerify: types.NewOptionalBool(!options.TLSVerify), + } + if options.Credentials != "" { + authConf, err := parse.AuthConfig(options.Credentials) + if err != nil { + return err + } + sysCtx.DockerAuthConfig = authConf + } + + if err := validateSourceImageReference(ctx, srcRef, sysCtx); err != nil { + return err + } + + policy, err := signature.DefaultPolicy(sysCtx) + if err != nil { + return fmt.Errorf("obtaining default signature policy: %w", err) + } + policyContext, err := signature.NewPolicyContext(policy) + if err != nil { + return fmt.Errorf("creating new signature policy context: %w", err) + } + + copyOpts := copy.Options{ + SourceCtx: sysCtx, + } + if !options.Quiet { + copyOpts.ReportWriter = os.Stderr + } + if _, err := copy.Image(ctx, policyContext, destRef, srcRef, ©Opts); err != nil { + return fmt.Errorf("pulling source image: %w", err) + } + + return nil +} + +func stringToImageReference(imageInput string) (types.ImageReference, error) { + 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) + } + + ref, err := alltransports.ParseImageName("docker://" + imageInput) + if err != nil { + return nil, fmt.Errorf("parsing image name: %w", err) + } + + return ref, nil +} + +func validateSourceImageReference(ctx context.Context, ref types.ImageReference, sysCtx *types.SystemContext) error { + src, err := ref.NewImageSource(ctx, sysCtx) + if err != nil { + return fmt.Errorf("creating image source from reference: %w", err) + } + defer src.Close() + + ociManifest, _, _, err := readManifestFromImageSource(ctx, src) + if err != nil { + return err + } + 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 new file mode 100644 index 0000000000..9be0dd9f5f --- /dev/null +++ b/pkg/machine/ocipull/source.go @@ -0,0 +1,120 @@ +package ocipull + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/containers/image/v5/docker" + "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" +) + +// 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) + } + + manifest := specV1.Manifest{} + if err := json.Unmarshal(rawData, &manifest); 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) +} + +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) { + 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 GetRemoteDescriptor(ctx context.Context, dest string) (*specV1.Descriptor, error) { + remoteManifest, err := GetRemoteManifest(ctx, dest) + if err != nil { + return nil, 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 +} diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go index 31ccd8cd6d..153cbe3db1 100644 --- a/pkg/machine/pull.go +++ b/pkg/machine/pull.go @@ -6,6 +6,7 @@ package machine import ( "archive/zip" "bufio" + "context" "errors" "fmt" "io" @@ -133,7 +134,11 @@ func DownloadImage(d DistributionDownload) error { } }() } - return Decompress(d.Get().LocalPath, d.Get().LocalUncompressedFile) + localPath, err := NewMachineFile(d.Get().LocalPath, nil) + if err != nil { + return err + } + return Decompress(localPath, d.Get().LocalUncompressedFile) } func progressBar(prefix string, size int64, onComplete string) (*mpb.Progress, *mpb.Bar) { @@ -205,17 +210,17 @@ func DownloadVMImage(downloadURL *url2.URL, imageName string, localImagePath str return nil } -func Decompress(localPath, uncompressedPath string) error { +func Decompress(localPath *VMFile, uncompressedPath string) error { var isZip bool uncompressedFileWriter, err := os.OpenFile(uncompressedPath, os.O_CREATE|os.O_RDWR, 0600) if err != nil { return err } - sourceFile, err := os.ReadFile(localPath) + sourceFile, err := localPath.Read() if err != nil { return err } - if strings.HasSuffix(localPath, ".zip") { + if strings.HasSuffix(localPath.GetPath(), ".zip") { isZip = true } prefix := "Copying uncompressed file" @@ -225,12 +230,12 @@ func Decompress(localPath, uncompressedPath string) error { } prefix += ": " + filepath.Base(uncompressedPath) if compressionType == archive.Xz { - return decompressXZ(prefix, localPath, uncompressedFileWriter) + return decompressXZ(prefix, localPath.GetPath(), uncompressedFileWriter) } if isZip && runtime.GOOS == "windows" { - return decompressZip(prefix, localPath, uncompressedFileWriter) + return decompressZip(prefix, localPath.GetPath(), uncompressedFileWriter) } - return decompressEverythingElse(prefix, localPath, uncompressedFileWriter) + return decompressEverythingElse(prefix, localPath.GetPath(), uncompressedFileWriter) } // Will error out if file without .Xz already exists @@ -405,3 +410,75 @@ func (dl Download) AcquireAlternateImage(inputPath string) (*VMFile, error) { return imagePath, nil } + +func isOci(input string) (bool, *OCIKind, error) { + inputURL, err := url2.Parse(input) + if err != nil { + return false, nil, err + } + switch inputURL.Scheme { + case OCIDir.String(): + return true, &OCIDir, nil + case OCIRegistry.String(): + return true, &OCIRegistry, nil + } + return false, nil, nil +} + +func Pull(input, machineName string, vp VirtProvider) (*VMFile, FCOSStream, error) { + var ( + disk Disker + ) + + ociBased, ociScheme, err := isOci(input) + if err != nil { + return nil, 0, err + } + if !ociBased { + // Business as usual + dl, err := vp.NewDownload(machineName) + if err != nil { + return nil, 0, err + } + return dl.AcquireVMImage(input) + } + oopts := OCIOpts{ + Scheme: ociScheme, + } + dataDir, err := GetDataDir(vp.VMType()) + if err != nil { + return nil, 0, err + } + if ociScheme.IsOCIDir() { + strippedOCIDir := StripOCIReference(input) + oopts.Dir = &strippedOCIDir + disk = NewOCIDir(context.Background(), input, dataDir, machineName) + } else { + // a use of a containers image type here might be + // tighter + strippedInput := strings.TrimPrefix(input, "docker://") + // this is the next piece of work + if len(strippedInput) > 0 { + return nil, 0, errors.New("image names are not supported yet") + } + disk, err = newVersioned(context.Background(), dataDir, machineName) + if err != nil { + return nil, 0, err + } + } + if err := disk.Pull(); err != nil { + return nil, 0, err + } + unpacked, err := disk.Unpack() + if err != nil { + return nil, 0, 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) + } + }() + imagePath, err := disk.Decompress(unpacked) + return imagePath, UnknownStream, err +} diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 0c0bbbb044..1c811af491 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -260,15 +260,12 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { v.IdentityPath = util.GetIdentityPath(v.Name) v.Rootful = opts.Rootful - dl, err := VirtualizationProvider().NewDownload(v.Name) + imagePath, strm, err := machine.Pull(opts.ImagePath, opts.Name, VirtualizationProvider()) if err != nil { return false, err } - imagePath, strm, err := dl.AcquireVMImage(opts.ImagePath) - if err != nil { - return false, err - } + // By this time, image should be had and uncompressed callbackFuncs.Add(imagePath.Delete) // Assign values about the download