mirror of
https://github.com/containers/podman.git
synced 2025-11-01 10:45:52 +08:00
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>
345 lines
9.3 KiB
Go
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()
|
|
}
|