Files
Ashley Cui e412eff33f Clean machine pull cache
Cache cleanups only happen if there is a cache miss, and we need to pull a new image

For quay.io/podman/machine-os, we remove all old images from the cache dir. This means we will delete any file that exists in the cache dir; this should be safe to do since the machine pull code should be the only thing touching this cache dir. OCI machine images will always have a different manifest, and won’t be updated with the same manifest, so if the version moves on, there isn’t a reason to keep the old version in the cache, it really doesn’t change.

For Fedora (WSL), we use the cache, so we go through the cache dir and remove any old cached images, on a cache miss. We also switch to using ~/.local/share/containers/podman/machine/wsl/cache as the cache dir rather than ~/.local/share/containers/podman/machine/wsl. Both these behaviors existed in v4.9, but are now added back into 5.x.

For generic files pulled from a URL or a non-default OCI image, we shouldn’t actually cache, so we delete the pulled file immediately after creating a machine image. This restores the behavior from v4.9.

For generic files from a local path, the original file will never be cleaned up

Unsure how to test, so:
[NO NEW TESTS NEEDED]

Signed-off-by: Ashley Cui <acui@redhat.com>
2024-04-26 12:31:42 -04:00

128 lines
2.9 KiB
Go

package stdpull
import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
url2 "net/url"
"os"
"path"
"path/filepath"
"github.com/containers/podman/v5/pkg/machine/compression"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/utils"
"github.com/containers/storage/pkg/fileutils"
"github.com/sirupsen/logrus"
)
type DiskFromURL struct {
u *url2.URL
finalPath *define.VMFile
tempLocation *define.VMFile
cache bool
}
func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.VMFile, optionalTempFileName *string, cache bool) (*DiskFromURL, error) {
var (
err error
)
u, err := url2.Parse(inputPath)
if err != nil {
return nil, err
}
// Make sure the temporary location exists before we get too deep
if err := fileutils.Exists(tempDir.GetPath()); err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, fmt.Errorf("temporary download directory %s does not exist", tempDir.GetPath())
}
}
remoteImageName := path.Base(inputPath)
if optionalTempFileName != nil {
remoteImageName = *optionalTempFileName
}
if remoteImageName == "" {
return nil, fmt.Errorf("invalid url: unable to determine image name in %q", inputPath)
}
tempLocation, err := tempDir.AppendToNewVMFile(remoteImageName, nil)
if err != nil {
return nil, err
}
return &DiskFromURL{
u: u,
finalPath: finalPath,
tempLocation: tempLocation,
cache: cache,
}, nil
}
func (d *DiskFromURL) Get() error {
// this fetches the image and writes it to the temporary location
if err := d.pull(); err != nil {
return err
}
if !d.cache {
defer func() {
if err := utils.GuardedRemoveAll(d.tempLocation.GetPath()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
logrus.Warn("failed to clean machine image cache: ", err)
}
}
}()
}
logrus.Debugf("decompressing (if needed) %s to %s", d.tempLocation.GetPath(), d.finalPath.GetPath())
return compression.Decompress(d.tempLocation, d.finalPath.GetPath())
}
func (d *DiskFromURL) pull() error {
out, err := os.Create(d.tempLocation.GetPath())
if err != nil {
return err
}
defer func() {
if err := out.Close(); err != nil {
logrus.Error(err)
}
}()
resp, err := http.Get(d.u.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", d.u.String(), resp.Status)
}
size := resp.ContentLength
prefix := "Downloading VM image: " + filepath.Base(d.tempLocation.GetPath())
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
}