wsl - wip

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2024-02-01 09:00:36 -06:00
committed by Jason T. Greene
parent 7c7b4430a5
commit d7cb66492b
20 changed files with 891 additions and 1070 deletions

View File

@ -16,6 +16,7 @@ import (
"github.com/containers/podman/v5/pkg/machine/applehv/vfkit"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/ignition"
"github.com/containers/podman/v5/pkg/machine/shim/diskpull"
"github.com/containers/podman/v5/pkg/machine/sockets"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/containers/podman/v5/utils"
@ -38,6 +39,10 @@ type AppleHVStubber struct {
vmconfigs.AppleHVConfig
}
func (a AppleHVStubber) UserModeNetworkEnabled(_ *vmconfigs.MachineConfig) bool {
return true
}
func (a AppleHVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, ignBuilder *ignition.IgnitionBuilder) error {
mc.AppleHypervisor = new(vmconfigs.AppleHVConfig)
mc.AppleHypervisor.Vfkit = vfkit.VfkitHelper{}
@ -317,3 +322,7 @@ func (a AppleHVStubber) PrepareIgnition(_ *vmconfigs.MachineConfig, _ *ignition.
func (a AppleHVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
return nil
}
func (a AppleHVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, a.VMType(), mc.Name)
}

View File

@ -10,8 +10,10 @@ var (
)
type CreateVMOpts struct {
Name string
Dirs *MachineDirs
Name string
Dirs *MachineDirs
ReExec bool
UserModeNetworking bool
}
type MachineDirs struct {

View File

@ -2,6 +2,7 @@ package e2e_test
import (
"fmt"
"github.com/containers/podman/v4/pkg/machine/wsl"
"io"
url2 "net/url"
"os"
@ -61,9 +62,20 @@ var _ = BeforeSuite(func() {
downloadLocation := os.Getenv("MACHINE_IMAGE")
if downloadLocation == "" {
downloadLocation, err = GetDownload(testProvider.VMType())
if err != nil {
Fail("unable to derive download disk from fedora coreos")
// TODO so beautifully gross ... ideally we can spend some time
// here making life easier on the next person
switch testProvider.VMType() {
case define.WSLVirt:
dl, _, _, _, err := wsl.GetFedoraDownloadForWSL()
if err != nil {
Fail("unable to determine WSL download")
}
downloadLocation = dl.String()
default:
downloadLocation, err = GetDownload(testProvider.VMType())
if err != nil {
Fail("unable to derive download disk from fedora coreos")
}
}
}

View File

@ -171,6 +171,9 @@ var _ = Describe("podman machine set", func() {
if testProvider.VMType() != define.WSLVirt {
Skip("Test is only for WSL")
}
// TODO - this currently fails
Skip("test fails bc usermode network needs plumbing for WSL")
name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()

View File

@ -10,6 +10,8 @@ import (
"os/exec"
"path/filepath"
"github.com/containers/podman/v5/pkg/machine/shim/diskpull"
"github.com/Microsoft/go-winio"
"github.com/containers/common/pkg/strongunits"
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
@ -27,6 +29,10 @@ type HyperVStubber struct {
vmconfigs.HyperVConfig
}
func (h HyperVStubber) UserModeNetworkEnabled(mc *vmconfigs.MachineConfig) bool {
return true
}
func (h HyperVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, builder *ignition.IgnitionBuilder) error {
var (
err error
@ -459,6 +465,10 @@ func (h HyperVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
return err
}
func (h HyperVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, h.VMType(), mc.Name)
}
func resizeDisk(newSize strongunits.GiB, imagePath *define.VMFile) error {
resize := exec.Command("powershell", []string{"-command", fmt.Sprintf("Resize-VHD %s %d", imagePath.GetPath(), newSize.ToBytes())}...)
logrus.Debug(resize.Args)

View File

@ -134,6 +134,13 @@ func launchWinProxy(opts WinProxyOpts) (bool, string, error) {
cmd := exec.Command(command, args...)
logrus.Debugf("winssh command: %s %v", command, args)
f, err := os.Open("c:\\Users\\baude\\sshproxy.log")
if err != nil {
return false, "", err
}
cmd.Stderr = f
cmd.Stdout = f
defer f.Close()
if err := cmd.Start(); err != nil {
return globalName, "", err
}

View File

@ -2,9 +2,11 @@ package provider
import (
"fmt"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"os"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/containers/podman/v5/pkg/machine/wsl"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/hyperv"
@ -27,9 +29,8 @@ func Get() (vmconfigs.VMProvider, error) {
logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String())
switch resolvedVMType {
// TODO re-enable this with WSL
//case define.WSLVirt:
// return wsl.VirtualizationProvider(), nil
case define.WSLVirt:
return new(wsl.WSLStubber), nil
case define.HyperVVirt:
return new(hyperv.HyperVStubber), nil
default:

View File

@ -11,14 +11,14 @@ import (
"strings"
"time"
"github.com/containers/podman/v5/pkg/machine/ignition"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/strongunits"
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/qemu/command"
"github.com/containers/podman/v5/pkg/machine/shim/diskpull"
"github.com/containers/podman/v5/pkg/machine/sockets"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/sirupsen/logrus"
@ -30,6 +30,10 @@ type QEMUStubber struct {
Command command.QemuCmd
}
func (q QEMUStubber) UserModeNetworkEnabled(*vmconfigs.MachineConfig) bool {
return true
}
func (q *QEMUStubber) setQEMUCommandLine(mc *vmconfigs.MachineConfig) error {
qemuBinary, err := findQEMUBinary()
if err != nil {
@ -326,3 +330,7 @@ func (q *QEMUStubber) MountType() vmconfigs.VolumeMountType {
func (q *QEMUStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
return nil
}
func (q *QEMUStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, q.VMType(), mc.Name)
}

View File

@ -0,0 +1,32 @@
package diskpull
import (
"context"
"strings"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/ocipull"
"github.com/containers/podman/v5/pkg/machine/stdpull"
)
func GetDisk(userInputPath string, dirs *define.MachineDirs, imagePath *define.VMFile, vmType define.VMType, name string) error {
var (
err error
mydisk ocipull.Disker
)
if userInputPath == "" {
mydisk, err = ocipull.NewVersioned(context.Background(), dirs.DataDir, name, vmType.String(), imagePath)
} else {
if strings.HasPrefix(userInputPath, "http") {
// TODO probably should use tempdir instead of datadir
mydisk, err = stdpull.NewDiskFromURL(userInputPath, imagePath, dirs.DataDir, nil)
} else {
mydisk, err = stdpull.NewStdDiskPull(userInputPath, imagePath)
}
}
if err != nil {
return err
}
return mydisk.Get()
}

View File

@ -1,12 +1,10 @@
package shim
import (
"context"
"errors"
"fmt"
"os"
"runtime"
"strings"
"time"
"github.com/containers/common/pkg/util"
@ -14,30 +12,13 @@ import (
"github.com/containers/podman/v5/pkg/machine/connection"
machineDefine "github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/ignition"
"github.com/containers/podman/v5/pkg/machine/ocipull"
"github.com/containers/podman/v5/pkg/machine/stdpull"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/sirupsen/logrus"
)
/*
Host
├ Info
├ OS Apply
├ SSH
├ List
├ Init
├ VMExists
├ CheckExclusiveActiveVM *HyperV/WSL need to check their hypervisors as well
*/
func Info() {}
func OSApply() {}
func SSH() {}
// List is done at the host level to allow for a *possible* future where
// more than one provider is used
func List(vmstubbers []vmconfigs.VMProvider, opts machine.ListOptions) ([]*machine.ListResponse, error) {
func List(vmstubbers []vmconfigs.VMProvider, _ machine.ListOptions) ([]*machine.ListResponse, error) {
var (
lrs []*machine.ListResponse
)
@ -114,6 +95,10 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M
Dirs: dirs,
}
if umn := opts.UserModeNetworking; umn != nil {
createOpts.UserModeNetworking = *umn
}
// Get Image
// TODO This needs rework bigtime; my preference is most of below of not living in here.
// ideally we could get a func back that pulls the image, and only do so IF everything works because
@ -137,8 +122,7 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M
if err != nil {
return nil, err
}
var mydisk ocipull.Disker
mc.ImagePath = imagePath
// TODO The following stanzas should be re-written in a differeent place. It should have a custom
// parser for our image pulling. It would be nice if init just got an error and mydisk back.
@ -149,25 +133,12 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M
// "/path
// "docker://quay.io/something/someManifest
if opts.ImagePath == "" {
mydisk, err = ocipull.NewVersioned(context.Background(), dirs.DataDir, opts.Name, mp.VMType().String(), imagePath)
} else {
if strings.HasPrefix(opts.ImagePath, "http") {
// TODO probably should use tempdir instead of datadir
mydisk, err = stdpull.NewDiskFromURL(opts.ImagePath, imagePath, dirs.DataDir)
} else {
mydisk, err = stdpull.NewStdDiskPull(opts.ImagePath, imagePath)
}
}
if err != nil {
return nil, err
}
err = mydisk.Get()
// TODO Ideally this changes into some way better ...
err = mp.GetDisk(opts.ImagePath, dirs, mc)
if err != nil {
return nil, err
}
mc.ImagePath = imagePath
callbackFuncs.Add(mc.ImagePath.Delete)
logrus.Debugf("--> imagePath is %q", imagePath.GetPath())
@ -182,8 +153,18 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M
uid = 1000
}
// TODO the definition of "user" should go into
// common for WSL
userName := opts.Username
if mp.VMType() == machineDefine.WSLVirt {
if opts.Username == "core" {
userName = "user"
mc.SSH.RemoteUsername = "user"
}
}
ignBuilder := ignition.NewIgnitionBuilder(ignition.DynamicIgnition{
Name: opts.Username,
Name: userName,
Key: sshKey,
TimeZone: opts.TimeZone,
UID: uid,
@ -223,7 +204,9 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M
ignBuilder.WithUnit(readyUnit)
// Mounts
mc.Mounts = CmdLineVolumesToMounts(opts.Volumes, mp.MountType())
if mp.VMType() != machineDefine.WSLVirt {
mc.Mounts = CmdLineVolumesToMounts(opts.Volumes, mp.MountType())
}
// TODO AddSSHConnectionToPodmanSocket could take an machineconfig instead
if err := connection.AddSSHConnectionsToPodmanSocket(mc.HostUser.UID, mc.SSH.Port, mc.SSH.IdentityPath, mc.Name, mc.SSH.RemoteUsername, opts); err != nil {
@ -347,21 +330,23 @@ func Stop(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDef
}
// Stop GvProxy and remove PID file
gvproxyPidFile, err := dirs.RuntimeDir.AppendToNewVMFile("gvproxy.pid", nil)
if err != nil {
return err
}
defer func() {
if err := machine.CleanupGVProxy(*gvproxyPidFile); err != nil {
logrus.Errorf("unable to clean up gvproxy: %q", err)
if mp.UserModeNetworkEnabled(mc) {
gvproxyPidFile, err := dirs.RuntimeDir.AppendToNewVMFile("gvproxy.pid", nil)
if err != nil {
return err
}
}()
defer func() {
if err := machine.CleanupGVProxy(*gvproxyPidFile); err != nil {
logrus.Errorf("unable to clean up gvproxy: %q", err)
}
}()
}
return nil
}
func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDefine.MachineDirs, opts machine.StartOptions) error {
func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, _ *machineDefine.MachineDirs, opts machine.StartOptions) error {
defaultBackoff := 500 * time.Millisecond
maxBackoffs := 6

View File

@ -23,16 +23,12 @@ const (
dockerConnectTimeout = 5 * time.Second
)
func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) (string, machine.APIForwardingState, error) {
var (
forwardingState machine.APIForwardingState
forwardSock string
)
// the guestSock is "inside" the guest machine
guestSock := fmt.Sprintf(defaultGuestSock, mc.HostUser.UID)
func startUserModeNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider, dirs *define.MachineDirs, hostSocket *define.VMFile) error {
forwardUser := mc.SSH.RemoteUsername
// TODO should this go up the stack higher
// TODO should this go up the stack higher or
// the guestSock is "inside" the guest machine
guestSock := fmt.Sprintf(defaultGuestSock, mc.HostUser.UID)
if mc.HostUser.Rootful {
guestSock = "/run/podman/podman.sock"
forwardUser = "root"
@ -40,38 +36,18 @@ func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider)
cfg, err := config.Default()
if err != nil {
return "", 0, err
return err
}
binary, err := cfg.FindHelperBinary(machine.ForwarderBinaryName, false)
if err != nil {
return "", 0, err
}
dataDir, err := mc.DataDir()
if err != nil {
return "", 0, err
}
hostSocket, err := dataDir.AppendToNewVMFile("podman.sock", nil)
if err != nil {
return "", 0, err
}
runDir, err := mc.RuntimeDir()
if err != nil {
return "", 0, err
}
linkSocketPath := filepath.Dir(dataDir.GetPath())
linkSocket, err := define.NewMachineFile(filepath.Join(linkSocketPath, "podman.sock"), nil)
if err != nil {
return "", 0, err
return err
}
cmd := gvproxy.NewGvproxyCommand()
// GvProxy PID file path is now derived
cmd.PidFile = filepath.Join(runDir.GetPath(), "gvproxy.pid")
cmd.PidFile = filepath.Join(dirs.RuntimeDir.GetPath(), "gvproxy.pid")
// TODO This can be re-enabled when gvisor-tap-vsock #305 is merged
// debug is set, we dump to a logfile as well
@ -94,6 +70,36 @@ func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider)
// This allows a provider to perform additional setup as well as
// add in any provider specific options for gvproxy
if err := provider.StartNetworking(mc, &cmd); err != nil {
return err
}
c := cmd.Cmd(binary)
logrus.Debugf("gvproxy command-line: %s %s", binary, strings.Join(cmd.ToCmdline(), " "))
if err := c.Start(); err != nil {
return fmt.Errorf("unable to execute: %q: %w", cmd.ToCmdline(), err)
}
return nil
}
func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) (string, machine.APIForwardingState, error) {
var (
forwardingState machine.APIForwardingState
forwardSock string
)
dirs, err := machine.GetMachineDirs(provider.VMType())
if err != nil {
return "", 0, err
}
hostSocket, err := dirs.DataDir.AppendToNewVMFile("podman.sock", nil)
if err != nil {
return "", 0, err
}
linkSocketPath := filepath.Dir(dirs.DataDir.GetPath())
linkSocket, err := define.NewMachineFile(filepath.Join(linkSocketPath, "podman.sock"), nil)
if err != nil {
return "", 0, err
}
@ -101,21 +107,15 @@ func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider)
forwardSock, forwardingState = setupAPIForwarding(hostSocket, linkSocket)
}
c := cmd.Cmd(binary)
logrus.Debugf("gvproxy command-line: %s %s", binary, strings.Join(cmd.ToCmdline(), " "))
if err := c.Start(); err != nil {
return forwardSock, 0, fmt.Errorf("unable to execute: %q: %w", cmd.ToCmdline(), err)
if provider.UserModeNetworkEnabled(mc) {
if err := startUserModeNetworking(mc, provider, dirs, hostSocket); err != nil {
return "", 0, err
}
}
return forwardSock, forwardingState, nil
}
type apiOptions struct { //nolint:unused
socketpath, destinationSocketPath *define.VMFile
fowardUser string
}
func setupAPIForwarding(hostSocket, linkSocket *define.VMFile) (string, machine.APIForwardingState) {
// The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket)
// This allows the helper to only have to maintain one constant target to the user, which can be

View File

@ -23,7 +23,7 @@ type DiskFromURL struct {
tempLocation *define.VMFile
}
func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.VMFile) (*DiskFromURL, error) {
func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.VMFile, optionalTempFileName *string) (*DiskFromURL, error) {
var (
err error
)
@ -40,6 +40,9 @@ func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.
}
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)
}

View File

@ -107,6 +107,10 @@ func (f fcosMachineImage) path() string {
type VMProvider interface { //nolint:interfacebloat
CreateVM(opts define.CreateVMOpts, mc *MachineConfig, builder *ignition.IgnitionBuilder) error
// GetDisk should be only temporary. It is largely here only because WSL disk pulling is different
// TODO
// Let's deprecate this ASAP
GetDisk(userInputPath string, dirs *define.MachineDirs, mc *MachineConfig) error
PrepareIgnition(mc *MachineConfig, ignBuilder *ignition.IgnitionBuilder) (*ignition.ReadyUnitOpts, error)
GetHyperVisorVMs() ([]string, error)
MountType() VolumeMountType
@ -121,6 +125,7 @@ type VMProvider interface { //nolint:interfacebloat
StopVM(mc *MachineConfig, hardStop bool) error
StopHostNetworking(mc *MachineConfig, vmType define.VMType) error
VMType() define.VMType
UserModeNetworkEnabled(mc *MachineConfig) bool
}
// HostUser describes the host user

View File

@ -13,7 +13,8 @@ type HyperVConfig struct {
}
type WSLConfig struct {
//wslstuff *aThing
// Uses usermode networking
UserModeNetworking bool
}
// Stubs

View File

@ -77,6 +77,7 @@ func NewMachineConfig(opts define.InitOptions, dirs *define.MachineDirs, sshIden
}
mc.Resources = mrc
// TODO WSL had a locking port mechanism, we should consider this.
sshPort, err := utils.GetRandomPort()
if err != nil {
return nil, err

247
pkg/machine/wsl/declares.go Normal file
View File

@ -0,0 +1,247 @@
package wsl
const (
ErrorSuccessRebootInitiated = 1641
ErrorSuccessRebootRequired = 3010
currentMachineVersion = 3
)
const containersConf = `[containers]
[engine]
cgroup_manager = "cgroupfs"
`
const registriesConf = `unqualified-search-registries=["docker.io"]
`
const appendPort = `grep -q Port\ %d /etc/ssh/sshd_config || echo Port %d >> /etc/ssh/sshd_config`
const changePort = `sed -E -i 's/^Port[[:space:]]+[0-9]+/Port %d/' /etc/ssh/sshd_config`
const configServices = `ln -fs /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/sshd.service
ln -fs /usr/lib/systemd/system/podman.socket /etc/systemd/system/sockets.target.wants/podman.socket
rm -f /etc/systemd/system/getty.target.wants/console-getty.service
rm -f /etc/systemd/system/getty.target.wants/getty@tty1.service
rm -f /etc/systemd/system/multi-user.target.wants/systemd-resolved.service
rm -f /etc/systemd/system/sysinit.target.wants//systemd-resolved.service
rm -f /etc/systemd/system/dbus-org.freedesktop.resolve1.service
ln -fs /dev/null /etc/systemd/system/console-getty.service
ln -fs /dev/null /etc/systemd/system/systemd-oomd.socket
mkdir -p /etc/systemd/system/systemd-sysusers.service.d/
echo CREATE_MAIL_SPOOL=no >> /etc/default/useradd
adduser -m [USER] -G wheel
mkdir -p /home/[USER]/.config/systemd/[USER]/
chown [USER]:[USER] /home/[USER]/.config
`
const sudoers = `%wheel ALL=(ALL) NOPASSWD: ALL
`
const bootstrap = `#!/bin/bash
ps -ef | grep -v grep | grep -q systemd && exit 0
nohup unshare --kill-child --fork --pid --mount --mount-proc --propagation shared /lib/systemd/systemd >/dev/null 2>&1 &
sleep 0.1
`
const wslmotd = `
You will be automatically entered into a nested process namespace where
systemd is running. If you need to access the parent namespace, hit ctrl-d
or type exit. This also means to log out you need to exit twice.
`
const sysdpid = "SYSDPID=`ps -eo cmd,pid | grep -m 1 ^/lib/systemd/systemd | awk '{print $2}'`"
const profile = sysdpid + `
if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then
cat /etc/wslmotd
/usr/local/bin/enterns
fi
`
const enterns = "#!/bin/bash\n" + sysdpid + `
if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then
NSENTER=("nsenter" "-m" "-p" "-t" "$SYSDPID" "--wd=$PWD")
if [ "$UID" != "0" ]; then
NSENTER=("sudo" "${NSENTER[@]}")
if [ "$#" != "0" ]; then
NSENTER+=("sudo" "-u" "$USER")
else
NSENTER+=("su" "-l" "$USER")
fi
fi
"${NSENTER[@]}" "$@"
fi`
const waitTerm = sysdpid + `
if [ ! -z "$SYSDPID" ]; then
timeout 60 tail -f /dev/null --pid $SYSDPID
fi
`
const wslConf = `[user]
default=[USER]
`
const wslConfUserNet = `
[network]
generateResolvConf = false
`
const resolvConfUserNet = `
nameserver 192.168.127.1
`
// WSL kernel does not have sg and crypto_user modules
const overrideSysusers = `[Service]
LoadCredential=
`
const lingerService = `[Unit]
Description=A systemd user unit demo
After=network-online.target
Wants=network-online.target podman.socket
[Service]
ExecStart=/usr/bin/sleep infinity
`
const lingerSetup = `mkdir -p /home/[USER]/.config/systemd/user/default.target.wants
ln -fs /home/[USER]/.config/systemd/user/linger-example.service \
/home/[USER]/.config/systemd/user/default.target.wants/linger-example.service
`
const bindMountSystemService = `
[Unit]
Description=Bind mount for system podman sockets
After=podman.socket
[Service]
RemainAfterExit=true
Type=oneshot
# Ensure user services can register sockets as well
ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets
ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets/%[1]s
ExecStartPre=touch /mnt/wsl/podman-sockets/%[1]s/podman-root.sock
ExecStart=mount --bind %%t/podman/podman.sock /mnt/wsl/podman-sockets/%[1]s/podman-root.sock
ExecStop=umount /mnt/wsl/podman-sockets/%[1]s/podman-root.sock
`
const bindMountUserService = `
[Unit]
Description=Bind mount for user podman sockets
After=podman.socket
[Service]
RemainAfterExit=true
Type=oneshot
# Consistency with system service (supports racing)
ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets
ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets/%[1]s
ExecStartPre=touch /mnt/wsl/podman-sockets/%[1]s/podman-user.sock
# Relies on /etc/fstab entry for user mounting
ExecStart=mount /mnt/wsl/podman-sockets/%[1]s/podman-user.sock
ExecStop=umount /mnt/wsl/podman-sockets/%[1]s/podman-user.sock
`
const bindMountFsTab = `/run/user/1000/podman/podman.sock /mnt/wsl/podman-sockets/%s/podman-user.sock none noauto,user,bind,defaults 0 0
`
const (
defaultTargetWants = "default.target.wants"
userSystemdPath = "/home/%[1]s/.config/systemd/user"
sysSystemdPath = "/etc/systemd/system"
userSystemdWants = userSystemdPath + "/" + defaultTargetWants
sysSystemdWants = sysSystemdPath + "/" + defaultTargetWants
bindUnitFileName = "podman-mnt-bindings.service"
bindUserUnitPath = userSystemdPath + "/" + bindUnitFileName
bindUserUnitWant = userSystemdWants + "/" + bindUnitFileName
bindSysUnitPath = sysSystemdPath + "/" + bindUnitFileName
bindSysUnitWant = sysSystemdWants + "/" + bindUnitFileName
podmanSocketDropin = "podman.socket.d"
podmanSocketDropinPath = sysSystemdPath + "/" + podmanSocketDropin
)
const configBindServices = "mkdir -p " + userSystemdWants + " " + sysSystemdWants + " " + podmanSocketDropinPath + "\n" +
"ln -fs " + bindUserUnitPath + " " + bindUserUnitWant + "\n" +
"ln -fs " + bindSysUnitPath + " " + bindSysUnitWant + "\n"
const overrideSocketGroup = `
[Socket]
SocketMode=0660
SocketGroup=wheel
`
const proxyConfigSetup = `#!/bin/bash
SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf
ENVD_CONF=/etc/environment.d/default-env.conf
PROFILE_CONF=/etc/profile.d/default-env.sh
IFS="|"
read proxies
mkdir -p /etc/profile.d /etc/environment.d /etc/systemd/system.conf.d/
rm -f $SYSTEMD_CONF
for proxy in $proxies; do
output+="$proxy "
done
echo "[Manager]" >> $SYSTEMD_CONF
echo -ne "DefaultEnvironment=" >> $SYSTEMD_CONF
echo $output >> $SYSTEMD_CONF
rm -f $ENVD_CONF
for proxy in $proxies; do
echo "$proxy" >> $ENVD_CONF
done
rm -f $PROFILE_CONF
for proxy in $proxies; do
echo "export $proxy" >> $PROFILE_CONF
done
`
const proxyConfigAttempt = `if [ -f /usr/local/bin/proxyinit ]; \
then /usr/local/bin/proxyinit; \
else exit 42; \
fi`
const clearProxySettings = `rm -f /etc/systemd/system.conf.d/default-env.conf \
/etc/environment.d/default-env.conf \
/etc/profile.d/default-env.sh`
const wslInstallError = `Could not %s. See previous output for any potential failure details.
If you can not resolve the issue, and rerunning fails, try the "wsl --install" process
outlined in the following article:
http://docs.microsoft.com/en-us/windows/wsl/install
`
const wslKernelError = `Could not %s. See previous output for any potential failure details.
If you can not resolve the issue, try rerunning the "podman machine init command". If that fails
try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails,
try following the steps outlined in the following article:
http://docs.microsoft.com/en-us/windows/wsl/install
`
const wslInstallKernel = "install the WSL Kernel"
const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows
Either update to Build 19041 (or later), or perform the manual installation steps
outlined in the following article:
http://docs.microsoft.com/en-us/windows/wsl/install\
`
const (
gvProxy = "gvproxy.exe"
winSShProxy = "win-sshproxy.exe"
pipePrefix = "npipe:////./pipe/"
globalPipe = "docker_engine"
userModeDist = "podman-net-usermode"
rootfulSock = "/run/podman/podman.sock"
rootlessSock = "/run/user/1000/podman/podman.sock"
)

View File

@ -1,10 +1,9 @@
//go:build windows
package wsl
import (
"errors"
"fmt"
"github.com/sirupsen/logrus"
"io"
"net/http"
"net/url"
@ -27,8 +26,10 @@ type FedoraDownload struct {
machine.Download
}
// NewFedoraDownloader
// deprecated
func NewFedoraDownloader(vmType define.VMType, vmName, releaseStream string) (machine.DistributionDownload, error) {
downloadURL, version, arch, size, err := getFedoraDownload()
downloadURL, version, arch, size, err := GetFedoraDownloadForWSL()
if err != nil {
return nil, err
}
@ -82,7 +83,7 @@ func (f FedoraDownload) CleanCache() error {
return machine.RemoveImageAfterExpire(f.CacheDir, expire)
}
func getFedoraDownload() (*url.URL, string, string, int64, error) {
func GetFedoraDownloadForWSL() (*url.URL, string, string, int64, error) {
var releaseURL string
arch := machine.DetermineMachineArch()
switch arch {
@ -118,11 +119,14 @@ func getFedoraDownload() (*url.URL, string, string, int64, error) {
return nil, "", "", -1, fmt.Errorf("get request failed: %s: %w", verURL.String(), err)
}
defer resp.Body.Close()
bytes, err := io.ReadAll(&io.LimitedReader{R: resp.Body, N: 1024})
defer func() {
if err := resp.Body.Close(); err != nil {
logrus.Errorf("error closing http boddy: %q", err)
}
}()
b, err := io.ReadAll(&io.LimitedReader{R: resp.Body, N: 1024})
if err != nil {
return nil, "", "", -1, fmt.Errorf("failed reading: %s: %w", verURL.String(), err)
}
return downloadURL, strings.TrimSpace(string(bytes)), arch, contentLen, nil
return downloadURL, strings.TrimSpace(string(b)), arch, contentLen, nil
}

File diff suppressed because it is too large Load Diff

362
pkg/machine/wsl/stubber.go Normal file
View File

@ -0,0 +1,362 @@
//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"
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)
// TODO
// USB opts are unsupported in WSL. Need to account for that here
// or up the stack
// if len(opts.USBs) > 0 {
// return nil, fmt.Errorf("USB host passthrough is not supported for WSL machines")
// }
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) GetHyperVisorVMs() ([]string, error) {
vms, err := getAllWSLDistros(false)
if err != nil {
return nil, err
}
wslVMs := make([]string, 0)
for name := range vms {
wslVMs = append(wslVMs, name)
}
return wslVMs, nil
}
func (w WSLStubber) MountType() vmconfigs.VolumeMountType {
//TODO implement me
panic("implement me")
}
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("wsl", "--unregister", 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()
// TODO the check for running when setting rootful is something I have not
// seen in the other distributions. I wonder if this is true everywhere or just
// with WSL?
// TODO maybe the "rule" for set is that it must be done when the machine is
// stopped?
// if opts.Rootful != nil && v.Rootful != *opts.Rootful {
// err := v.setRootful(*opts.Rootful)
// if err != nil {
// setErrors = append(setErrors, fmt.Errorf("setting rootful option: %w", err))
// } else {
// if v.isRunning() {
// logrus.Warn("restart is necessary for rootful change to go into effect")
// }
// v.Rootful = *opts.Rootful
// }
// }
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")
}
// TODO USB still needs to be plumbed for all providers
// if USBs != nil {
// setErrors = append(setErrors, errors.New("changing USBs not supported for WSL machines"))
// }
if opts.DiskSize != nil {
return errors.New("changing disk size not supported for WSL machines")
}
// TODO This needs to be plumbed in for set as well
//if opts.UserModeNetworking != nil && *opts.UserModeNetworking != v.UserModeNetworking {
// update := true
//
// if v.isRunning() {
// update = false
// setErrors = append(setErrors, fmt.Errorf("user-mode networking can only be changed when the machine is not running"))
// } else {
// dist := toDist(v.Name)
// if err := changeDistUserModeNetworking(dist, v.RemoteUsername, v.ImagePath, *opts.UserModeNetworking); err != nil {
// update = false
// setErrors = append(setErrors, err)
// }
// }
//
// if update {
// v.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) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
if mc.WSLHypervisor.UserModeNetworking {
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, false)
}
return nil
}
func (w WSLStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func() error, error) {
useProxy := setupWslProxyEnv()
// TODO Quiet is hard set to false: follow up
if err := configureProxy(mc.Name, useProxy, false); err != nil {
return nil, nil, err
}
// 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(mc.Name, "/root/bootstrap")
if err != nil {
err = fmt.Errorf("the WSL bootstrap script failed: %w", err)
}
// TODO we dont show this for any other provider. perhaps we should ? and if
// so, we need to move it up the stack
//if !v.Rootful && !opts.NoInfo {
// fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
// fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
// fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
//
// suffix := ""
// if name != machine.DefaultMachineName {
// suffix = " " + name
// }
// fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
//}
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
)
// by this time, state has been verified to be running and a request
// to stop is fair game
mc.Lock()
defer mc.Unlock()
// 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("wsl", "-u", "root", "-d", mc.Name, "sh")
cmd.Stdin = strings.NewReader(waitTerm)
if err = cmd.Start(); err != nil {
return fmt.Errorf("executing wait command: %w", err)
}
exitCmd := exec.Command("wsl", "-u", "root", "-d", mc.Name, "/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(mc.Name)
}
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()
}

View File

@ -5,6 +5,7 @@ package wsl
import (
"errors"
"fmt"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"os"
"os/exec"
"path/filepath"
@ -69,8 +70,8 @@ func verifyWSLUserModeCompat() error {
prefix)
}
func (v *MachineVM) startUserModeNetworking() error {
if !v.UserModeNetworking {
func startUserModeNetworking(mc *vmconfigs.MachineConfig) error {
if !mc.WSLHypervisor.UserModeNetworking {
return nil
}
@ -79,7 +80,7 @@ func (v *MachineVM) startUserModeNetworking() error {
return fmt.Errorf("could not locate %s, which is necessary for user-mode networking, please reinstall", gvProxy)
}
flock, err := v.obtainUserModeNetLock()
flock, err := obtainUserModeNetLock()
if err != nil {
return err
}
@ -93,17 +94,17 @@ func (v *MachineVM) startUserModeNetworking() error {
// Start or reuse
if !running {
if err := v.launchUserModeNetDist(exe); err != nil {
if err := launchUserModeNetDist(exe); err != nil {
return err
}
}
if err := createUserModeResolvConf(toDist(v.Name)); err != nil {
if err := createUserModeResolvConf(mc.Name); err != nil {
return err
}
// Register in-use
err = v.addUserModeNetEntry()
err = addUserModeNetEntry(mc)
if err != nil {
return err
}
@ -111,23 +112,23 @@ func (v *MachineVM) startUserModeNetworking() error {
return nil
}
func (v *MachineVM) stopUserModeNetworking(dist string) error {
if !v.UserModeNetworking {
func stopUserModeNetworking(mc *vmconfigs.MachineConfig) error {
if !mc.WSLHypervisor.UserModeNetworking {
return nil
}
flock, err := v.obtainUserModeNetLock()
flock, err := obtainUserModeNetLock()
if err != nil {
return err
}
defer flock.unlock()
err = v.removeUserModeNetEntry()
err = removeUserModeNetEntry(mc.Name)
if err != nil {
return err
}
count, err := v.cleanupAndCountNetEntries()
count, err := cleanupAndCountNetEntries()
if err != nil {
return err
}
@ -159,7 +160,7 @@ func isGvProxyVMRunning() bool {
return wslInvoke(userModeDist, "bash", "-c", "ps -eo args | grep -q -m1 ^/usr/local/bin/vm || exit 42") == nil
}
func (v *MachineVM) launchUserModeNetDist(exeFile string) error {
func launchUserModeNetDist(exeFile string) error {
fmt.Println("Starting user-mode networking...")
exe, err := specgen.ConvertWinMountPath(exeFile)
@ -220,7 +221,7 @@ func createUserModeResolvConf(dist string) error {
return err
}
func (v *MachineVM) getUserModeNetDir() (string, error) {
func getUserModeNetDir() (string, error) {
vmDataDir, err := machine.GetDataDir(vmtype)
if err != nil {
return "", err
@ -234,8 +235,8 @@ func (v *MachineVM) getUserModeNetDir() (string, error) {
return dir, nil
}
func (v *MachineVM) getUserModeNetEntriesDir() (string, error) {
netDir, err := v.getUserModeNetDir()
func getUserModeNetEntriesDir() (string, error) {
netDir, err := getUserModeNetDir()
if err != nil {
return "", err
}
@ -248,13 +249,13 @@ func (v *MachineVM) getUserModeNetEntriesDir() (string, error) {
return dir, nil
}
func (v *MachineVM) addUserModeNetEntry() error {
entriesDir, err := v.getUserModeNetEntriesDir()
func addUserModeNetEntry(mc *vmconfigs.MachineConfig) error {
entriesDir, err := getUserModeNetEntriesDir()
if err != nil {
return err
}
path := filepath.Join(entriesDir, toDist(v.Name))
path := filepath.Join(entriesDir, mc.Name)
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("could not add user-mode networking registration: %w", err)
@ -263,18 +264,18 @@ func (v *MachineVM) addUserModeNetEntry() error {
return nil
}
func (v *MachineVM) removeUserModeNetEntry() error {
entriesDir, err := v.getUserModeNetEntriesDir()
func removeUserModeNetEntry(name string) error {
entriesDir, err := getUserModeNetEntriesDir()
if err != nil {
return err
}
path := filepath.Join(entriesDir, toDist(v.Name))
path := filepath.Join(entriesDir, name)
return os.Remove(path)
}
func (v *MachineVM) cleanupAndCountNetEntries() (uint, error) {
entriesDir, err := v.getUserModeNetEntriesDir()
func cleanupAndCountNetEntries() (uint, error) {
entriesDir, err := getUserModeNetEntriesDir()
if err != nil {
return 0, err
}
@ -302,8 +303,8 @@ func (v *MachineVM) cleanupAndCountNetEntries() (uint, error) {
return count, nil
}
func (v *MachineVM) obtainUserModeNetLock() (*fileLock, error) {
dir, err := v.getUserModeNetDir()
func obtainUserModeNetLock() (*fileLock, error) {
dir, err := getUserModeNetDir()
if err != nil {
return nil, err