summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2021-12-26 13:26:07 +0100
committerGitHub <noreply@github.com>2021-12-26 13:26:07 +0100
commite06631d6c22f4d5b7a62f70ccdf623379a9d5fe7 (patch)
tree54fe3a08b58b9129f87e51cd1b8fcd938f582777
parent73a54ea54d0a1b4ccaa2a0e23c678e5b7c1d5c37 (diff)
parent803defbe509af1902a1fdc2ed7f41b49ebd241f6 (diff)
downloadpodman-e06631d6c22f4d5b7a62f70ccdf623379a9d5fe7.tar.gz
podman-e06631d6c22f4d5b7a62f70ccdf623379a9d5fe7.tar.bz2
podman-e06631d6c22f4d5b7a62f70ccdf623379a9d5fe7.zip
Merge pull request #12503 from n1hility/wsl-machine
Introduce Windows WSL implementation of podman machine
-rw-r--r--cmd/podman/machine/init.go48
-rw-r--r--cmd/podman/machine/list.go23
-rw-r--r--cmd/podman/machine/machine.go6
-rw-r--r--cmd/podman/machine/machine_unsupported.go2
-rw-r--r--cmd/podman/machine/platform.go12
-rw-r--r--cmd/podman/machine/platform_windows.go10
-rw-r--r--cmd/podman/machine/rm.go15
-rw-r--r--cmd/podman/machine/ssh.go29
-rw-r--r--cmd/podman/machine/start.go22
-rw-r--r--cmd/podman/machine/stop.go15
-rw-r--r--go.mod4
-rw-r--r--go.sum4
-rw-r--r--pkg/machine/config.go16
-rw-r--r--pkg/machine/connection.go3
-rw-r--r--pkg/machine/fcos.go29
-rw-r--r--pkg/machine/fedora.go122
-rw-r--r--pkg/machine/ignition.go2
-rw-r--r--pkg/machine/ignition_schema.go2
-rw-r--r--pkg/machine/ignition_windows.go7
-rw-r--r--pkg/machine/keys.go47
-rw-r--r--pkg/machine/machine_unsupported.go2
-rw-r--r--pkg/machine/pull.go96
-rw-r--r--pkg/machine/qemu/config.go4
-rw-r--r--pkg/machine/qemu/machine.go72
-rw-r--r--pkg/machine/qemu/machine_unsupported.go2
-rw-r--r--pkg/machine/wsl/machine.go1119
-rw-r--r--pkg/machine/wsl/machine_unsupported.go3
-rw-r--r--pkg/machine/wsl/util_windows.go338
-rw-r--r--vendor/github.com/containers/common/pkg/config/config.go2
-rw-r--r--vendor/github.com/containers/common/pkg/config/containers.conf5
-rw-r--r--vendor/github.com/containers/common/pkg/config/default.go3
-rw-r--r--vendor/github.com/containers/common/pkg/config/default_linux.go11
-rw-r--r--vendor/github.com/containers/common/pkg/config/default_unsupported.go13
-rw-r--r--vendor/github.com/containers/common/pkg/config/default_windows.go28
-rw-r--r--vendor/modules.txt4
35 files changed, 1941 insertions, 179 deletions
diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go
index bee6844df..14e87c201 100644
--- a/cmd/podman/machine/init.go
+++ b/cmd/podman/machine/init.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -8,7 +8,6 @@ import (
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine"
- "github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -38,6 +37,8 @@ func init() {
})
flags := initCmd.Flags()
cfg := registry.PodmanConfig()
+ initOpts.Username = cfg.Config.Machine.User
+
cpusFlagName := "cpus"
flags.Uint64Var(
&initOpts.CPUS,
@@ -76,6 +77,13 @@ func init() {
flags.StringVar(&initOpts.TimeZone, timezoneFlagName, defaultTz, "Set timezone")
_ = initCmd.RegisterFlagCompletionFunc(timezoneFlagName, completion.AutocompleteDefault)
+ flags.BoolVar(
+ &initOpts.ReExec,
+ "reexec", false,
+ "process was rexeced",
+ )
+ flags.MarkHidden("reexec")
+
ImagePathFlagName := "image-path"
flags.StringVar(&initOpts.ImagePath, ImagePathFlagName, cfg.Machine.Image, "Path to qcow image")
_ = initCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault)
@@ -88,33 +96,47 @@ func init() {
// TODO should we allow for a users to append to the qemu cmdline?
func initMachine(cmd *cobra.Command, args []string) error {
var (
- vm machine.VM
- vmType string
- err error
+ vm machine.VM
+ err error
)
+
+ provider := getSystemDefaultProvider()
initOpts.Name = defaultMachineName
if len(args) > 0 {
initOpts.Name = args[0]
}
- switch vmType {
- default: // qemu is the default
- if _, err := qemu.LoadVMByName(initOpts.Name); err == nil {
- return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name)
- }
- vm, err = qemu.NewMachine(initOpts)
+ if _, err := provider.LoadVMByName(initOpts.Name); err == nil {
+ return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name)
}
+
+ vm, err = provider.NewMachine(initOpts)
if err != nil {
return err
}
- err = vm.Init(initOpts)
- if err != nil {
+
+ if finished, err := vm.Init(initOpts); err != nil || !finished {
+ // Finished = true, err = nil - Success! Log a message with further instructions
+ // Finished = false, err = nil - The installation is partially complete and podman should
+ // exit gracefully with no error and no success message.
+ // Examples:
+ // - a user has chosen to perform their own reboot
+ // - reexec for limited admin operations, returning to parent
+ // Finished = *, err != nil - Exit with an error message
+
return err
}
+ fmt.Println("Machine init complete")
if now {
err = vm.Start(initOpts.Name, machine.StartOptions{})
if err == nil {
fmt.Printf("Machine %q started successfully\n", initOpts.Name)
}
+ } else {
+ extra := ""
+ if initOpts.Name != defaultMachineName {
+ extra = " " + initOpts.Name
+ }
+ fmt.Printf("To start your machine run:\n\n\tpodman machine start%s\n\n", extra)
}
return err
}
diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go
index 774ab4fd0..858d87401 100644
--- a/cmd/podman/machine/list.go
+++ b/cmd/podman/machine/list.go
@@ -1,4 +1,5 @@
-// +build amd64,!windows arm64,!windows
+//go:build amd64 || arm64
+// +build amd64 arm64
package machine
@@ -16,7 +17,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/cmd/podman/validate"
"github.com/containers/podman/v3/pkg/machine"
- "github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -69,9 +69,14 @@ func init() {
}
func list(cmd *cobra.Command, args []string) error {
- var opts machine.ListOptions
- // We only have qemu VM's for now
- listResponse, err := qemu.List(opts)
+ var (
+ opts machine.ListOptions
+ listResponse []*machine.ListResponse
+ err error
+ )
+
+ provider := getSystemDefaultProvider()
+ listResponse, err = provider.List(opts)
if err != nil {
return errors.Wrap(err, "error listing vms")
}
@@ -182,8 +187,8 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*machineReporter, error) {
response.Stream = streamName(vm.Stream)
response.VMType = vm.VMType
response.CPUs = vm.CPUs
- response.Memory = strUint(vm.Memory * units.MiB)
- response.DiskSize = strUint(vm.DiskSize * units.GiB)
+ response.Memory = strUint(vm.Memory)
+ response.DiskSize = strUint(vm.DiskSize)
machineResponses = append(machineResponses, response)
}
@@ -214,8 +219,8 @@ func toHumanFormat(vms []*machine.ListResponse) ([]*machineReporter, error) {
response.Created = units.HumanDuration(time.Since(vm.CreatedAt)) + " ago"
response.VMType = vm.VMType
response.CPUs = vm.CPUs
- response.Memory = units.HumanSize(float64(vm.Memory) * units.MiB)
- response.DiskSize = units.HumanSize(float64(vm.DiskSize) * units.GiB)
+ response.Memory = units.HumanSize(float64(vm.Memory))
+ response.DiskSize = units.HumanSize(float64(vm.DiskSize))
humanResponses = append(humanResponses, response)
}
diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go
index 8ff9055f0..22ffbbee7 100644
--- a/cmd/podman/machine/machine.go
+++ b/cmd/podman/machine/machine.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -8,7 +8,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/cmd/podman/validate"
"github.com/containers/podman/v3/pkg/machine"
- "github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/spf13/cobra"
)
@@ -51,7 +50,8 @@ func autocompleteMachine(cmd *cobra.Command, args []string, toComplete string) (
func getMachines(toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
- machines, err := qemu.List(machine.ListOptions{})
+ provider := getSystemDefaultProvider()
+ machines, err := provider.List(machine.ListOptions{})
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
diff --git a/cmd/podman/machine/machine_unsupported.go b/cmd/podman/machine/machine_unsupported.go
index f8392694a..2f4189446 100644
--- a/cmd/podman/machine/machine_unsupported.go
+++ b/cmd/podman/machine/machine_unsupported.go
@@ -1,4 +1,4 @@
-// +build !amd64 amd64,windows
+// +build !amd64,!arm64
package machine
diff --git a/cmd/podman/machine/platform.go b/cmd/podman/machine/platform.go
new file mode 100644
index 000000000..fc3186205
--- /dev/null
+++ b/cmd/podman/machine/platform.go
@@ -0,0 +1,12 @@
+// +build amd64,!windows arm64,!windows
+
+package machine
+
+import (
+ "github.com/containers/podman/v3/pkg/machine"
+ "github.com/containers/podman/v3/pkg/machine/qemu"
+)
+
+func getSystemDefaultProvider() machine.Provider {
+ return qemu.GetQemuProvider()
+}
diff --git a/cmd/podman/machine/platform_windows.go b/cmd/podman/machine/platform_windows.go
new file mode 100644
index 000000000..a4a35e712
--- /dev/null
+++ b/cmd/podman/machine/platform_windows.go
@@ -0,0 +1,10 @@
+package machine
+
+import (
+ "github.com/containers/podman/v3/pkg/machine"
+ "github.com/containers/podman/v3/pkg/machine/wsl"
+)
+
+func getSystemDefaultProvider() machine.Provider {
+ return wsl.GetWSLProvider()
+}
diff --git a/cmd/podman/machine/rm.go b/cmd/podman/machine/rm.go
index c17399c78..c58e74a42 100644
--- a/cmd/podman/machine/rm.go
+++ b/cmd/podman/machine/rm.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -10,7 +10,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine"
- "github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/spf13/cobra"
)
@@ -52,18 +51,16 @@ func init() {
func rm(cmd *cobra.Command, args []string) error {
var (
- err error
- vm machine.VM
- vmType string
+ err error
+ vm machine.VM
)
vmName := defaultMachineName
if len(args) > 0 && len(args[0]) > 0 {
vmName = args[0]
}
- switch vmType {
- default:
- vm, err = qemu.LoadVMByName(vmName)
- }
+
+ provider := getSystemDefaultProvider()
+ vm, err = provider.LoadVMByName(vmName)
if err != nil {
return err
}
diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go
index da0a09338..5ef34afc6 100644
--- a/cmd/podman/machine/ssh.go
+++ b/cmd/podman/machine/ssh.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -9,7 +9,6 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine"
- "github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -47,27 +46,24 @@ func ssh(cmd *cobra.Command, args []string) error {
err error
validVM bool
vm machine.VM
- vmType string
)
// Set the VM to default
vmName := defaultMachineName
+ provider := getSystemDefaultProvider()
// If len is greater than 0, it means we may have been
// provided the VM name. If so, we check. The VM name,
// if provided, must be in args[0].
if len(args) > 0 {
- switch vmType {
- default:
- validVM, err = qemu.IsValidVMName(args[0])
- if err != nil {
- return err
- }
- if validVM {
- vmName = args[0]
- } else {
- sshOpts.Args = append(sshOpts.Args, args[0])
- }
+ validVM, err = provider.IsValidVMName(args[0])
+ if err != nil {
+ return err
+ }
+ if validVM {
+ vmName = args[0]
+ } else {
+ sshOpts.Args = append(sshOpts.Args, args[0])
}
}
@@ -88,10 +84,7 @@ func ssh(cmd *cobra.Command, args []string) error {
}
}
- switch vmType {
- default:
- vm, err = qemu.LoadVMByName(vmName)
- }
+ vm, err = provider.LoadVMByName(vmName)
if err != nil {
return errors.Wrapf(err, "vm %s not found", vmName)
}
diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go
index 4ae31e6de..9c9c24f64 100644
--- a/cmd/podman/machine/start.go
+++ b/cmd/podman/machine/start.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -7,7 +7,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine"
- "github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -33,30 +32,31 @@ func init() {
func start(cmd *cobra.Command, args []string) error {
var (
- err error
- vm machine.VM
- vmType string
+ err error
+ vm machine.VM
)
vmName := defaultMachineName
if len(args) > 0 && len(args[0]) > 0 {
vmName = args[0]
}
- // We only have qemu VM's for now
- active, activeName, err := qemu.CheckActiveVM()
+ provider := getSystemDefaultProvider()
+ vm, err = provider.LoadVMByName(vmName)
if err != nil {
return err
}
+
+ active, activeName, cerr := provider.CheckExclusiveActiveVM()
+ if cerr != nil {
+ return cerr
+ }
if active {
if vmName == activeName {
return errors.Wrapf(machine.ErrVMAlreadyRunning, "cannot start VM %s", vmName)
}
return errors.Wrapf(machine.ErrMultipleActiveVM, "cannot start VM %s. VM %s is currently running", vmName, activeName)
}
- switch vmType {
- default:
- vm, err = qemu.LoadVMByName(vmName)
- }
+ vm, err = provider.LoadVMByName(vmName)
if err != nil {
return err
}
diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go
index 75666f734..17969298b 100644
--- a/cmd/podman/machine/stop.go
+++ b/cmd/podman/machine/stop.go
@@ -1,4 +1,5 @@
-// +build amd64,!windows arm64,!windows
+//go:build amd64 || arm64
+// +build amd64 arm64
package machine
@@ -7,7 +8,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine"
- "github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/spf13/cobra"
)
@@ -33,18 +33,15 @@ func init() {
// TODO Name shouldn't be required, need to create a default vm
func stop(cmd *cobra.Command, args []string) error {
var (
- err error
- vm machine.VM
- vmType string
+ err error
+ vm machine.VM
)
vmName := defaultMachineName
if len(args) > 0 && len(args[0]) > 0 {
vmName = args[0]
}
- switch vmType {
- default:
- vm, err = qemu.LoadVMByName(vmName)
- }
+ provider := getSystemDefaultProvider()
+ vm, err = provider.LoadVMByName(vmName)
if err != nil {
return err
}
diff --git a/go.mod b/go.mod
index 2a8902a1a..a432c2d5e 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
github.com/containernetworking/cni v1.0.1
github.com/containernetworking/plugins v1.0.1
github.com/containers/buildah v1.23.1
- github.com/containers/common v0.46.1-0.20211205182721-515a2805e7b9
+ github.com/containers/common v0.46.1-0.20211209220542-24f363480347
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.17.1-0.20211207161909-6f3c8453e1a7
github.com/containers/ocicrypt v1.1.2
@@ -60,12 +60,14 @@ require (
github.com/stretchr/testify v1.7.0
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
github.com/uber/jaeger-client-go v2.30.0+incompatible
+ github.com/ulikunitz/xz v0.5.10
github.com/vbauerster/mpb/v6 v6.0.4
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d
+ golang.org/x/text v0.3.7
google.golang.org/protobuf v1.27.1
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
diff --git a/go.sum b/go.sum
index 0597ab5c0..cc0c9e0e0 100644
--- a/go.sum
+++ b/go.sum
@@ -283,8 +283,8 @@ github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNB
github.com/containers/buildah v1.23.1 h1:Tpc9DsRuU+0Oofewpxb6OJVNQjCu7yloN/obUqzfDTY=
github.com/containers/buildah v1.23.1/go.mod h1:4WnrN0yrA7ab0ppgunixu2WM1rlD2rG8QLJAKbEkZlQ=
github.com/containers/common v0.44.2/go.mod h1:7sdP4vmI5Bm6FPFxb3lvAh1Iktb6tiO1MzjUzhxdoGo=
-github.com/containers/common v0.46.1-0.20211205182721-515a2805e7b9 h1:BoPxjWIPX+cn3CGNxd1FC10jcq9TBMk1uvGQEWTXWto=
-github.com/containers/common v0.46.1-0.20211205182721-515a2805e7b9/go.mod h1:cxAKmvKoYBl/iLZ1YD/SKnJF7wPR9H6xM/Hu75ZN/oA=
+github.com/containers/common v0.46.1-0.20211209220542-24f363480347 h1:6CS7RroQLJu/SgUJXGZ3bOs2vnh9rxEnxczDcGjStBw=
+github.com/containers/common v0.46.1-0.20211209220542-24f363480347/go.mod h1:SoHWZESBD7dbqIOkvKrIg5D8EuVIQgL6vkOvv0Yebws=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.16.0/go.mod h1:XgTpfAPLRGOd1XYyCU5cISFr777bLmOerCSpt/v7+Q4=
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index e5e701303..4f2947ac0 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -24,6 +24,15 @@ type InitOptions struct {
TimeZone string
URI url.URL
Username string
+ ReExec bool
+}
+
+type Provider interface {
+ NewMachine(opts InitOptions) (VM, error)
+ LoadVMByName(name string) (VM, error)
+ List(opts ListOptions) ([]*ListResponse, error)
+ IsValidVMName(name string) (bool, error)
+ CheckExclusiveActiveVM() (bool, string, error)
}
type RemoteConnectionType string
@@ -49,6 +58,7 @@ type Download struct {
Sha256sum string
URL *url.URL
VMName string
+ Size int64
}
type ListOptions struct{}
@@ -81,7 +91,7 @@ type RemoveOptions struct {
}
type VM interface {
- Init(opts InitOptions) error
+ Init(opts InitOptions) (bool, error)
Remove(name string, opts RemoveOptions) (string, func() error, error)
SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error
@@ -89,7 +99,7 @@ type VM interface {
}
type DistributionDownload interface {
- DownloadImage() error
+ HasUsableCache() (bool, error)
Get() *Download
}
diff --git a/pkg/machine/connection.go b/pkg/machine/connection.go
index ed1093264..d28ffcef1 100644
--- a/pkg/machine/connection.go
+++ b/pkg/machine/connection.go
@@ -1,4 +1,5 @@
-// +build amd64,!windows arm64,!windows
+//go:build amd64 || arm64
+// +build amd64 arm64
package machine
diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go
index 99197ac0e..60ab471ee 100644
--- a/pkg/machine/fcos.go
+++ b/pkg/machine/fcos.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -65,25 +65,6 @@ func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload
return fcd, nil
}
-func (f FcosDownload) getLocalUncompressedName() string {
- uncompressedFilename := filepath.Join(filepath.Dir(f.LocalPath), f.VMName+"_"+f.ImageName)
- return strings.TrimSuffix(uncompressedFilename, ".xz")
-}
-
-func (f FcosDownload) DownloadImage() error {
- // check if the latest image is already present
- ok, err := UpdateAvailable(&f.Download)
- if err != nil {
- return err
- }
- if !ok {
- if err := DownloadVMImage(f.URL, f.LocalPath); err != nil {
- return err
- }
- }
- return Decompress(f.LocalPath, f.getLocalUncompressedName())
-}
-
func (f FcosDownload) Get() *Download {
return &f.Download
}
@@ -95,14 +76,14 @@ type fcosDownloadInfo struct {
Sha256Sum string
}
-func UpdateAvailable(d *Download) (bool, error) {
+func (f FcosDownload) HasUsableCache() (bool, error) {
// check the sha of the local image if it exists
// get the sha of the remote image
// == dont bother to pull
- if _, err := os.Stat(d.LocalPath); os.IsNotExist(err) {
+ if _, err := os.Stat(f.LocalPath); os.IsNotExist(err) {
return false, nil
}
- fd, err := os.Open(d.LocalPath)
+ fd, err := os.Open(f.LocalPath)
if err != nil {
return false, err
}
@@ -115,7 +96,7 @@ func UpdateAvailable(d *Download) (bool, error) {
if err != nil {
return false, err
}
- return sum.Encoded() == d.Sha256sum, nil
+ return sum.Encoded() == f.Sha256sum, nil
}
func getFcosArch() string {
diff --git a/pkg/machine/fedora.go b/pkg/machine/fedora.go
new file mode 100644
index 000000000..cd713dde7
--- /dev/null
+++ b/pkg/machine/fedora.go
@@ -0,0 +1,122 @@
+// +build amd64 arm64
+
+package machine
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "regexp"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ githubURL = "http://github.com/fedora-cloud/docker-brew-fedora/"
+)
+
+type FedoraDownload struct {
+ Download
+}
+
+func NewFedoraDownloader(vmType, vmName, releaseStream string) (DistributionDownload, error) {
+ imageName, downloadURL, size, err := getFedoraDownload(releaseStream)
+ if err != nil {
+ return nil, err
+ }
+
+ dataDir, err := GetDataDir(vmType)
+ if err != nil {
+ return nil, err
+ }
+
+ f := FedoraDownload{
+ Download: Download{
+ Arch: getFcosArch(),
+ Artifact: artifact,
+ Format: Format,
+ ImageName: imageName,
+ LocalPath: filepath.Join(dataDir, imageName),
+ URL: downloadURL,
+ VMName: vmName,
+ Size: size,
+ },
+ }
+ f.Download.LocalUncompressedFile = f.getLocalUncompressedName()
+ return f, nil
+}
+
+func (f FedoraDownload) Get() *Download {
+ return &f.Download
+}
+
+func (f FedoraDownload) HasUsableCache() (bool, error) {
+ info, err := os.Stat(f.LocalPath)
+ if err != nil {
+ return false, nil
+ }
+ return info.Size() == f.Size, nil
+}
+
+func truncRead(url string) ([]byte, error) {
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, err
+ }
+
+ defer func() {
+ if err := resp.Body.Close(); err != nil {
+ logrus.Error(err)
+ }
+ }()
+
+ body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 10*1024*1024))
+ if err != nil {
+ return nil, err
+ }
+
+ _, _ = io.Copy(io.Discard, resp.Body)
+
+ return body, nil
+}
+
+func getFedoraDownload(releaseStream string) (string, *url.URL, int64, error) {
+ dirURL := githubURL + "tree/" + releaseStream + "/" + getFcosArch() + "/"
+ body, err := truncRead(dirURL)
+ if err != nil {
+ return "", nil, -1, err
+ }
+
+ rx, err := regexp.Compile(`fedora[^\"]+xz`)
+ if err != nil {
+ return "", nil, -1, err
+ }
+ file := rx.FindString(string(body))
+ if len(file) <= 0 {
+ return "", nil, -1, fmt.Errorf("could not locate Fedora download at %s", dirURL)
+ }
+
+ rawURL := githubURL + "raw/" + releaseStream + "/" + getFcosArch() + "/"
+ newLocation := rawURL + file
+ downloadURL, err := url.Parse(newLocation)
+ if err != nil {
+ return "", nil, -1, errors.Wrapf(err, "invalid URL generated from discovered Fedora file: %s", newLocation)
+ }
+
+ resp, err := http.Head(newLocation)
+ if err != nil {
+ return "", nil, -1, errors.Wrapf(err, "head request failed: %s", newLocation)
+ }
+ _ = resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return "", nil, -1, fmt.Errorf("head request failed [%d] on download: %s", resp.StatusCode, newLocation)
+ }
+
+ return file, downloadURL, resp.ContentLength, nil
+}
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index 9368cc8ed..139318977 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
diff --git a/pkg/machine/ignition_schema.go b/pkg/machine/ignition_schema.go
index aa4b8e060..8cfb0d04e 100644
--- a/pkg/machine/ignition_schema.go
+++ b/pkg/machine/ignition_schema.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
diff --git a/pkg/machine/ignition_windows.go b/pkg/machine/ignition_windows.go
new file mode 100644
index 000000000..c0de48bd3
--- /dev/null
+++ b/pkg/machine/ignition_windows.go
@@ -0,0 +1,7 @@
+//+build windows
+
+package machine
+
+func getLocalTimeZone() (string, error) {
+ return "", nil
+}
diff --git a/pkg/machine/keys.go b/pkg/machine/keys.go
index 319fc2b4e..711b091f0 100644
--- a/pkg/machine/keys.go
+++ b/pkg/machine/keys.go
@@ -1,13 +1,21 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
import (
+ "errors"
+ "fmt"
"io/ioutil"
+ "os"
"os/exec"
+ "path/filepath"
"strings"
+
+ "github.com/sirupsen/logrus"
)
+var sshCommand = []string{"ssh-keygen", "-N", "", "-t", "ed25519", "-f"}
+
// CreateSSHKeys makes a priv and pub ssh key for interacting
// the a VM.
func CreateSSHKeys(writeLocation string) (string, error) {
@@ -21,7 +29,42 @@ func CreateSSHKeys(writeLocation string) (string, error) {
return strings.TrimSuffix(string(b), "\n"), nil
}
+func CreateSSHKeysPrefix(dir string, file string, passThru bool, skipExisting bool, prefix ...string) (string, error) {
+ location := filepath.Join(dir, file)
+
+ _, e := os.Stat(location)
+ if !skipExisting || errors.Is(e, os.ErrNotExist) {
+ if err := generatekeysPrefix(dir, file, passThru, prefix...); err != nil {
+ return "", err
+ }
+ } else {
+ fmt.Println("Keys already exist, reusing")
+ }
+ b, err := ioutil.ReadFile(filepath.Join(dir, file) + ".pub")
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSuffix(string(b), "\n"), nil
+}
+
// generatekeys creates an ed25519 set of keys
func generatekeys(writeLocation string) error {
- return exec.Command("ssh-keygen", "-N", "", "-t", "ed25519", "-f", writeLocation).Run()
+ args := append(append([]string{}, sshCommand[1:]...), writeLocation)
+ return exec.Command(sshCommand[0], args...).Run()
+}
+
+// generatekeys creates an ed25519 set of keys
+func generatekeysPrefix(dir string, file string, passThru bool, prefix ...string) error {
+ args := append([]string{}, prefix[1:]...)
+ args = append(args, sshCommand...)
+ args = append(args, file)
+ cmd := exec.Command(prefix[0], args...)
+ cmd.Dir = dir
+ if passThru {
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ }
+ logrus.Debugf("Running wsl cmd %v in dir: %s", args, dir)
+ return cmd.Run()
}
diff --git a/pkg/machine/machine_unsupported.go b/pkg/machine/machine_unsupported.go
index 9309d16bc..da1437984 100644
--- a/pkg/machine/machine_unsupported.go
+++ b/pkg/machine/machine_unsupported.go
@@ -1,3 +1,3 @@
-// +build !amd64 amd64,windows
+// +build !amd64,!arm64
package machine
diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go
index 3c8422a30..280b47f96 100644
--- a/pkg/machine/pull.go
+++ b/pkg/machine/pull.go
@@ -1,8 +1,9 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
import (
+ "bufio"
"fmt"
"io"
"io/ioutil"
@@ -17,6 +18,7 @@ import (
"github.com/containers/image/v5/pkg/compression"
"github.com/containers/storage/pkg/archive"
"github.com/sirupsen/logrus"
+ "github.com/ulikunitz/xz"
"github.com/vbauerster/mpb/v6"
"github.com/vbauerster/mpb/v6/decor"
)
@@ -43,7 +45,7 @@ func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload
return nil, err
}
if len(getURL.Scheme) > 0 {
- urlSplit := strings.Split(pullPath, "/")
+ urlSplit := strings.Split(getURL.Path, "/")
imageName = urlSplit[len(urlSplit)-1]
dl.LocalUncompressedFile = filepath.Join(dataDir, imageName)
dl.URL = getURL
@@ -63,39 +65,48 @@ func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload
return gd, nil
}
-func (g GenericDownload) getLocalUncompressedName() string {
+func (d Download) getLocalUncompressedName() string {
var (
extension string
)
switch {
- case strings.HasSuffix(g.LocalPath, ".bz2"):
+ case strings.HasSuffix(d.LocalPath, ".bz2"):
extension = ".bz2"
- case strings.HasSuffix(g.LocalPath, ".gz"):
+ case strings.HasSuffix(d.LocalPath, ".gz"):
extension = ".gz"
- case strings.HasSuffix(g.LocalPath, ".xz"):
+ case strings.HasSuffix(d.LocalPath, ".xz"):
extension = ".xz"
}
- uncompressedFilename := filepath.Join(filepath.Dir(g.LocalUncompressedFile), g.VMName+"_"+g.ImageName)
+ uncompressedFilename := filepath.Join(filepath.Dir(d.LocalPath), d.VMName+"_"+d.ImageName)
return strings.TrimSuffix(uncompressedFilename, extension)
}
-func (g GenericDownload) DownloadImage() error {
+func (g GenericDownload) Get() *Download {
+ return &g.Download
+}
+
+func (g GenericDownload) HasUsableCache() (bool, error) {
// If we have a URL for this "downloader", we now pull it
- if g.URL != nil {
- if err := DownloadVMImage(g.URL, g.LocalPath); err != nil {
+ return g.URL == nil, nil
+}
+
+func DownloadImage(d DistributionDownload) error {
+ // check if the latest image is already present
+ ok, err := d.HasUsableCache()
+ if err != nil {
+ return err
+ }
+ if !ok {
+ if err := DownloadVMImage(d.Get().URL, d.Get().LocalPath); err != nil {
return err
}
}
- return Decompress(g.LocalPath, g.getLocalUncompressedName())
-}
-
-func (g GenericDownload) Get() *Download {
- return &g.Download
+ return Decompress(d.Get().LocalPath, d.Get().getLocalUncompressedName())
}
// DownloadVMImage downloads a VM image from url to given path
// with download status
-func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error {
+func DownloadVMImage(downloadURL *url2.URL, localImagePath string) error {
out, err := os.Create(localImagePath)
if err != nil {
return err
@@ -120,7 +131,7 @@ func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error {
return fmt.Errorf("error downloading VM image %s: %s", downloadURL, resp.Status)
}
size := resp.ContentLength
- urlSplit := strings.Split(downloadURL.String(), "/")
+ urlSplit := strings.Split(downloadURL.Path, "/")
prefix := "Downloading VM image: " + urlSplit[len(urlSplit)-1]
onComplete := prefix + ": done"
@@ -177,24 +188,50 @@ func Decompress(localPath, uncompressedPath string) error {
// Will error out if file without .xz already exists
// Maybe extracting then renameing is a good idea here..
// depends on xz: not pre-installed on mac, so it becomes a brew dependency
-func decompressXZ(src string, output io.Writer) error {
- cmd := exec.Command("xzcat", "-k", src)
- //cmd := exec.Command("xz", "-d", "-k", "-v", src)
- stdOut, err := cmd.StdoutPipe()
- if err != nil {
- return err
+func decompressXZ(src string, output io.WriteCloser) error {
+ var read io.Reader
+ var cmd *exec.Cmd
+ // Prefer xz utils for fastest performance, fallback to go xi2 impl
+ if _, err := exec.LookPath("xzcat"); err == nil {
+ cmd = exec.Command("xzcat", "-k", src)
+ read, err = cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ cmd.Stderr = os.Stderr
+ } else {
+ file, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ // This XZ implementation is reliant on buffering. It is also 3x+ slower than XZ utils.
+ // Consider replacing with a faster implementation (e.g. xi2) if podman machine is
+ // updated with a larger image for the distribution base.
+ buf := bufio.NewReader(file)
+ read, err = xz.NewReader(buf)
+ if err != nil {
+ return err
+ }
}
- //cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
+
+ done := make(chan bool)
go func() {
- if _, err := io.Copy(output, stdOut); err != nil {
+ if _, err := io.Copy(output, read); err != nil {
logrus.Error(err)
}
+ output.Close()
+ done <- true
}()
- return cmd.Run()
+
+ if cmd != nil {
+ return cmd.Run()
+ }
+ <-done
+ return nil
}
-func decompressEverythingElse(src string, output io.Writer) error {
+func decompressEverythingElse(src string, output io.WriteCloser) error {
f, err := os.Open(src)
if err != nil {
return err
@@ -207,6 +244,9 @@ func decompressEverythingElse(src string, output io.Writer) error {
if err := uncompressStream.Close(); err != nil {
logrus.Error(err)
}
+ if err := output.Close(); err != nil {
+ logrus.Error(err)
+ }
}()
_, err = io.Copy(output, uncompressStream)
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index c04773450..8404079a2 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -4,6 +4,8 @@ package qemu
import "time"
+type Provider struct{}
+
type MachineVM struct {
// CPUs to be assigned to the VM
CPUs uint64
@@ -44,6 +46,4 @@ var (
// defaultQMPTimeout is the timeout duration for the
// qmp monitor interactions
defaultQMPTimeout time.Duration = 2 * time.Second
- // defaultRemoteUser describes the ssh username default
- defaultRemoteUser = "core"
)
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 19cd131e1..a80a11573 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -21,18 +21,24 @@ import (
"github.com/containers/podman/v3/utils"
"github.com/containers/storage/pkg/homedir"
"github.com/digitalocean/go-qemu/qmp"
+ "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var (
+ qemuProvider = &Provider{}
// vmtype refers to qemu (vs libvirt, krun, etc)
vmtype = "qemu"
)
+func GetQemuProvider() machine.Provider {
+ return qemuProvider
+}
+
// NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization.
-func NewMachine(opts machine.InitOptions) (machine.VM, error) {
+func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
@@ -44,16 +50,8 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) {
ignitionFile := filepath.Join(vmConfigDir, vm.Name+".ign")
vm.IgnitionFilePath = ignitionFile
- // An image was specified
- if len(opts.ImagePath) > 0 {
- vm.ImagePath = opts.ImagePath
- }
-
- // Assign remote user name. if not provided, use default
+ vm.ImagePath = opts.ImagePath
vm.RemoteUsername = opts.Username
- if len(vm.RemoteUsername) < 1 {
- vm.RemoteUsername = defaultRemoteUser
- }
// Add a random port for ssh
port, err := utils.GetRandomPort()
@@ -106,7 +104,7 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) {
// LoadByName reads a json file that describes a known qemu vm
// and returns a vm instance
-func LoadVMByName(name string) (machine.VM, error) {
+func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
vm := new(MachineVM)
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
@@ -126,7 +124,7 @@ func LoadVMByName(name string) (machine.VM, error) {
// Init writes the json configuration file to the filesystem for
// other verbs (start, stop)
-func (v *MachineVM) Init(opts machine.InitOptions) error {
+func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
var (
key string
)
@@ -135,7 +133,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
// its existence
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
- return err
+ return false, err
}
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
v.IdentityPath = filepath.Join(sshDir, v.Name)
@@ -147,11 +145,11 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
if err != nil {
- return err
+ return false, err
}
v.ImagePath = dd.Get().LocalUncompressedFile
- if err := dd.DownloadImage(); err != nil {
- return err
+ if err := machine.DownloadImage(dd); err != nil {
+ return false, err
}
default:
// The user has provided an alternate image which can be a file path
@@ -159,11 +157,11 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
v.ImageStream = "custom"
g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
if err != nil {
- return err
+ return false, err
}
v.ImagePath = g.Get().LocalUncompressedFile
- if err := g.DownloadImage(); err != nil {
- return err
+ if err := machine.DownloadImage(g); err != nil {
+ return false, err
}
}
// Add arch specific options including image location
@@ -175,12 +173,12 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
if len(opts.IgnitionPath) < 1 {
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
if err := machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
- return err
+ return false, err
}
uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
- return err
+ return false, err
}
} else {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
@@ -188,10 +186,10 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
// Write the JSON file
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
- return err
+ return false, err
}
if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
- return err
+ return false, err
}
// User has provided ignition file so keygen
@@ -199,17 +197,17 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
if len(opts.IgnitionPath) < 1 {
key, err = machine.CreateSSHKeys(v.IdentityPath)
if err != nil {
- return err
+ return false, err
}
}
// Run arch specific things that need to be done
if err := v.prepare(); err != nil {
- return err
+ return false, err
}
originalDiskSize, err := getDiskSize(v.ImagePath)
if err != nil {
- return err
+ return false, err
}
// Resize the disk image to input disk size
// only if the virtualdisk size is less than
@@ -219,7 +217,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
resize.Stdout = os.Stdout
resize.Stderr = os.Stderr
if err := resize.Run(); err != nil {
- return errors.Errorf("error resizing image: %q", err)
+ return false, errors.Errorf("error resizing image: %q", err)
}
}
// If the user provides an ignition file, we need to
@@ -227,9 +225,9 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
if len(opts.IgnitionPath) > 0 {
inputIgnition, err := ioutil.ReadFile(opts.IgnitionPath)
if err != nil {
- return err
+ return false, err
}
- return ioutil.WriteFile(v.IgnitionFilePath, inputIgnition, 0644)
+ return false, ioutil.WriteFile(v.IgnitionFilePath, inputIgnition, 0644)
}
// Write the ignition file
ign := machine.DynamicIgnition{
@@ -239,7 +237,8 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
TimeZone: opts.TimeZone,
WritePath: v.IgnitionFilePath,
}
- return machine.NewIgnitionFile(ign)
+ err = machine.NewIgnitionFile(ign)
+ return err == nil, err
}
// Start executes the qemu command line and forks it
@@ -571,7 +570,7 @@ func getDiskSize(path string) (uint64, error) {
}
// List lists all vm's that use qemu virtualization
-func List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
+func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
return GetVMInfos()
}
@@ -601,8 +600,8 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
listEntry.Stream = vm.ImageStream
listEntry.VMType = "qemu"
listEntry.CPUs = vm.CPUs
- listEntry.Memory = vm.Memory
- listEntry.DiskSize = vm.DiskSize
+ listEntry.Memory = vm.Memory * units.MiB
+ listEntry.DiskSize = vm.DiskSize * units.GiB
fi, err := os.Stat(fullPath)
if err != nil {
return err
@@ -627,7 +626,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
return listed, err
}
-func IsValidVMName(name string) (bool, error) {
+func (p *Provider) IsValidVMName(name string) (bool, error) {
infos, err := GetVMInfos()
if err != nil {
return false, err
@@ -640,8 +639,9 @@ func IsValidVMName(name string) (bool, error) {
return false, nil
}
-// CheckActiveVM checks if there is a VM already running
-func CheckActiveVM() (bool, string, error) {
+// CheckExclusiveActiveVM checks if there is a VM already running
+// that does not allow other VMs to be running
+func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
vms, err := GetVMInfos()
if err != nil {
return false, "", errors.Wrap(err, "error checking VM active")
diff --git a/pkg/machine/qemu/machine_unsupported.go b/pkg/machine/qemu/machine_unsupported.go
index da06ac324..e3ce05e3d 100644
--- a/pkg/machine/qemu/machine_unsupported.go
+++ b/pkg/machine/qemu/machine_unsupported.go
@@ -1,3 +1,3 @@
-// +build !amd64 amd64,windows
+// +build !amd64,!arm64 windows
package qemu
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
new file mode 100644
index 000000000..b4ee79acc
--- /dev/null
+++ b/pkg/machine/wsl/machine.go
@@ -0,0 +1,1119 @@
+//go:build windows
+// +build windows
+
+package wsl
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/podman/v3/pkg/machine"
+ "github.com/containers/podman/v3/utils"
+ "github.com/containers/storage/pkg/homedir"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/text/encoding/unicode"
+ "golang.org/x/text/transform"
+)
+
+var (
+ wslProvider = &Provider{}
+ // vmtype refers to qemu (vs libvirt, krun, etc)
+ vmtype = "wsl"
+)
+
+const (
+ ErrorSuccessRebootInitiated = 1641
+ ErrorSuccessRebootRequired = 3010
+)
+
+// Usermode networking avoids potential nftables compatibility issues between the distro
+// and the WSL Kernel. Additionally it avoids fw rule conflicts between distros, since
+// all instances run under the same Kernel at runtime
+const containersConf = `[containers]
+
+[engine]
+cgroup_manager = "cgroupfs"
+events_logger = "file"
+`
+
+const appendPort = `grep -q Port\ %d /etc/ssh/sshd_config || echo 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/dbus-org.freedesktop.resolve1.service
+ln -fs /dev/null /etc/systemd/system/console-getty.service
+mkdir -p /etc/systemd/system/systemd-sysusers.service.d/
+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 -m -p -t $SYSDPID "$@"
+fi
+`
+
+const waitTerm = sysdpid + `
+if [ ! -z "$SYSDPID" ]; then
+ timeout 60 tail -f /dev/null --pid $SYSDPID
+fi
+`
+
+// 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 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\
+
+`
+
+type Provider struct{}
+
+type MachineVM struct {
+ // IdentityPath is the fq path to the ssh priv key
+ IdentityPath string
+ // IgnitionFilePath is the fq path to the .ign file
+ ImageStream string
+ // ImagePath is the fq path to
+ ImagePath string
+ // Name of the vm
+ Name string
+ // SSH port for user networking
+ Port int
+ // RemoteUsername of the vm user
+ RemoteUsername string
+}
+
+type ExitCodeError struct {
+ code uint
+}
+
+func (e *ExitCodeError) Error() string {
+ return fmt.Sprintf("Process failed with exit code: %d", e.code)
+}
+
+func GetWSLProvider() machine.Provider {
+ return wslProvider
+}
+
+// NewMachine initializes an instance of a virtual machine based on the qemu
+// virtualization.
+func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
+ vm := new(MachineVM)
+ if len(opts.Name) > 0 {
+ vm.Name = opts.Name
+ }
+
+ vm.ImagePath = opts.ImagePath
+ vm.RemoteUsername = opts.Username
+
+ // Add a random port for ssh
+ port, err := utils.GetRandomPort()
+ if err != nil {
+ return nil, err
+ }
+ vm.Port = port
+
+ return vm, nil
+}
+
+// LoadByName reads a json file that describes a known qemu vm
+// and returns a vm instance
+func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
+ vm := new(MachineVM)
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return nil, err
+ }
+ b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json"))
+ if os.IsNotExist(err) {
+ return nil, errors.Wrap(machine.ErrNoSuchVM, name)
+ }
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(b, vm)
+ return vm, err
+}
+
+// Init writes the json configuration file to the filesystem for
+// other verbs (start, stop)
+func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
+ if cont, err := checkAndInstallWSL(opts); !cont {
+ appendOutputIfError(opts.ReExec, err)
+ return cont, err
+ }
+
+ homeDir := homedir.Get()
+ sshDir := filepath.Join(homeDir, ".ssh")
+ v.IdentityPath = filepath.Join(sshDir, v.Name)
+
+ if err := downloadDistro(v, opts); err != nil {
+ return false, err
+ }
+
+ if err := writeJSON(v); err != nil {
+ return false, err
+ }
+
+ if err := setupConnections(v, opts, sshDir); err != nil {
+ return false, err
+ }
+
+ dist, err := provisionWSLDist(v)
+ if err != nil {
+ return false, err
+ }
+
+ fmt.Println("Configuring system...")
+ if err = configureSystem(v, dist); err != nil {
+ return false, err
+ }
+
+ if err = installScripts(dist); err != nil {
+ return false, err
+ }
+
+ if err = createKeys(v, dist, sshDir); err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
+
+func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
+ var (
+ dd machine.DistributionDownload
+ err error
+ )
+
+ if _, e := strconv.Atoi(opts.ImagePath); e == nil {
+ v.ImageStream = opts.ImagePath
+ dd, err = machine.NewFedoraDownloader(vmtype, v.Name, v.ImageStream)
+ } else {
+ v.ImageStream = "custom"
+ dd, err = machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
+ }
+ if err != nil {
+ return err
+ }
+
+ v.ImagePath = dd.Get().LocalUncompressedFile
+ return machine.DownloadImage(dd)
+}
+
+func writeJSON(v *MachineVM) error {
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return err
+ }
+
+ jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
+
+ b, err := json.MarshalIndent(v, "", " ")
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
+ return errors.Wrap(err, "could not write machine json config")
+ }
+
+ return nil
+}
+
+func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error {
+ uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
+ if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
+ return err
+ }
+
+ user := opts.Username
+ uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", withUser("/run/[USER]/1000/podman/podman.sock", user), strconv.Itoa(v.Port), v.RemoteUsername)
+ return machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault)
+}
+
+func provisionWSLDist(v *MachineVM) (string, error) {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return "", err
+ }
+
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ distTarget := filepath.Join(distDir, v.Name)
+ if err := os.MkdirAll(distDir, 0755); err != nil {
+ return "", errors.Wrap(err, "could not create wsldist directory")
+ }
+
+ dist := toDist(v.Name)
+ fmt.Println("Importing operating system into WSL (this may take 5+ minutes on a new WSL install)...")
+ if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath); err != nil {
+ return "", errors.Wrap(err, "WSL import of guest OS failed")
+ }
+
+ fmt.Println("Installing packages (this will take awhile)...")
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "upgrade", "-y"); err != nil {
+ return "", errors.Wrap(err, "package upgrade on guest OS failed")
+ }
+
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install",
+ "podman", "podman-docker", "openssh-server", "procps-ng", "-y"); err != nil {
+ return "", errors.Wrap(err, "package installation on guest OS failed")
+ }
+
+ // Fixes newuidmap
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "reinstall", "shadow-utils", "-y"); err != nil {
+ return "", errors.Wrap(err, "package reinstallation of shadow-utils on guest OS failed")
+ }
+
+ // Windows 11 (NT Version = 10, Build 22000) generates harmless but scary messages on every
+ // operation when mount was not present on the initial start. Force a cycle so that it won't
+ // repeatedly complain.
+ if winVersionAtLeast(10, 0, 22000) {
+ if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil {
+ logrus.Warnf("could not cycle WSL dist: %s", err.Error())
+ }
+ }
+
+ return dist, nil
+}
+
+func createKeys(v *MachineVM, dist string, sshDir string) error {
+ user := v.RemoteUsername
+
+ if err := os.MkdirAll(sshDir, 0700); err != nil {
+ return errors.Wrap(err, "could not create ssh directory")
+ }
+
+ if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil {
+ return errors.Wrap(err, "could not cycle WSL dist")
+ }
+
+ key, err := machine.CreateSSHKeysPrefix(sshDir, v.Name, true, true, "wsl", "-d", dist)
+ if err != nil {
+ return errors.Wrap(err, "could not create ssh keys")
+ }
+
+ if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", "mkdir -p /root/.ssh;"+
+ "cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil {
+ return errors.Wrap(err, "could not create root authorized keys on guest OS")
+ }
+
+ userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+
+ "cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+
+ "chmod 600 /home/[USER]/.ssh/authorized_keys", user)
+ if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", userAuthCmd); err != nil {
+ return errors.Wrapf(err, "could not create '%s' authorized keys on guest OS", v.RemoteUsername)
+ }
+
+ return nil
+}
+
+func configureSystem(v *MachineVM, dist string) error {
+ user := v.RemoteUsername
+ if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil {
+ return errors.Wrap(err, "could not configure SSH port for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", withUser(configServices, user), "-d", dist, "sh"); err != nil {
+ return errors.Wrap(err, "could not configure systemd settings for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", sudoers, "-d", dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil {
+ return errors.Wrap(err, "could not add wheel to sudoers")
+ }
+
+ if err := pipeCmdPassThrough("wsl", overrideSysusers, "-d", dist, "sh", "-c",
+ "cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil {
+ return errors.Wrap(err, "could not generate systemd-sysusers override for guest OS")
+ }
+
+ lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user)
+ if err := pipeCmdPassThrough("wsl", lingerService, "-d", dist, "sh", "-c", lingerCmd); err != nil {
+ return errors.Wrap(err, "could not generate linger service for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", withUser(lingerSetup, user), "-d", dist, "sh"); err != nil {
+ return errors.Wrap(err, "could not configure systemd settomgs for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", containersConf, "-d", dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil {
+ return errors.Wrap(err, "could not create containers.conf for guest OS")
+ }
+
+ return nil
+}
+
+func installScripts(dist string) error {
+ if err := pipeCmdPassThrough("wsl", enterns, "-d", dist, "sh", "-c",
+ "cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil {
+ return errors.Wrap(err, "could not create enterns script for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", profile, "-d", dist, "sh", "-c",
+ "cat > /etc/profile.d/enterns.sh"); err != nil {
+ return errors.Wrap(err, "could not create motd profile script for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", wslmotd, "-d", dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil {
+ return errors.Wrap(err, "could not create a WSL MOTD for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", bootstrap, "-d", dist, "sh", "-c",
+ "cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil {
+ return errors.Wrap(err, "could not create bootstrap script for guest OS")
+ }
+
+ return nil
+}
+
+func checkAndInstallWSL(opts machine.InitOptions) (bool, error) {
+ if isWSLInstalled() {
+ return true, nil
+ }
+
+ admin := hasAdminRights()
+
+ if !isWSLFeatureEnabled() {
+ return false, attemptFeatureInstall(opts, admin)
+ }
+
+ skip := false
+ if !opts.ReExec && !admin {
+ fmt.Println("Launching WSL Kernel Install...")
+ if err := launchElevate(wslInstallKernel); err != nil {
+ return false, err
+ }
+
+ skip = true
+ }
+
+ if !skip {
+ if err := installWslKernel(); err != nil {
+ fmt.Fprintf(os.Stderr, wslKernelError, wslInstallKernel)
+ return false, err
+ }
+
+ if opts.ReExec {
+ return false, nil
+ }
+ }
+
+ return true, nil
+}
+
+func attemptFeatureInstall(opts machine.InitOptions, admin bool) error {
+ if !winVersionAtLeast(10, 0, 18362) {
+ return errors.Errorf("Your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later")
+ } else if !winVersionAtLeast(10, 0, 19041) {
+ fmt.Fprint(os.Stderr, wslOldVersion)
+ return errors.Errorf("WSL can not be automatically installed")
+ }
+
+ message := "WSL is not installed on this system, installing it.\n\n"
+
+ if !admin {
+ message += "Since you are not running as admin, a new window will open and " +
+ "require you to approve administrator privileges.\n\n"
+ }
+
+ message += "NOTE: A system reboot will be required as part of this process. " +
+ "If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command."
+
+ if !opts.ReExec && MessageBox(message, "Podman Machine", false) != 1 {
+ return errors.Errorf("WSL installation aborted")
+ }
+
+ if !opts.ReExec && !admin {
+ return launchElevate("install the Windows WSL Features")
+ }
+
+ return installWsl()
+}
+
+func launchElevate(operation string) error {
+ truncateElevatedOutputFile()
+ err := relaunchElevatedWait()
+ if err != nil {
+ if eerr, ok := err.(*ExitCodeError); ok {
+ if eerr.code == ErrorSuccessRebootRequired {
+ fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
+ return nil
+ }
+ }
+
+ fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
+ dumpOutputFile()
+ fmt.Fprintf(os.Stderr, wslInstallError, operation)
+ }
+ return err
+}
+
+func installWsl() error {
+ log, err := getElevatedOutputFileWrite()
+ if err != nil {
+ return err
+ }
+ defer log.Close()
+ if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
+ "/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) {
+ return errors.Wrap(err, "could not enable WSL Feature")
+ }
+
+ if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
+ "/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) {
+ return errors.Wrap(err, "could not enable Virtual Machine Feature")
+ }
+ log.Close()
+
+ return reboot()
+}
+
+func installWslKernel() error {
+ log, err := getElevatedOutputFileWrite()
+ if err != nil {
+ return err
+ }
+ defer log.Close()
+
+ message := "Installing WSL Kernel Update"
+ fmt.Println(message)
+ fmt.Fprintln(log, message)
+
+ backoff := 500 * time.Millisecond
+ for i := 0; i < 5; i++ {
+ err = runCmdPassThroughTee(log, "wsl", "--update")
+ if err == nil {
+ break
+ }
+ // In case of unusual circumstances (e.g. race with installer actions)
+ // retry a few times
+ message = "An error occured attempting the WSL Kernel update, retrying..."
+ fmt.Println(message)
+ fmt.Fprintln(log, message)
+ time.Sleep(backoff)
+ backoff *= 2
+ }
+
+ if err != nil {
+ return errors.Wrap(err, "could not install WSL Kernel")
+ }
+
+ return nil
+}
+
+func getElevatedOutputFileName() (string, error) {
+ dir, err := homedir.GetDataHome()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(dir, "podman-elevated-output.log"), nil
+}
+
+func dumpOutputFile() {
+ file, err := getElevatedOutputFileRead()
+ if err != nil {
+ logrus.Debug("could not find elevated child output file")
+ return
+ }
+ defer file.Close()
+ _, _ = io.Copy(os.Stdout, file)
+}
+
+func getElevatedOutputFileRead() (*os.File, error) {
+ return getElevatedOutputFile(os.O_RDONLY)
+}
+
+func getElevatedOutputFileWrite() (*os.File, error) {
+ return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND)
+}
+
+func appendOutputIfError(write bool, err error) {
+ if write && err == nil {
+ return
+ }
+
+ if file, check := getElevatedOutputFileWrite(); check == nil {
+ defer file.Close()
+ fmt.Fprintf(file, "Error: %v\n", err)
+ }
+}
+
+func truncateElevatedOutputFile() error {
+ name, err := getElevatedOutputFileName()
+ if err != nil {
+ return err
+ }
+
+ return os.Truncate(name, 0)
+}
+
+func getElevatedOutputFile(mode int) (*os.File, error) {
+ name, err := getElevatedOutputFileName()
+ if err != nil {
+ return nil, err
+ }
+
+ dir, err := homedir.GetDataHome()
+ if err != nil {
+ return nil, err
+ }
+
+ if err = os.MkdirAll(dir, 0755); err != nil {
+ return nil, err
+ }
+
+ return os.OpenFile(name, mode, 0644)
+}
+
+func isMsiError(err error) bool {
+ if err == nil {
+ return false
+ }
+
+ if eerr, ok := err.(*exec.ExitError); ok {
+ switch eerr.ExitCode() {
+ case 0:
+ fallthrough
+ case ErrorSuccessRebootInitiated:
+ fallthrough
+ case ErrorSuccessRebootRequired:
+ return false
+ }
+ }
+
+ return true
+}
+func toDist(name string) string {
+ if !strings.HasPrefix(name, "podman") {
+ name = "podman-" + name
+ }
+ return name
+}
+
+func withUser(s string, user string) string {
+ return strings.ReplaceAll(s, "[USER]", user)
+}
+
+func runCmdPassThrough(name string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+
+ // TODO - Perhaps improve this with a conpty pseudo console so that
+ // dism installer text bars mirror console behavior (redraw)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = io.MultiWriter(os.Stdout, out)
+ cmd.Stderr = io.MultiWriter(os.Stderr, out)
+ return cmd.Run()
+}
+
+func pipeCmdPassThrough(name string, input string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = strings.NewReader(input)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
+ if v.isRunning() {
+ return errors.Errorf("%q is already running", name)
+ }
+
+ fmt.Println("Starting machine...")
+
+ dist := toDist(name)
+
+ err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap")
+ if err != nil {
+ return errors.Wrap(err, "WSL bootstrap script failed")
+ }
+
+ return markStart(name)
+}
+
+func isWSLInstalled() bool {
+ cmd := exec.Command("wsl", "--status")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false
+ }
+ if err = cmd.Start(); err != nil {
+ return false
+ }
+ scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
+ result := true
+ for scanner.Scan() {
+ line := scanner.Text()
+ // Windows 11 does not set an error exit code when a kernel is not avail
+ if strings.Contains(line, "kernel file is not found") {
+ result = false
+ break
+ }
+ }
+ if err := cmd.Wait(); !result || err != nil {
+ return false
+ }
+
+ return true
+}
+
+func isWSLFeatureEnabled() bool {
+ cmd := exec.Command("wsl", "--set-default-version", "2")
+ return cmd.Run() == nil
+}
+
+func isWSLRunning(dist string) (bool, error) {
+ cmd := exec.Command("wsl", "-l", "--running")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false, err
+ }
+ if err = cmd.Start(); err != nil {
+ return false, err
+ }
+ scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
+ result := false
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if len(fields) > 0 && dist == fields[0] {
+ result = true
+ break
+ }
+ }
+
+ _ = cmd.Wait()
+
+ return result, nil
+}
+
+func isSystemdRunning(dist string) (bool, error) {
+ cmd := exec.Command("wsl", "-d", dist, "sh")
+ cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false, err
+ }
+ if err = cmd.Start(); err != nil {
+ return false, err
+ }
+ scanner := bufio.NewScanner(out)
+ result := false
+ if scanner.Scan() {
+ text := scanner.Text()
+ i, err := strconv.Atoi(text)
+ if err == nil && i > 0 {
+ result = true
+ }
+ }
+
+ _ = cmd.Wait()
+
+ return result, nil
+}
+
+func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
+ dist := toDist(v.Name)
+
+ wsl, err := isWSLRunning(dist)
+ if err != nil {
+ return err
+ }
+
+ sysd := false
+ if wsl {
+ sysd, err = isSystemdRunning(dist)
+ if err != nil {
+ return err
+ }
+ }
+
+ if !wsl || !sysd {
+ return errors.Errorf("%q is not running", v.Name)
+ }
+
+ cmd := exec.Command("wsl", "-d", dist, "sh")
+ cmd.Stdin = strings.NewReader(waitTerm)
+ if err = cmd.Start(); err != nil {
+ return errors.Wrap(err, "Error executing wait command")
+ }
+
+ exitCmd := exec.Command("wsl", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
+ if err = exitCmd.Run(); err != nil {
+ return errors.Wrap(err, "Error stopping sysd")
+ }
+
+ if err = cmd.Wait(); err != nil {
+ return err
+ }
+
+ cmd = exec.Command("wsl", "--terminate", dist)
+ if err = cmd.Run(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+//nolint:cyclop
+func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
+ var files []string
+
+ if v.isRunning() {
+ return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
+ }
+
+ // Collect all the files that need to be destroyed
+ if !opts.SaveKeys {
+ files = append(files, v.IdentityPath, v.IdentityPath+".pub")
+ }
+ if !opts.SaveImage {
+ files = append(files, v.ImagePath)
+ }
+
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return "", nil, err
+ }
+ files = append(files, filepath.Join(vmConfigDir, v.Name+".json"))
+
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return "", nil, err
+ }
+ files = append(files, filepath.Join(vmDataDir, "wsldist", v.Name))
+
+ confirmationMessage := "\nThe following files will be deleted:\n\n"
+ for _, msg := range files {
+ confirmationMessage += msg + "\n"
+ }
+
+ confirmationMessage += "\n"
+ return confirmationMessage, func() error {
+ if err := machine.RemoveConnection(v.Name); err != nil {
+ logrus.Error(err)
+ }
+ if err := machine.RemoveConnection(v.Name + "-root"); err != nil {
+ logrus.Error(err)
+ }
+ if err := runCmdPassThrough("wsl", "--unregister", toDist(v.Name)); err != nil {
+ logrus.Error(err)
+ }
+ for _, f := range files {
+ if err := os.RemoveAll(f); err != nil {
+ logrus.Error(err)
+ }
+ }
+ return nil
+ }, nil
+}
+
+func (v *MachineVM) isRunning() bool {
+ dist := toDist(v.Name)
+
+ wsl, err := isWSLRunning(dist)
+ if err != nil {
+ return false
+ }
+
+ sysd := false
+ if wsl {
+ sysd, err = isSystemdRunning(dist)
+
+ if err != nil {
+ return false
+ }
+ }
+
+ return sysd
+}
+
+// SSH opens an interactive SSH session to the vm specified.
+// Added ssh function to VM interface: pkg/machine/config/go : line 58
+func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
+ if !v.isRunning() {
+ return errors.Errorf("vm %q is not running.", v.Name)
+ }
+
+ username := opts.Username
+ if username == "" {
+ username = v.RemoteUsername
+ }
+
+ sshDestination := username + "@localhost"
+ port := strconv.Itoa(v.Port)
+
+ args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile /dev/null", "-o", "StrictHostKeyChecking no"}
+ if len(opts.Args) > 0 {
+ args = append(args, opts.Args...)
+ } else {
+ fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name)
+ }
+
+ cmd := exec.Command("ssh", args...)
+ logrus.Debugf("Executing: ssh %v\n", args)
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Stdin = os.Stdin
+
+ return cmd.Run()
+}
+
+// List lists all vm's that use qemu virtualization
+func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
+ return GetVMInfos()
+}
+
+func GetVMInfos() ([]*machine.ListResponse, error) {
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return nil, err
+ }
+
+ var listed []*machine.ListResponse
+
+ if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error {
+ vm := new(MachineVM)
+ if strings.HasSuffix(info.Name(), ".json") {
+ fullPath := filepath.Join(vmConfigDir, info.Name())
+ b, err := ioutil.ReadFile(fullPath)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(b, vm)
+ if err != nil {
+ return err
+ }
+ listEntry := new(machine.ListResponse)
+
+ listEntry.Name = vm.Name
+ listEntry.Stream = vm.ImageStream
+ listEntry.VMType = "wsl"
+ listEntry.CPUs, _ = getCPUs(vm)
+ listEntry.Memory, _ = getMem(vm)
+ listEntry.DiskSize = getDiskSize(vm)
+ fi, err := os.Stat(fullPath)
+ if err != nil {
+ return err
+ }
+ listEntry.CreatedAt = fi.ModTime()
+ listEntry.LastUp = getLastStart(vm, fi.ModTime())
+ if vm.isRunning() {
+ listEntry.Running = true
+ }
+
+ listed = append(listed, listEntry)
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return listed, err
+}
+
+func getDiskSize(vm *MachineVM) uint64 {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return 0
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ disk := filepath.Join(distDir, vm.Name, "ext4.vhdx")
+ info, err := os.Stat(disk)
+ if err != nil {
+ return 0
+ }
+ return uint64(info.Size())
+}
+
+func markStart(name string) error {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return err
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ start := filepath.Join(distDir, name, "laststart")
+ file, err := os.Create(start)
+ if err != nil {
+ return err
+ }
+ file.Close()
+
+ return nil
+}
+
+func getLastStart(vm *MachineVM, created time.Time) time.Time {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return created
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ start := filepath.Join(distDir, vm.Name, "laststart")
+ info, err := os.Stat(start)
+ if err != nil {
+ return created
+ }
+ return info.ModTime()
+}
+
+func getCPUs(vm *MachineVM) (uint64, error) {
+ dist := toDist(vm.Name)
+ if run, _ := isWSLRunning(dist); !run {
+ return 0, nil
+ }
+ cmd := exec.Command("wsl", "-d", dist, "nproc")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return 0, err
+ }
+ if err = cmd.Start(); err != nil {
+ return 0, err
+ }
+ scanner := bufio.NewScanner(out)
+ var result string
+ for scanner.Scan() {
+ result = scanner.Text()
+ }
+ _ = cmd.Wait()
+
+ ret, err := strconv.Atoi(result)
+ return uint64(ret), err
+}
+
+func getMem(vm *MachineVM) (uint64, error) {
+ dist := toDist(vm.Name)
+ if run, _ := isWSLRunning(dist); !run {
+ return 0, nil
+ }
+ cmd := exec.Command("wsl", "-d", dist, "cat", "/proc/meminfo")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return 0, err
+ }
+ if err = cmd.Start(); err != nil {
+ return 0, err
+ }
+ scanner := bufio.NewScanner(out)
+ var (
+ total, available uint64
+ t, a int
+ )
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if strings.HasPrefix(fields[0], "MemTotal") && len(fields) >= 2 {
+ t, err = strconv.Atoi(fields[1])
+ total = uint64(t) * 1024
+ } else if strings.HasPrefix(fields[0], "MemAvailable") && len(fields) >= 2 {
+ a, err = strconv.Atoi(fields[1])
+ available = uint64(a) * 1024
+ }
+ if err != nil {
+ break
+ }
+ }
+ _ = cmd.Wait()
+
+ return total - available, err
+}
+
+func (p *Provider) IsValidVMName(name string) (bool, error) {
+ infos, err := GetVMInfos()
+ if err != nil {
+ return false, err
+ }
+ for _, vm := range infos {
+ if vm.Name == name {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
+ return false, "", nil
+}
diff --git a/pkg/machine/wsl/machine_unsupported.go b/pkg/machine/wsl/machine_unsupported.go
new file mode 100644
index 000000000..043c5d729
--- /dev/null
+++ b/pkg/machine/wsl/machine_unsupported.go
@@ -0,0 +1,3 @@
+// +build !windows
+
+package wsl
diff --git a/pkg/machine/wsl/util_windows.go b/pkg/machine/wsl/util_windows.go
new file mode 100644
index 000000000..95e4c9894
--- /dev/null
+++ b/pkg/machine/wsl/util_windows.go
@@ -0,0 +1,338 @@
+package wsl
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "unicode/utf16"
+ "unsafe"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/windows"
+ "golang.org/x/sys/windows/registry"
+
+ "github.com/containers/storage/pkg/homedir"
+)
+
+//nolint
+type SHELLEXECUTEINFO struct {
+ cbSize uint32
+ fMask uint32
+ hwnd syscall.Handle
+ lpVerb uintptr
+ lpFile uintptr
+ lpParameters uintptr
+ lpDirectory uintptr
+ nShow int
+ hInstApp syscall.Handle
+ lpIDList uintptr
+ lpClass uintptr
+ hkeyClass syscall.Handle
+ dwHotKey uint32
+ hIconOrMonitor syscall.Handle
+ hProcess syscall.Handle
+}
+
+//nolint
+type Luid struct {
+ lowPart uint32
+ highPart int32
+}
+
+type LuidAndAttributes struct {
+ luid Luid
+ attributes uint32
+}
+
+type TokenPrivileges struct {
+ privilegeCount uint32
+ privileges [1]LuidAndAttributes
+}
+
+//nolint // Cleaner to refer to the official OS constant names, and consistent with syscall
+const (
+ SEE_MASK_NOCLOSEPROCESS = 0x40
+ EWX_FORCEIFHUNG = 0x10
+ EWX_REBOOT = 0x02
+ EWX_RESTARTAPPS = 0x40
+ SHTDN_REASON_MAJOR_APPLICATION = 0x00040000
+ SHTDN_REASON_MINOR_INSTALLATION = 0x00000002
+ SHTDN_REASON_FLAG_PLANNED = 0x80000000
+ TOKEN_ADJUST_PRIVILEGES = 0x0020
+ TOKEN_QUERY = 0x0008
+ SE_PRIVILEGE_ENABLED = 0x00000002
+ SE_ERR_ACCESSDENIED = 0x05
+)
+
+func winVersionAtLeast(major uint, minor uint, build uint) bool {
+ var out [3]uint32
+
+ in := []uint32{uint32(major), uint32(minor), uint32(build)}
+ out[0], out[1], out[2] = windows.RtlGetNtVersionNumbers()
+
+ for i, o := range out {
+ if in[i] > o {
+ return false
+ }
+ if in[i] < o {
+ return true
+ }
+ }
+
+ return true
+}
+
+func hasAdminRights() bool {
+ var sid *windows.SID
+
+ // See: https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/
+ if err := windows.AllocateAndInitializeSid(
+ &windows.SECURITY_NT_AUTHORITY,
+ 2,
+ windows.SECURITY_BUILTIN_DOMAIN_RID,
+ windows.DOMAIN_ALIAS_RID_ADMINS,
+ 0, 0, 0, 0, 0, 0,
+ &sid); err != nil {
+ logrus.Warnf("SID allocation error: %s", err)
+ return false
+ }
+ defer windows.FreeSid(sid)
+
+ // From MS docs:
+ // "If TokenHandle is NULL, CheckTokenMembership uses the impersonation
+ // token of the calling thread. If the thread is not impersonating,
+ // the function duplicates the thread's primary token to create an
+ // impersonation token."
+ token := windows.Token(0)
+
+ member, err := token.IsMember(sid)
+ if err != nil {
+ logrus.Warnf("Token Membership Error: %s", err)
+ return false
+ }
+
+ return member || token.IsElevated()
+}
+
+func relaunchElevatedWait() error {
+ e, _ := os.Executable()
+ d, _ := os.Getwd()
+ exe, _ := syscall.UTF16PtrFromString(e)
+ cwd, _ := syscall.UTF16PtrFromString(d)
+ arg, _ := syscall.UTF16PtrFromString(buildCommandArgs(true))
+ verb, _ := syscall.UTF16PtrFromString("runas")
+
+ shell32 := syscall.NewLazyDLL("shell32.dll")
+
+ info := &SHELLEXECUTEINFO{
+ fMask: SEE_MASK_NOCLOSEPROCESS,
+ hwnd: 0,
+ lpVerb: uintptr(unsafe.Pointer(verb)),
+ lpFile: uintptr(unsafe.Pointer(exe)),
+ lpParameters: uintptr(unsafe.Pointer(arg)),
+ lpDirectory: uintptr(unsafe.Pointer(cwd)),
+ nShow: 1,
+ }
+ info.cbSize = uint32(unsafe.Sizeof(*info))
+ procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
+ if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
+ err := syscall.GetLastError()
+ if info.hInstApp == SE_ERR_ACCESSDENIED {
+ return wrapMaybe(err, "request to elevate privileges was denied")
+ }
+ return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
+ }
+
+ handle := syscall.Handle(info.hProcess)
+ defer syscall.CloseHandle(handle)
+
+ w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
+ switch w {
+ case syscall.WAIT_OBJECT_0:
+ break
+ case syscall.WAIT_FAILED:
+ return errors.Wrap(err, "could not wait for process, failed")
+ default:
+ return errors.Errorf("could not wait for process, unknown error")
+ }
+ var code uint32
+ if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
+ return err
+ }
+ if code != 0 {
+ return &ExitCodeError{uint(code)}
+ }
+
+ return nil
+}
+
+func wrapMaybe(err error, message string) error {
+ if err != nil {
+ return errors.Wrap(err, message)
+ }
+
+ return errors.New(message)
+}
+
+func wrapMaybef(err error, format string, args ...interface{}) error {
+ if err != nil {
+ return errors.Wrapf(err, format, args...)
+ }
+
+ return errors.Errorf(format, args...)
+}
+
+func reboot() error {
+ const (
+ wtLocation = `Microsoft\WindowsApps\wt.exe`
+ wtPrefix = `%LocalAppData%\Microsoft\WindowsApps\wt -p "Windows PowerShell" `
+ localAppData = "LocalAppData"
+ pShellLaunch = `powershell -noexit "powershell -EncodedCommand (Get-Content '%s')"`
+ )
+
+ exe, _ := os.Executable()
+ relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), buildCommandArgs(false))
+ encoded := base64.StdEncoding.EncodeToString(encodeUTF16Bytes(relaunch))
+
+ dataDir, err := homedir.GetDataHome()
+ if err != nil {
+ return errors.Wrap(err, "could not determine data directory")
+ }
+ if err := os.MkdirAll(dataDir, 0755); err != nil {
+ return errors.Wrap(err, "could not create data directory")
+ }
+ commFile := filepath.Join(dataDir, "podman-relaunch.dat")
+ if err := ioutil.WriteFile(commFile, []byte(encoded), 0600); err != nil {
+ return errors.Wrap(err, "could not serialize command state")
+ }
+
+ command := fmt.Sprintf(pShellLaunch, commFile)
+ if _, err := os.Lstat(filepath.Join(os.Getenv(localAppData), wtLocation)); err == nil {
+ wtCommand := wtPrefix + command
+ // RunOnce is limited to 260 chars (supposedly no longer in Builds >= 19489)
+ // For now fallbacak in cases of long usernames (>89 chars)
+ if len(wtCommand) < 260 {
+ command = wtCommand
+ }
+ }
+
+ if err := addRunOnceRegistryEntry(command); err != nil {
+ return err
+ }
+
+ if err := obtainShutdownPrivilege(); err != nil {
+ return err
+ }
+
+ message := "To continue the process of enabling WSL, the system needs to reboot. " +
+ "Alternatively, you can cancel and reboot manually\n\n" +
+ "After rebooting, please wait a minute or two for podman machine to relaunch and continue installing."
+
+ if MessageBox(message, "Podman Machine", false) != 1 {
+ fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
+ os.Exit(ErrorSuccessRebootRequired)
+ return nil
+ }
+
+ user32 := syscall.NewLazyDLL("user32")
+ procExit := user32.NewProc("ExitWindowsEx")
+ if ret, _, err := procExit.Call(EWX_REBOOT|EWX_RESTARTAPPS|EWX_FORCEIFHUNG,
+ SHTDN_REASON_MAJOR_APPLICATION|SHTDN_REASON_MINOR_INSTALLATION|SHTDN_REASON_FLAG_PLANNED); ret != 1 {
+ return errors.Wrap(err, "reboot failed")
+ }
+
+ return nil
+}
+
+func obtainShutdownPrivilege() error {
+ const SeShutdownName = "SeShutdownPrivilege"
+
+ advapi32 := syscall.NewLazyDLL("advapi32")
+ OpenProcessToken := advapi32.NewProc("OpenProcessToken")
+ LookupPrivilegeValue := advapi32.NewProc("LookupPrivilegeValueW")
+ AdjustTokenPrivileges := advapi32.NewProc("AdjustTokenPrivileges")
+
+ proc, _ := syscall.GetCurrentProcess()
+
+ var hToken uintptr
+ if ret, _, err := OpenProcessToken.Call(uintptr(proc), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken))); ret != 1 {
+ return errors.Wrap(err, "error opening process token")
+ }
+
+ var privs TokenPrivileges
+ if ret, _, err := LookupPrivilegeValue.Call(uintptr(0), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SeShutdownName))), uintptr(unsafe.Pointer(&(privs.privileges[0].luid)))); ret != 1 {
+ return errors.Wrap(err, "error looking up shutdown privilege")
+ }
+
+ privs.privilegeCount = 1
+ privs.privileges[0].attributes = SE_PRIVILEGE_ENABLED
+
+ if ret, _, err := AdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&privs)), 0, uintptr(0), 0); ret != 1 {
+ return errors.Wrap(err, "error enabling shutdown privilege on token")
+ }
+
+ return nil
+}
+
+func addRunOnceRegistryEntry(command string) error {
+ k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE)
+ if err != nil {
+ return errors.Wrap(err, "could not open RunOnce registry entry")
+ }
+
+ defer k.Close()
+
+ if err := k.SetExpandStringValue("podman-machine", command); err != nil {
+ return errors.Wrap(err, "could not open RunOnce registry entry")
+ }
+
+ return nil
+}
+
+func encodeUTF16Bytes(s string) []byte {
+ u16 := utf16.Encode([]rune(s))
+ u16le := make([]byte, len(u16)*2)
+ for i := 0; i < len(u16); i++ {
+ u16le[i<<1] = byte(u16[i])
+ u16le[(i<<1)+1] = byte(u16[i] >> 8)
+ }
+ return u16le
+}
+
+func MessageBox(caption, title string, fail bool) int {
+ var format int
+ if fail {
+ format = 0x10
+ } else {
+ format = 0x41
+ }
+
+ user32 := syscall.NewLazyDLL("user32.dll")
+ captionPtr, _ := syscall.UTF16PtrFromString(caption)
+ titlePtr, _ := syscall.UTF16PtrFromString(title)
+ ret, _, _ := user32.NewProc("MessageBoxW").Call(
+ uintptr(0),
+ uintptr(unsafe.Pointer(captionPtr)),
+ uintptr(unsafe.Pointer(titlePtr)),
+ uintptr(format))
+
+ return int(ret)
+}
+
+func buildCommandArgs(elevate bool) string {
+ var args []string
+ for _, arg := range os.Args[1:] {
+ if arg != "--reexec" {
+ args = append(args, syscall.EscapeArg(arg))
+ if elevate && arg == "init" {
+ args = append(args, "--reexec")
+ }
+ }
+ }
+ return strings.Join(args, " ")
+}
diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go
index 29c505e9c..f419601e9 100644
--- a/vendor/github.com/containers/common/pkg/config/config.go
+++ b/vendor/github.com/containers/common/pkg/config/config.go
@@ -512,6 +512,8 @@ type MachineConfig struct {
Image string `toml:"image,omitempty"`
// Memory in MB a machine is created with.
Memory uint64 `toml:"memory,omitempty,omitzero"`
+ // Username to use for rootless podman when init-ing a podman machine VM
+ User string `toml:"user,omitempty"`
}
// Destination represents destination for remote service
diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf b/vendor/github.com/containers/common/pkg/config/containers.conf
index 84b49b7e4..4e8ad21f8 100644
--- a/vendor/github.com/containers/common/pkg/config/containers.conf
+++ b/vendor/github.com/containers/common/pkg/config/containers.conf
@@ -587,6 +587,11 @@ default_sysctls = [
#
#memory=2048
+# The username to use and create on the podman machine OS for rootless
+# container access.
+#
+#user = "core"
+
# The [machine] table MUST be the last entry in this file.
# (Unless another table is added)
# TOML does not provide a way to end a table other than a further table being
diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go
index 8821aa91e..cd7fea4a1 100644
--- a/vendor/github.com/containers/common/pkg/config/default.go
+++ b/vendor/github.com/containers/common/pkg/config/default.go
@@ -227,8 +227,9 @@ func defaultMachineConfig() MachineConfig {
return MachineConfig{
CPUs: 1,
DiskSize: 100,
- Image: "testing",
+ Image: getDefaultMachineImage(),
Memory: 2048,
+ User: getDefaultMachineUser(),
}
}
diff --git a/vendor/github.com/containers/common/pkg/config/default_linux.go b/vendor/github.com/containers/common/pkg/config/default_linux.go
index c68c0b130..9446d3ff9 100644
--- a/vendor/github.com/containers/common/pkg/config/default_linux.go
+++ b/vendor/github.com/containers/common/pkg/config/default_linux.go
@@ -13,6 +13,17 @@ const (
oldMaxSize = uint64(1048576)
)
+// getDefaultMachineImage returns the default machine image stream
+// On Linux/Mac, this returns the FCOS stream
+func getDefaultMachineImage() string {
+ return "testing"
+}
+
+// getDefaultMachineUser returns the user to use for rootless podman
+func getDefaultMachineUser() string {
+ return "core"
+}
+
// getDefaultRootlessNetwork returns the default rootless network configuration.
// It is "slirp4netns" for Linux.
func getDefaultRootlessNetwork() string {
diff --git a/vendor/github.com/containers/common/pkg/config/default_unsupported.go b/vendor/github.com/containers/common/pkg/config/default_unsupported.go
index e38fb810d..b6ee286ec 100644
--- a/vendor/github.com/containers/common/pkg/config/default_unsupported.go
+++ b/vendor/github.com/containers/common/pkg/config/default_unsupported.go
@@ -1,7 +1,18 @@
-// +build !linux
+// +build !linux,!windows
package config
+// getDefaultMachineImage returns the default machine image stream
+// On Linux/Mac, this returns the FCOS stream
+func getDefaultMachineImage() string {
+ return "testing"
+}
+
+// getDefaultMachineUser returns the user to use for rootless podman
+func getDefaultMachineUser() string {
+ return "core"
+}
+
// getDefaultRootlessNetwork returns the default rootless network configuration.
// It is "cni" for non-Linux OSes (to better support `podman-machine` usecases).
func getDefaultRootlessNetwork() string {
diff --git a/vendor/github.com/containers/common/pkg/config/default_windows.go b/vendor/github.com/containers/common/pkg/config/default_windows.go
new file mode 100644
index 000000000..5f8dd1a28
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/config/default_windows.go
@@ -0,0 +1,28 @@
+package config
+
+// getDefaultImage returns the default machine image stream
+// On Windows this refers to the Fedora major release number
+func getDefaultMachineImage() string {
+ return "35"
+}
+
+// getDefaultMachineUser returns the user to use for rootless podman
+func getDefaultMachineUser() string {
+ return "user"
+}
+
+// getDefaultRootlessNetwork returns the default rootless network configuration.
+// It is "cni" for non-Linux OSes (to better support `podman-machine` usecases).
+func getDefaultRootlessNetwork() string {
+ return "cni"
+}
+
+// isCgroup2UnifiedMode returns whether we are running in cgroup2 mode.
+func isCgroup2UnifiedMode() (isUnified bool, isUnifiedErr error) {
+ return false, nil
+}
+
+// getDefaultProcessLimits returns the nofile and nproc for the current process in ulimits format
+func getDefaultProcessLimits() []string {
+ return []string{}
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index d1cd5f24a..68f9e5a99 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -106,7 +106,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/util
-# github.com/containers/common v0.46.1-0.20211205182721-515a2805e7b9
+# github.com/containers/common v0.46.1-0.20211209220542-24f363480347
## explicit
github.com/containers/common/libimage
github.com/containers/common/libimage/manifests
@@ -653,6 +653,7 @@ github.com/uber/jaeger-client-go/thrift-gen/jaeger
github.com/uber/jaeger-client-go/thrift-gen/zipkincore
github.com/uber/jaeger-client-go/utils
# github.com/ulikunitz/xz v0.5.10
+## explicit
github.com/ulikunitz/xz
github.com/ulikunitz/xz/internal/hash
github.com/ulikunitz/xz/internal/xlog
@@ -747,6 +748,7 @@ golang.org/x/sys/windows/registry
# golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
golang.org/x/term
# golang.org/x/text v0.3.7
+## explicit
golang.org/x/text/encoding
golang.org/x/text/encoding/charmap
golang.org/x/text/encoding/htmlindex