Files
podman/pkg/machine/wsl/stubber.go
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

345 lines
9.3 KiB
Go

//go:build windows
package wsl
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/containers/podman/v5/pkg/machine/env"
"github.com/containers/podman/v5/pkg/machine/ocipull"
"github.com/containers/podman/v5/pkg/machine/shim/diskpull"
"github.com/containers/podman/v5/pkg/machine/stdpull"
"github.com/containers/podman/v5/pkg/machine/wsl/wutil"
"github.com/containers/podman/v5/utils"
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/podman/v5/pkg/machine"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/ignition"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/sirupsen/logrus"
)
type WSLStubber struct {
vmconfigs.WSLConfig
}
func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, _ *ignition.IgnitionBuilder) error {
var (
err error
)
// cleanup half-baked files if init fails at any point
callbackFuncs := machine.CleanUp()
defer callbackFuncs.CleanIfErr(&err)
go callbackFuncs.CleanOnSignal()
mc.WSLHypervisor = new(vmconfigs.WSLConfig)
if cont, err := checkAndInstallWSL(opts.ReExec); !cont {
appendOutputIfError(opts.ReExec, err)
return err
}
_ = setupWslProxyEnv()
if opts.UserModeNetworking {
if err = verifyWSLUserModeCompat(); err != nil {
return err
}
mc.WSLHypervisor.UserModeNetworking = true
}
const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..."
dist, err := provisionWSLDist(mc.Name, mc.ImagePath.GetPath(), prompt)
if err != nil {
return err
}
unprovisionCallbackFunc := func() error {
return unprovisionWSL(mc)
}
callbackFuncs.Add(unprovisionCallbackFunc)
if mc.WSLHypervisor.UserModeNetworking {
if err = installUserModeDist(dist, mc.ImagePath.GetPath()); err != nil {
_ = unregisterDist(dist)
return err
}
}
fmt.Println("Configuring system...")
if err = configureSystem(mc, dist); err != nil {
return err
}
if err = installScripts(dist); err != nil {
return err
}
if err = createKeys(mc, dist); err != nil {
return err
}
// recycle vm
return terminateDist(dist)
}
func (w WSLStubber) PrepareIgnition(_ *vmconfigs.MachineConfig, _ *ignition.IgnitionBuilder) (*ignition.ReadyUnitOpts, error) {
return nil, nil
}
func (w WSLStubber) Exists(name string) (bool, error) {
return isWSLExist(env.WithPodmanPrefix(name))
}
func (w WSLStubber) MountType() vmconfigs.VolumeMountType {
return vmconfigs.Unknown
}
func (w WSLStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool) error {
return nil
}
func (w WSLStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() error, error) {
// Note: we could consider swapping the two conditionals
// below if we wanted to hard error on the wsl unregister
// of the vm
wslRemoveFunc := func() error {
if err := runCmdPassThrough(wutil.FindWSL(), "--unregister", env.WithPodmanPrefix(mc.Name)); err != nil {
logrus.Error(err)
}
return nil
}
return []string{}, wslRemoveFunc, nil
}
func (w WSLStubber) RemoveAndCleanMachines(_ *define.MachineDirs) error {
return nil
}
func (w WSLStubber) SetProviderAttrs(mc *vmconfigs.MachineConfig, opts define.SetOptions) error {
state, err := w.State(mc, false)
if err != nil {
return err
}
if state != define.Stopped {
return errors.New("unable to change settings unless vm is stopped")
}
if opts.Rootful != nil && mc.HostUser.Rootful != *opts.Rootful {
if err := mc.SetRootful(*opts.Rootful); err != nil {
return err
}
}
if opts.CPUs != nil {
return errors.New("changing CPUs not supported for WSL machines")
}
if opts.Memory != nil {
return errors.New("changing memory not supported for WSL machines")
}
if opts.USBs != nil {
return errors.New("changing USBs not supported for WSL machines")
}
if opts.DiskSize != nil {
return errors.New("changing disk size not supported for WSL machines")
}
if opts.UserModeNetworking != nil && mc.WSLHypervisor.UserModeNetworking != *opts.UserModeNetworking {
if running, _ := isRunning(mc.Name); running {
return errors.New("user-mode networking can only be changed when the machine is not running")
}
dist := env.WithPodmanPrefix(mc.Name)
if err := changeDistUserModeNetworking(dist, mc.SSH.RemoteUsername, mc.ImagePath.GetPath(), *opts.UserModeNetworking); err != nil {
return fmt.Errorf("failure changing state of user-mode networking setting: %w", err)
}
mc.WSLHypervisor.UserModeNetworking = *opts.UserModeNetworking
}
return nil
}
func (w WSLStubber) StartNetworking(mc *vmconfigs.MachineConfig, cmd *gvproxy.GvproxyCommand) error {
// Startup user-mode networking if enabled
if mc.WSLHypervisor.UserModeNetworking {
return startUserModeNetworking(mc)
}
return nil
}
func (w WSLStubber) UserModeNetworkEnabled(mc *vmconfigs.MachineConfig) bool {
return mc.WSLHypervisor.UserModeNetworking
}
func (w WSLStubber) UseProviderNetworkSetup() bool {
return true
}
func (w WSLStubber) RequireExclusiveActive() bool {
return false
}
func (w WSLStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bool) error {
winProxyOpts := machine.WinProxyOpts{
Name: mc.Name,
IdentityPath: mc.SSH.IdentityPath,
Port: mc.SSH.Port,
RemoteUsername: mc.SSH.RemoteUsername,
Rootful: mc.HostUser.Rootful,
VMType: w.VMType(),
}
machine.LaunchWinProxy(winProxyOpts, noInfo)
return nil
}
func (w WSLStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func() error, error) {
dist := env.WithPodmanPrefix(mc.Name)
err := wslInvoke(dist, "/root/bootstrap")
if err != nil {
err = fmt.Errorf("the WSL bootstrap script failed: %w", err)
}
readyFunc := func() error {
return nil
}
return nil, readyFunc, err
}
func (w WSLStubber) State(mc *vmconfigs.MachineConfig, bypass bool) (define.Status, error) {
running, err := isRunning(mc.Name)
if err != nil {
return "", err
}
if running {
return define.Running, nil
}
return define.Stopped, nil
}
func (w WSLStubber) StopVM(mc *vmconfigs.MachineConfig, hardStop bool) error {
var (
err error
)
if running, err := isRunning(mc.Name); !running {
return err
}
dist := env.WithPodmanPrefix(mc.Name)
// Stop user-mode networking if enabled
if err := stopUserModeNetworking(mc); err != nil {
fmt.Fprintf(os.Stderr, "Could not cleanly stop user-mode networking: %s\n", err.Error())
}
if err := machine.StopWinProxy(mc.Name, vmtype); err != nil {
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
}
cmd := exec.Command(wutil.FindWSL(), "-u", "root", "-d", dist, "sh")
cmd.Stdin = strings.NewReader(waitTerm)
if err = cmd.Start(); err != nil {
return fmt.Errorf("executing wait command: %w", err)
}
exitCmd := exec.Command(wutil.FindWSL(), "-u", "root", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
if err = exitCmd.Run(); err != nil {
return fmt.Errorf("stopping sysd: %w", err)
}
if err = cmd.Wait(); err != nil {
return err
}
return terminateDist(dist)
}
func (w WSLStubber) StopHostNetworking(mc *vmconfigs.MachineConfig, vmType define.VMType) error {
return stopUserModeNetworking(mc)
}
func (w WSLStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error {
dist := env.WithPodmanPrefix(mc.Name)
if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(changePort, port)); err != nil {
return fmt.Errorf("could not change SSH port for guest OS: %w", err)
}
return nil
}
func (w WSLStubber) VMType() define.VMType {
return define.WSLVirt
}
func (w WSLStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
var (
myDisk ocipull.Disker
)
if userInputPath != "" {
return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, w.VMType(), mc.Name)
}
// check github for the latest version of the WSL dist
downloadURL, downloadVersion, _, _, err := GetFedoraDownloadForWSL()
if err != nil {
return err
}
// we now save the "cached" rootfs in the form of "v<version-number>-rootfs.tar.xz"
// i.e.v39.0.31-rootfs.tar.xz
versionedBase := fmt.Sprintf("%s-%s", downloadVersion, filepath.Base(downloadURL.Path))
cachedFile, err := dirs.ImageCacheDir.AppendToNewVMFile(versionedBase, nil)
if err != nil {
return err
}
// if we find the same file cached (determined by filename only), then dont pull
if _, err = os.Stat(cachedFile.GetPath()); err == nil {
logrus.Debugf("%q already exists locally", cachedFile.GetPath())
myDisk, err = stdpull.NewStdDiskPull(cachedFile.GetPath(), mc.ImagePath)
if err != nil {
return err
}
} else {
files, err := os.ReadDir(dirs.ImageCacheDir.GetPath())
if err != nil {
logrus.Warn("failed to clean machine image cache: ", err)
} else {
defer func() {
for _, file := range files {
path := filepath.Join(dirs.ImageCacheDir.GetPath(), file.Name())
logrus.Debugf("cleaning cached image: %s", path)
err := utils.GuardedRemoveAll(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
logrus.Warn("failed to clean machine image cache: ", err)
}
}
}()
}
myDisk, err = stdpull.NewDiskFromURL(downloadURL.String(), mc.ImagePath, dirs.ImageCacheDir, &versionedBase, true)
if err != nil {
return err
}
}
// up until now, nothing has really happened
// pull if needed and decompress to image location
return myDisk.Get()
}