Files
podman/pkg/machine/wsl/stubber.go
Paul Holzinger f218f8430a machine: implement http proxy logic for all providers
Copy all proxy envs into the VM on each start, this allows for updates
without having to recrate the VM. This is implemented via shell script
that is passed via ssh to the VM after it is started.

With that we now use the same logic for all providers the old fw_cfg
logic for qemu has been removed and the WSL code as well which keeps the
behavior the same.

There is a small risk now because we only update the env via ssh that
processes started before will have the old incorrect env but it should
really only effect core system processes which likely do not need them
anyway. The podman system service should not be started at this point
so it should be good enough.

It also fixes the broken behavior with SSL_CERT_FILE/SSL_CERT_DIR which
were updated on each start which is not correct as the files are only
copied with ignition so these should not be updated and just set
statically when the VM was created.

e2e test has been added to ensure the behavior works as expected.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2024-02-22 11:23:45 +01:00

326 lines
8.7 KiB
Go

//go:build windows
package wsl
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/containers/podman/v5/pkg/machine/ocipull"
"github.com/containers/podman/v5/pkg/machine/stdpull"
"github.com/containers/podman/v5/pkg/machine/wsl/wutil"
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.InitCleanup()
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(machine.ToDist(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", machine.ToDist(mc.Name)); err != nil {
logrus.Error(err)
}
return machine.ReleaseMachinePort(mc.SSH.Port)
}
return []string{}, wslRemoveFunc, nil
}
func (w WSLStubber) RemoveAndCleanMachines(_ *define.MachineDirs) error {
return nil
}
func (w WSLStubber) SetProviderAttrs(mc *vmconfigs.MachineConfig, opts define.SetOptions) error {
mc.Lock()
defer mc.Unlock()
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 := machine.ToDist(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 := machine.ToDist(mc.Name)
// TODO The original code checked to see if the SSH port was actually open and re-assigned if it was
// we could consider this but it should be higher up the stack
// if !machine.IsLocalPortAvailable(v.Port) {
// logrus.Warnf("SSH port conflict detected, reassigning a new port")
// if err := v.reassignSshPort(); err != nil {
// return err
// }
// }
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
)
mc.Lock()
defer mc.Unlock()
// recheck after lock
if running, err := isRunning(mc.Name); !running {
return err
}
dist := machine.ToDist(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) VMType() define.VMType {
return define.WSLVirt
}
func (w WSLStubber) GetDisk(_ string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
var (
myDisk ocipull.Disker
)
// 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))
// TODO we need a mechanism for "flushing" old cache files
cachedFile, err := dirs.DataDir.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)
} else {
// no cached file
myDisk, err = stdpull.NewDiskFromURL(downloadURL.String(), mc.ImagePath, dirs.DataDir, &versionedBase)
}
if err != nil {
return err
}
// up until now, nothing has really happened
// pull if needed and decompress to image location
return myDisk.Get()
}