mirror of
https://github.com/containers/podman.git
synced 2025-06-22 18:08:11 +08:00
Merge pull request #20538 from baude/ocipull
Consume OCI images for machine image
This commit is contained in:
@ -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]
|
||||
|
@ -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(), "":
|
||||
|
154
pkg/machine/default.go
Normal file
154
pkg/machine/default.go
Normal file
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
136
pkg/machine/oci.go
Normal file
136
pkg/machine/oci.go
Normal file
@ -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))
|
||||
}
|
116
pkg/machine/ocidir.go
Normal file
116
pkg/machine/ocidir.go
Normal file
@ -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
|
||||
}
|
110
pkg/machine/ocipull/pull.go
Normal file
110
pkg/machine/ocipull/pull.go
Normal file
@ -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
|
||||
}
|
120
pkg/machine/ocipull/source.go
Normal file
120
pkg/machine/ocipull/source.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user