mirror of
https://github.com/containers/podman.git
synced 2026-03-27 19:13:49 -04:00
The following PR is the leading PR for refactoring podman machine with the following goals: * less duplication/more re-use * common configuration file between providers * more consistentency in how machines are handled by providers The goal of this PR is the rough refactor. There are still rough spots for sure, specifically around the podman socket and pipe. This implemention is only for Linux. All other providers are still present but will not compile or work. This is why tests for them have been temporarily suspended. The ready socket code is another area that needs to be smoothed over. Right now, the ready socket code is still in QEMU. Preferably it would be moved to a generic spot where all three approaches to readiness socket use can be defined. It should also be noted: * all machine related tests pass. * make validate for Linux passes * Apple QEMU was largely removed * More code pruning is possible; will become clearer when other providers are complete. the dir pkg/machine/p5 is not permanent. i had to seperate this from machine initially due to circular import problems. i think when all providers are done (or nearly done), it can be placed and named properly. Signed-off-by: Brent Baude <bbaude@redhat.com>
292 lines
7.5 KiB
Go
292 lines
7.5 KiB
Go
//go:build amd64 || arm64
|
|
|
|
package machine
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
url2 "net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/podman/v4/pkg/machine/compression"
|
|
"github.com/containers/podman/v4/pkg/machine/define"
|
|
"github.com/containers/podman/v4/pkg/machine/ocipull"
|
|
"github.com/containers/podman/v4/utils"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// GenericDownload is used when a user provides a URL
|
|
// or path for an image
|
|
type GenericDownload struct {
|
|
Download
|
|
}
|
|
|
|
// NewGenericDownloader is used when the disk image is provided by the user
|
|
func NewGenericDownloader(vmType define.VMType, vmName, pullPath string) (DistributionDownload, error) {
|
|
var (
|
|
imageName string
|
|
)
|
|
dataDir, err := GetDataDir(vmType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cacheDir, err := GetCacheDir(vmType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dl := Download{}
|
|
// Is pullpath a file or url?
|
|
if getURL := supportedURL(pullPath); getURL != nil {
|
|
urlSplit := strings.Split(getURL.Path, "/")
|
|
imageName = urlSplit[len(urlSplit)-1]
|
|
dl.URL = getURL
|
|
dl.LocalPath = filepath.Join(cacheDir, imageName)
|
|
} else {
|
|
// Dealing with FilePath
|
|
imageName = filepath.Base(pullPath)
|
|
dl.LocalPath = pullPath
|
|
}
|
|
dl.VMName = vmName
|
|
dl.ImageName = imageName
|
|
dl.LocalUncompressedFile = dl.GetLocalUncompressedFile(dataDir)
|
|
// The download needs to be pulled into the datadir
|
|
|
|
gd := GenericDownload{Download: dl}
|
|
return gd, nil
|
|
}
|
|
|
|
func supportedURL(path string) (url *url2.URL) {
|
|
getURL, err := url2.Parse(path)
|
|
if err != nil {
|
|
// ignore error, probably not a URL, fallback & treat as file path
|
|
return nil
|
|
}
|
|
|
|
// Check supported scheme. Since URL is passed to net.http, only http
|
|
// schemes are supported. Also, windows drive paths can resemble a
|
|
// URL, but with a single letter scheme. These values should be
|
|
// passed through for interpretation as a file path.
|
|
switch getURL.Scheme {
|
|
case "http":
|
|
fallthrough
|
|
case "https":
|
|
return getURL
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (dl Download) GetLocalUncompressedFile(dataDir string) string {
|
|
compressedFilename := dl.VMName + "_" + dl.ImageName
|
|
extension := compression.KindFromFile(compressedFilename)
|
|
uncompressedFile := strings.TrimSuffix(compressedFilename, fmt.Sprintf(".%s", extension.String()))
|
|
dl.LocalUncompressedFile = filepath.Join(dataDir, uncompressedFile)
|
|
return dl.LocalUncompressedFile
|
|
}
|
|
|
|
func (g GenericDownload) Get() *Download {
|
|
return &g.Download
|
|
}
|
|
|
|
func (g GenericDownload) HasUsableCache() (bool, error) {
|
|
// If we have a URL for this "downloader", we now pull it
|
|
return g.URL == nil, nil
|
|
}
|
|
|
|
// CleanCache cleans out downloaded uncompressed image files
|
|
func (g GenericDownload) CleanCache() error {
|
|
// Remove any image that has been downloaded via URL
|
|
// We never read from cache for generic downloads
|
|
if g.URL != nil {
|
|
if err := os.Remove(g.LocalPath); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DownloadImage(d DistributionDownload) error {
|
|
// check if the latest image is already present
|
|
ok, err := d.HasUsableCache()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
if err := DownloadVMImage(d.Get().URL, d.Get().ImageName, d.Get().LocalPath); err != nil {
|
|
return err
|
|
}
|
|
// Clean out old cached images, since we didn't find needed image in cache
|
|
defer func() {
|
|
if err = d.CleanCache(); err != nil {
|
|
logrus.Warnf("error cleaning machine image cache: %s", err)
|
|
}
|
|
}()
|
|
}
|
|
localPath, err := define.NewMachineFile(d.Get().LocalPath, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return compression.Decompress(localPath, d.Get().LocalUncompressedFile)
|
|
}
|
|
|
|
// DownloadVMImage downloads a VM image from url to given path
|
|
// with download status
|
|
func DownloadVMImage(downloadURL *url2.URL, imageName string, localImagePath string) error {
|
|
out, err := os.Create(localImagePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := out.Close(); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}()
|
|
|
|
resp, err := http.Get(downloadURL.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := resp.Body.Close(); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("downloading VM image %s: %s", downloadURL, resp.Status)
|
|
}
|
|
size := resp.ContentLength
|
|
prefix := "Downloading VM image: " + imageName
|
|
onComplete := prefix + ": done"
|
|
|
|
p, bar := utils.ProgressBar(prefix, size, onComplete)
|
|
|
|
proxyReader := bar.ProxyReader(resp.Body)
|
|
defer func() {
|
|
if err := proxyReader.Close(); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}()
|
|
|
|
if _, err := io.Copy(out, proxyReader); err != nil {
|
|
return err
|
|
}
|
|
|
|
p.Wait()
|
|
return nil
|
|
}
|
|
|
|
func RemoveImageAfterExpire(dir string, expire time.Duration) error {
|
|
now := time.Now()
|
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
// Delete any cache files that are older than expiry date
|
|
if !info.IsDir() && (now.Sub(info.ModTime()) > expire) {
|
|
err := os.Remove(path)
|
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
logrus.Warnf("unable to clean up cached image: %s", path)
|
|
} else {
|
|
logrus.Debugf("cleaning up cached image: %s", path)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
return err
|
|
}
|
|
|
|
// AcquireAlternateImage downloads the alternate image the user provided, which
|
|
// can be a file path or URL
|
|
func (dl Download) AcquireAlternateImage(inputPath string) (*define.VMFile, error) {
|
|
g, err := NewGenericDownloader(dl.VMKind, dl.VMName, inputPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
imagePath, err := define.NewMachineFile(g.Get().LocalUncompressedFile, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := DownloadImage(g); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return imagePath, nil
|
|
}
|
|
|
|
func isOci(input string) (bool, *ocipull.OCIKind, error) { //nolint:unused
|
|
inputURL, err := url2.Parse(input)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
switch inputURL.Scheme {
|
|
case ocipull.OCIDir.String():
|
|
return true, &ocipull.OCIDir, nil
|
|
case ocipull.OCIRegistry.String():
|
|
return true, &ocipull.OCIRegistry, nil
|
|
}
|
|
return false, nil, nil
|
|
}
|
|
|
|
// func Pull(input, machineName string, vp VirtProvider) (*define.VMFile, FCOSStream, error) {
|
|
// var (
|
|
// disk ocipull.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 := ocipull.OCIOpts{
|
|
// Scheme: ociScheme,
|
|
// }
|
|
// dataDir, err := GetDataDir(vp.VMType())
|
|
// if err != nil {
|
|
// return nil, 0, err
|
|
// }
|
|
// if ociScheme.IsOCIDir() {
|
|
// strippedOCIDir := ocipull.StripOCIReference(input)
|
|
// oopts.Dir = &strippedOCIDir
|
|
// disk = ocipull.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 = ocipull.NewVersioned(context.Background(), dataDir, machineName, vp.VMType().String())
|
|
// 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
|
|
//}
|