Merge pull request #12503 from n1hility/wsl-machine

Introduce Windows WSL implementation of podman machine
This commit is contained in:
OpenShift Merge Robot
2021-12-26 13:26:07 +01:00
committed by GitHub
35 changed files with 1943 additions and 181 deletions

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
@ -8,7 +8,6 @@ import (
"github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/completion"
"github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -38,6 +37,8 @@ func init() {
}) })
flags := initCmd.Flags() flags := initCmd.Flags()
cfg := registry.PodmanConfig() cfg := registry.PodmanConfig()
initOpts.Username = cfg.Config.Machine.User
cpusFlagName := "cpus" cpusFlagName := "cpus"
flags.Uint64Var( flags.Uint64Var(
&initOpts.CPUS, &initOpts.CPUS,
@ -76,6 +77,13 @@ func init() {
flags.StringVar(&initOpts.TimeZone, timezoneFlagName, defaultTz, "Set timezone") flags.StringVar(&initOpts.TimeZone, timezoneFlagName, defaultTz, "Set timezone")
_ = initCmd.RegisterFlagCompletionFunc(timezoneFlagName, completion.AutocompleteDefault) _ = initCmd.RegisterFlagCompletionFunc(timezoneFlagName, completion.AutocompleteDefault)
flags.BoolVar(
&initOpts.ReExec,
"reexec", false,
"process was rexeced",
)
flags.MarkHidden("reexec")
ImagePathFlagName := "image-path" ImagePathFlagName := "image-path"
flags.StringVar(&initOpts.ImagePath, ImagePathFlagName, cfg.Machine.Image, "Path to qcow image") flags.StringVar(&initOpts.ImagePath, ImagePathFlagName, cfg.Machine.Image, "Path to qcow image")
_ = initCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault) _ = initCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault)
@ -88,33 +96,47 @@ func init() {
// TODO should we allow for a users to append to the qemu cmdline? // TODO should we allow for a users to append to the qemu cmdline?
func initMachine(cmd *cobra.Command, args []string) error { func initMachine(cmd *cobra.Command, args []string) error {
var ( var (
vm machine.VM vm machine.VM
vmType string err error
err error
) )
provider := getSystemDefaultProvider()
initOpts.Name = defaultMachineName initOpts.Name = defaultMachineName
if len(args) > 0 { if len(args) > 0 {
initOpts.Name = args[0] initOpts.Name = args[0]
} }
switch vmType { if _, err := provider.LoadVMByName(initOpts.Name); err == nil {
default: // qemu is the default return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name)
if _, err := qemu.LoadVMByName(initOpts.Name); err == nil {
return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name)
}
vm, err = qemu.NewMachine(initOpts)
} }
vm, err = provider.NewMachine(initOpts)
if err != nil { if err != nil {
return err 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 return err
} }
fmt.Println("Machine init complete")
if now { if now {
err = vm.Start(initOpts.Name, machine.StartOptions{}) err = vm.Start(initOpts.Name, machine.StartOptions{})
if err == nil { if err == nil {
fmt.Printf("Machine %q started successfully\n", initOpts.Name) 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 return err
} }

View File

@ -1,4 +1,5 @@
// +build amd64,!windows arm64,!windows //go:build amd64 || arm64
// +build amd64 arm64
package machine package machine
@ -16,7 +17,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/cmd/podman/validate"
"github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/docker/go-units" "github.com/docker/go-units"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -69,9 +69,14 @@ func init() {
} }
func list(cmd *cobra.Command, args []string) error { func list(cmd *cobra.Command, args []string) error {
var opts machine.ListOptions var (
// We only have qemu VM's for now opts machine.ListOptions
listResponse, err := qemu.List(opts) listResponse []*machine.ListResponse
err error
)
provider := getSystemDefaultProvider()
listResponse, err = provider.List(opts)
if err != nil { if err != nil {
return errors.Wrap(err, "error listing vms") return errors.Wrap(err, "error listing vms")
} }
@ -182,8 +187,8 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*machineReporter, error) {
response.Stream = streamName(vm.Stream) response.Stream = streamName(vm.Stream)
response.VMType = vm.VMType response.VMType = vm.VMType
response.CPUs = vm.CPUs response.CPUs = vm.CPUs
response.Memory = strUint(vm.Memory * units.MiB) response.Memory = strUint(vm.Memory)
response.DiskSize = strUint(vm.DiskSize * units.GiB) response.DiskSize = strUint(vm.DiskSize)
machineResponses = append(machineResponses, response) 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.Created = units.HumanDuration(time.Since(vm.CreatedAt)) + " ago"
response.VMType = vm.VMType response.VMType = vm.VMType
response.CPUs = vm.CPUs response.CPUs = vm.CPUs
response.Memory = units.HumanSize(float64(vm.Memory) * units.MiB) response.Memory = units.HumanSize(float64(vm.Memory))
response.DiskSize = units.HumanSize(float64(vm.DiskSize) * units.GiB) response.DiskSize = units.HumanSize(float64(vm.DiskSize))
humanResponses = append(humanResponses, response) humanResponses = append(humanResponses, response)
} }

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
@ -8,7 +8,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/cmd/podman/validate"
"github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -51,7 +50,8 @@ func autocompleteMachine(cmd *cobra.Command, args []string, toComplete string) (
func getMachines(toComplete string) ([]string, cobra.ShellCompDirective) { func getMachines(toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{} suggestions := []string{}
machines, err := qemu.List(machine.ListOptions{}) provider := getSystemDefaultProvider()
machines, err := provider.List(machine.ListOptions{})
if err != nil { if err != nil {
cobra.CompErrorln(err.Error()) cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp

View File

@ -1,4 +1,4 @@
// +build !amd64 amd64,windows // +build !amd64,!arm64
package machine package machine

View File

@ -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()
}

View File

@ -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()
}

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
@ -10,7 +10,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -52,18 +51,16 @@ func init() {
func rm(cmd *cobra.Command, args []string) error { func rm(cmd *cobra.Command, args []string) error {
var ( var (
err error err error
vm machine.VM vm machine.VM
vmType string
) )
vmName := defaultMachineName vmName := defaultMachineName
if len(args) > 0 && len(args[0]) > 0 { if len(args) > 0 && len(args[0]) > 0 {
vmName = args[0] vmName = args[0]
} }
switch vmType {
default: provider := getSystemDefaultProvider()
vm, err = qemu.LoadVMByName(vmName) vm, err = provider.LoadVMByName(vmName)
}
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
@ -9,7 +9,6 @@ import (
"github.com/containers/common/pkg/config" "github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -47,27 +46,24 @@ func ssh(cmd *cobra.Command, args []string) error {
err error err error
validVM bool validVM bool
vm machine.VM vm machine.VM
vmType string
) )
// Set the VM to default // Set the VM to default
vmName := defaultMachineName vmName := defaultMachineName
provider := getSystemDefaultProvider()
// If len is greater than 0, it means we may have been // If len is greater than 0, it means we may have been
// provided the VM name. If so, we check. The VM name, // provided the VM name. If so, we check. The VM name,
// if provided, must be in args[0]. // if provided, must be in args[0].
if len(args) > 0 { if len(args) > 0 {
switch vmType { validVM, err = provider.IsValidVMName(args[0])
default: if err != nil {
validVM, err = qemu.IsValidVMName(args[0]) return err
if err != nil { }
return err if validVM {
} vmName = args[0]
if validVM { } else {
vmName = args[0] sshOpts.Args = append(sshOpts.Args, args[0])
} else {
sshOpts.Args = append(sshOpts.Args, args[0])
}
} }
} }
@ -88,10 +84,7 @@ func ssh(cmd *cobra.Command, args []string) error {
} }
} }
switch vmType { vm, err = provider.LoadVMByName(vmName)
default:
vm, err = qemu.LoadVMByName(vmName)
}
if err != nil { if err != nil {
return errors.Wrapf(err, "vm %s not found", vmName) return errors.Wrapf(err, "vm %s not found", vmName)
} }

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
@ -7,7 +7,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -33,30 +32,31 @@ func init() {
func start(cmd *cobra.Command, args []string) error { func start(cmd *cobra.Command, args []string) error {
var ( var (
err error err error
vm machine.VM vm machine.VM
vmType string
) )
vmName := defaultMachineName vmName := defaultMachineName
if len(args) > 0 && len(args[0]) > 0 { if len(args) > 0 && len(args[0]) > 0 {
vmName = args[0] vmName = args[0]
} }
// We only have qemu VM's for now provider := getSystemDefaultProvider()
active, activeName, err := qemu.CheckActiveVM() vm, err = provider.LoadVMByName(vmName)
if err != nil { if err != nil {
return err return err
} }
active, activeName, cerr := provider.CheckExclusiveActiveVM()
if cerr != nil {
return cerr
}
if active { if active {
if vmName == activeName { if vmName == activeName {
return errors.Wrapf(machine.ErrVMAlreadyRunning, "cannot start VM %s", vmName) 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) return errors.Wrapf(machine.ErrMultipleActiveVM, "cannot start VM %s. VM %s is currently running", vmName, activeName)
} }
switch vmType { vm, err = provider.LoadVMByName(vmName)
default:
vm, err = qemu.LoadVMByName(vmName)
}
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,4 +1,5 @@
// +build amd64,!windows arm64,!windows //go:build amd64 || arm64
// +build amd64 arm64
package machine package machine
@ -7,7 +8,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/pkg/machine/qemu"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -33,18 +33,15 @@ func init() {
// TODO Name shouldn't be required, need to create a default vm // TODO Name shouldn't be required, need to create a default vm
func stop(cmd *cobra.Command, args []string) error { func stop(cmd *cobra.Command, args []string) error {
var ( var (
err error err error
vm machine.VM vm machine.VM
vmType string
) )
vmName := defaultMachineName vmName := defaultMachineName
if len(args) > 0 && len(args[0]) > 0 { if len(args) > 0 && len(args[0]) > 0 {
vmName = args[0] vmName = args[0]
} }
switch vmType { provider := getSystemDefaultProvider()
default: vm, err = provider.LoadVMByName(vmName)
vm, err = qemu.LoadVMByName(vmName)
}
if err != nil { if err != nil {
return err return err
} }

4
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/containernetworking/cni v1.0.1 github.com/containernetworking/cni v1.0.1
github.com/containernetworking/plugins v1.0.1 github.com/containernetworking/plugins v1.0.1
github.com/containers/buildah v1.23.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/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.17.1-0.20211207161909-6f3c8453e1a7 github.com/containers/image/v5 v5.17.1-0.20211207161909-6f3c8453e1a7
github.com/containers/ocicrypt v1.1.2 github.com/containers/ocicrypt v1.1.2
@ -60,12 +60,14 @@ require (
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
github.com/uber/jaeger-client-go v2.30.0+incompatible 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/vbauerster/mpb/v6 v6.0.4
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d golang.org/x/sys v0.0.0-20211205182925-97ca703d548d
golang.org/x/text v0.3.7
google.golang.org/protobuf v1.27.1 google.golang.org/protobuf v1.27.1
gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b

4
go.sum
View File

@ -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 h1:Tpc9DsRuU+0Oofewpxb6OJVNQjCu7yloN/obUqzfDTY=
github.com/containers/buildah v1.23.1/go.mod h1:4WnrN0yrA7ab0ppgunixu2WM1rlD2rG8QLJAKbEkZlQ= 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.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.20211209220542-24f363480347 h1:6CS7RroQLJu/SgUJXGZ3bOs2vnh9rxEnxczDcGjStBw=
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/go.mod h1:SoHWZESBD7dbqIOkvKrIg5D8EuVIQgL6vkOvv0Yebws=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= 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/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.16.0/go.mod h1:XgTpfAPLRGOd1XYyCU5cISFr777bLmOerCSpt/v7+Q4= github.com/containers/image/v5 v5.16.0/go.mod h1:XgTpfAPLRGOd1XYyCU5cISFr777bLmOerCSpt/v7+Q4=

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
@ -24,6 +24,15 @@ type InitOptions struct {
TimeZone string TimeZone string
URI url.URL URI url.URL
Username string 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 type RemoteConnectionType string
@ -49,6 +58,7 @@ type Download struct {
Sha256sum string Sha256sum string
URL *url.URL URL *url.URL
VMName string VMName string
Size int64
} }
type ListOptions struct{} type ListOptions struct{}
@ -81,7 +91,7 @@ type RemoveOptions struct {
} }
type VM interface { type VM interface {
Init(opts InitOptions) error Init(opts InitOptions) (bool, error)
Remove(name string, opts RemoveOptions) (string, func() error, error) Remove(name string, opts RemoveOptions) (string, func() error, error)
SSH(name string, opts SSHOptions) error SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error Start(name string, opts StartOptions) error
@ -89,7 +99,7 @@ type VM interface {
} }
type DistributionDownload interface { type DistributionDownload interface {
DownloadImage() error HasUsableCache() (bool, error)
Get() *Download Get() *Download
} }

View File

@ -1,4 +1,5 @@
// +build amd64,!windows arm64,!windows //go:build amd64 || arm64
// +build amd64 arm64
package machine package machine

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
@ -65,25 +65,6 @@ func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload
return fcd, nil 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 { func (f FcosDownload) Get() *Download {
return &f.Download return &f.Download
} }
@ -95,14 +76,14 @@ type fcosDownloadInfo struct {
Sha256Sum string Sha256Sum string
} }
func UpdateAvailable(d *Download) (bool, error) { func (f FcosDownload) HasUsableCache() (bool, error) {
// check the sha of the local image if it exists // check the sha of the local image if it exists
// get the sha of the remote image // get the sha of the remote image
// == dont bother to pull // == 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 return false, nil
} }
fd, err := os.Open(d.LocalPath) fd, err := os.Open(f.LocalPath)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -115,7 +96,7 @@ func UpdateAvailable(d *Download) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
return sum.Encoded() == d.Sha256sum, nil return sum.Encoded() == f.Sha256sum, nil
} }
func getFcosArch() string { func getFcosArch() string {

122
pkg/machine/fedora.go Normal file
View File

@ -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
}

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine

View File

@ -1,4 +1,4 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine

View File

@ -0,0 +1,7 @@
//+build windows
package machine
func getLocalTimeZone() (string, error) {
return "", nil
}

View File

@ -1,13 +1,21 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
import ( import (
"errors"
"fmt"
"io/ioutil" "io/ioutil"
"os"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"github.com/sirupsen/logrus"
) )
var sshCommand = []string{"ssh-keygen", "-N", "", "-t", "ed25519", "-f"}
// CreateSSHKeys makes a priv and pub ssh key for interacting // CreateSSHKeys makes a priv and pub ssh key for interacting
// the a VM. // the a VM.
func CreateSSHKeys(writeLocation string) (string, error) { func CreateSSHKeys(writeLocation string) (string, error) {
@ -21,7 +29,42 @@ func CreateSSHKeys(writeLocation string) (string, error) {
return strings.TrimSuffix(string(b), "\n"), nil 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 // generatekeys creates an ed25519 set of keys
func generatekeys(writeLocation string) error { 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()
} }

View File

@ -1,3 +1,3 @@
// +build !amd64 amd64,windows // +build !amd64,!arm64
package machine package machine

View File

@ -1,8 +1,9 @@
// +build amd64,!windows arm64,!windows // +build amd64 arm64
package machine package machine
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -17,6 +18,7 @@ import (
"github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/pkg/compression"
"github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/archive"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/ulikunitz/xz"
"github.com/vbauerster/mpb/v6" "github.com/vbauerster/mpb/v6"
"github.com/vbauerster/mpb/v6/decor" "github.com/vbauerster/mpb/v6/decor"
) )
@ -43,7 +45,7 @@ func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload
return nil, err return nil, err
} }
if len(getURL.Scheme) > 0 { if len(getURL.Scheme) > 0 {
urlSplit := strings.Split(pullPath, "/") urlSplit := strings.Split(getURL.Path, "/")
imageName = urlSplit[len(urlSplit)-1] imageName = urlSplit[len(urlSplit)-1]
dl.LocalUncompressedFile = filepath.Join(dataDir, imageName) dl.LocalUncompressedFile = filepath.Join(dataDir, imageName)
dl.URL = getURL dl.URL = getURL
@ -63,39 +65,48 @@ func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload
return gd, nil return gd, nil
} }
func (g GenericDownload) getLocalUncompressedName() string { func (d Download) getLocalUncompressedName() string {
var ( var (
extension string extension string
) )
switch { switch {
case strings.HasSuffix(g.LocalPath, ".bz2"): case strings.HasSuffix(d.LocalPath, ".bz2"):
extension = ".bz2" extension = ".bz2"
case strings.HasSuffix(g.LocalPath, ".gz"): case strings.HasSuffix(d.LocalPath, ".gz"):
extension = ".gz" extension = ".gz"
case strings.HasSuffix(g.LocalPath, ".xz"): case strings.HasSuffix(d.LocalPath, ".xz"):
extension = ".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) return strings.TrimSuffix(uncompressedFilename, extension)
} }
func (g GenericDownload) DownloadImage() 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 err
}
}
return Decompress(g.LocalPath, g.getLocalUncompressedName())
}
func (g GenericDownload) Get() *Download { func (g GenericDownload) Get() *Download {
return &g.Download return &g.Download
} }
func (g GenericDownload) HasUsableCache() (bool, error) {
// If we have a URL for this "downloader", we now pull it
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(d.Get().LocalPath, d.Get().getLocalUncompressedName())
}
// DownloadVMImage downloads a VM image from url to given path // DownloadVMImage downloads a VM image from url to given path
// with download status // with download status
func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error { func DownloadVMImage(downloadURL *url2.URL, localImagePath string) error {
out, err := os.Create(localImagePath) out, err := os.Create(localImagePath)
if err != nil { if err != nil {
return err 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) return fmt.Errorf("error downloading VM image %s: %s", downloadURL, resp.Status)
} }
size := resp.ContentLength size := resp.ContentLength
urlSplit := strings.Split(downloadURL.String(), "/") urlSplit := strings.Split(downloadURL.Path, "/")
prefix := "Downloading VM image: " + urlSplit[len(urlSplit)-1] prefix := "Downloading VM image: " + urlSplit[len(urlSplit)-1]
onComplete := prefix + ": done" onComplete := prefix + ": done"
@ -177,24 +188,50 @@ func Decompress(localPath, uncompressedPath string) error {
// Will error out if file without .xz already exists // Will error out if file without .xz already exists
// Maybe extracting then renameing is a good idea here.. // Maybe extracting then renameing is a good idea here..
// depends on xz: not pre-installed on mac, so it becomes a brew dependency // depends on xz: not pre-installed on mac, so it becomes a brew dependency
func decompressXZ(src string, output io.Writer) error { func decompressXZ(src string, output io.WriteCloser) error {
cmd := exec.Command("xzcat", "-k", src) var read io.Reader
//cmd := exec.Command("xz", "-d", "-k", "-v", src) var cmd *exec.Cmd
stdOut, err := cmd.StdoutPipe() // Prefer xz utils for fastest performance, fallback to go xi2 impl
if err != nil { if _, err := exec.LookPath("xzcat"); err == nil {
return err 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() { go func() {
if _, err := io.Copy(output, stdOut); err != nil { if _, err := io.Copy(output, read); err != nil {
logrus.Error(err) 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) f, err := os.Open(src)
if err != nil { if err != nil {
return err return err
@ -207,6 +244,9 @@ func decompressEverythingElse(src string, output io.Writer) error {
if err := uncompressStream.Close(); err != nil { if err := uncompressStream.Close(); err != nil {
logrus.Error(err) logrus.Error(err)
} }
if err := output.Close(); err != nil {
logrus.Error(err)
}
}() }()
_, err = io.Copy(output, uncompressStream) _, err = io.Copy(output, uncompressStream)

View File

@ -4,6 +4,8 @@ package qemu
import "time" import "time"
type Provider struct{}
type MachineVM struct { type MachineVM struct {
// CPUs to be assigned to the VM // CPUs to be assigned to the VM
CPUs uint64 CPUs uint64
@ -44,6 +46,4 @@ var (
// defaultQMPTimeout is the timeout duration for the // defaultQMPTimeout is the timeout duration for the
// qmp monitor interactions // qmp monitor interactions
defaultQMPTimeout time.Duration = 2 * time.Second defaultQMPTimeout time.Duration = 2 * time.Second
// defaultRemoteUser describes the ssh username default
defaultRemoteUser = "core"
) )

View File

@ -21,18 +21,24 @@ import (
"github.com/containers/podman/v3/utils" "github.com/containers/podman/v3/utils"
"github.com/containers/storage/pkg/homedir" "github.com/containers/storage/pkg/homedir"
"github.com/digitalocean/go-qemu/qmp" "github.com/digitalocean/go-qemu/qmp"
"github.com/docker/go-units"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
var ( var (
qemuProvider = &Provider{}
// vmtype refers to qemu (vs libvirt, krun, etc) // vmtype refers to qemu (vs libvirt, krun, etc)
vmtype = "qemu" vmtype = "qemu"
) )
func GetQemuProvider() machine.Provider {
return qemuProvider
}
// NewMachine initializes an instance of a virtual machine based on the qemu // NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization. // virtualization.
func NewMachine(opts machine.InitOptions) (machine.VM, error) { func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vmConfigDir, err := machine.GetConfDir(vmtype) vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil { if err != nil {
return nil, err return nil, err
@ -44,16 +50,8 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) {
ignitionFile := filepath.Join(vmConfigDir, vm.Name+".ign") ignitionFile := filepath.Join(vmConfigDir, vm.Name+".ign")
vm.IgnitionFilePath = ignitionFile vm.IgnitionFilePath = ignitionFile
// An image was specified vm.ImagePath = opts.ImagePath
if len(opts.ImagePath) > 0 {
vm.ImagePath = opts.ImagePath
}
// Assign remote user name. if not provided, use default
vm.RemoteUsername = opts.Username vm.RemoteUsername = opts.Username
if len(vm.RemoteUsername) < 1 {
vm.RemoteUsername = defaultRemoteUser
}
// Add a random port for ssh // Add a random port for ssh
port, err := utils.GetRandomPort() 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 // LoadByName reads a json file that describes a known qemu vm
// and returns a vm instance // and returns a vm instance
func LoadVMByName(name string) (machine.VM, error) { func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
vm := new(MachineVM) vm := new(MachineVM)
vmConfigDir, err := machine.GetConfDir(vmtype) vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil { if err != nil {
@ -126,7 +124,7 @@ func LoadVMByName(name string) (machine.VM, error) {
// Init writes the json configuration file to the filesystem for // Init writes the json configuration file to the filesystem for
// other verbs (start, stop) // other verbs (start, stop)
func (v *MachineVM) Init(opts machine.InitOptions) error { func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
var ( var (
key string key string
) )
@ -135,7 +133,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
// its existence // its existence
vmConfigDir, err := machine.GetConfDir(vmtype) vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil { if err != nil {
return err return false, err
} }
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json" jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
v.IdentityPath = filepath.Join(sshDir, v.Name) 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) dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
if err != nil { if err != nil {
return err return false, err
} }
v.ImagePath = dd.Get().LocalUncompressedFile v.ImagePath = dd.Get().LocalUncompressedFile
if err := dd.DownloadImage(); err != nil { if err := machine.DownloadImage(dd); err != nil {
return err return false, err
} }
default: default:
// The user has provided an alternate image which can be a file path // 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" v.ImageStream = "custom"
g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath) g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
if err != nil { if err != nil {
return err return false, err
} }
v.ImagePath = g.Get().LocalUncompressedFile v.ImagePath = g.Get().LocalUncompressedFile
if err := g.DownloadImage(); err != nil { if err := machine.DownloadImage(g); err != nil {
return err return false, err
} }
} }
// Add arch specific options including image location // Add arch specific options including image location
@ -175,12 +173,12 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
if len(opts.IgnitionPath) < 1 { if len(opts.IgnitionPath) < 1 {
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername) 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 { 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") 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 { if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
return err return false, err
} }
} else { } else {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman") 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 // Write the JSON file
b, err := json.MarshalIndent(v, "", " ") b, err := json.MarshalIndent(v, "", " ")
if err != nil { if err != nil {
return err return false, err
} }
if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil { if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
return err return false, err
} }
// User has provided ignition file so keygen // User has provided ignition file so keygen
@ -199,17 +197,17 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
if len(opts.IgnitionPath) < 1 { if len(opts.IgnitionPath) < 1 {
key, err = machine.CreateSSHKeys(v.IdentityPath) key, err = machine.CreateSSHKeys(v.IdentityPath)
if err != nil { if err != nil {
return err return false, err
} }
} }
// Run arch specific things that need to be done // Run arch specific things that need to be done
if err := v.prepare(); err != nil { if err := v.prepare(); err != nil {
return err return false, err
} }
originalDiskSize, err := getDiskSize(v.ImagePath) originalDiskSize, err := getDiskSize(v.ImagePath)
if err != nil { if err != nil {
return err return false, err
} }
// Resize the disk image to input disk size // Resize the disk image to input disk size
// only if the virtualdisk size is less than // 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.Stdout = os.Stdout
resize.Stderr = os.Stderr resize.Stderr = os.Stderr
if err := resize.Run(); err != nil { 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 // 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 { if len(opts.IgnitionPath) > 0 {
inputIgnition, err := ioutil.ReadFile(opts.IgnitionPath) inputIgnition, err := ioutil.ReadFile(opts.IgnitionPath)
if err != nil { 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 // Write the ignition file
ign := machine.DynamicIgnition{ ign := machine.DynamicIgnition{
@ -239,7 +237,8 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
TimeZone: opts.TimeZone, TimeZone: opts.TimeZone,
WritePath: v.IgnitionFilePath, WritePath: v.IgnitionFilePath,
} }
return machine.NewIgnitionFile(ign) err = machine.NewIgnitionFile(ign)
return err == nil, err
} }
// Start executes the qemu command line and forks it // 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 // 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() return GetVMInfos()
} }
@ -601,8 +600,8 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
listEntry.Stream = vm.ImageStream listEntry.Stream = vm.ImageStream
listEntry.VMType = "qemu" listEntry.VMType = "qemu"
listEntry.CPUs = vm.CPUs listEntry.CPUs = vm.CPUs
listEntry.Memory = vm.Memory listEntry.Memory = vm.Memory * units.MiB
listEntry.DiskSize = vm.DiskSize listEntry.DiskSize = vm.DiskSize * units.GiB
fi, err := os.Stat(fullPath) fi, err := os.Stat(fullPath)
if err != nil { if err != nil {
return err return err
@ -627,7 +626,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
return listed, err return listed, err
} }
func IsValidVMName(name string) (bool, error) { func (p *Provider) IsValidVMName(name string) (bool, error) {
infos, err := GetVMInfos() infos, err := GetVMInfos()
if err != nil { if err != nil {
return false, err return false, err
@ -640,8 +639,9 @@ func IsValidVMName(name string) (bool, error) {
return false, nil return false, nil
} }
// CheckActiveVM checks if there is a VM already running // CheckExclusiveActiveVM checks if there is a VM already running
func CheckActiveVM() (bool, string, error) { // that does not allow other VMs to be running
func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
vms, err := GetVMInfos() vms, err := GetVMInfos()
if err != nil { if err != nil {
return false, "", errors.Wrap(err, "error checking VM active") return false, "", errors.Wrap(err, "error checking VM active")

View File

@ -1,3 +1,3 @@
// +build !amd64 amd64,windows // +build !amd64,!arm64 windows
package qemu package qemu

1119
pkg/machine/wsl/machine.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
// +build !windows
package wsl

View File

@ -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, " ")
}

View File

@ -512,6 +512,8 @@ type MachineConfig struct {
Image string `toml:"image,omitempty"` Image string `toml:"image,omitempty"`
// Memory in MB a machine is created with. // Memory in MB a machine is created with.
Memory uint64 `toml:"memory,omitempty,omitzero"` 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 // Destination represents destination for remote service

View File

@ -587,6 +587,11 @@ default_sysctls = [
# #
#memory=2048 #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. # The [machine] table MUST be the last entry in this file.
# (Unless another table is added) # (Unless another table is added)
# TOML does not provide a way to end a table other than a further table being # TOML does not provide a way to end a table other than a further table being

View File

@ -227,8 +227,9 @@ func defaultMachineConfig() MachineConfig {
return MachineConfig{ return MachineConfig{
CPUs: 1, CPUs: 1,
DiskSize: 100, DiskSize: 100,
Image: "testing", Image: getDefaultMachineImage(),
Memory: 2048, Memory: 2048,
User: getDefaultMachineUser(),
} }
} }

View File

@ -13,6 +13,17 @@ const (
oldMaxSize = uint64(1048576) 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. // getDefaultRootlessNetwork returns the default rootless network configuration.
// It is "slirp4netns" for Linux. // It is "slirp4netns" for Linux.
func getDefaultRootlessNetwork() string { func getDefaultRootlessNetwork() string {

View File

@ -1,7 +1,18 @@
// +build !linux // +build !linux,!windows
package config 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. // getDefaultRootlessNetwork returns the default rootless network configuration.
// It is "cni" for non-Linux OSes (to better support `podman-machine` usecases). // It is "cni" for non-Linux OSes (to better support `podman-machine` usecases).
func getDefaultRootlessNetwork() string { func getDefaultRootlessNetwork() string {

View File

@ -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{}
}

4
vendor/modules.txt vendored
View File

@ -106,7 +106,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/util
github.com/containers/buildah/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 ## explicit
github.com/containers/common/libimage github.com/containers/common/libimage
github.com/containers/common/libimage/manifests 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/thrift-gen/zipkincore
github.com/uber/jaeger-client-go/utils github.com/uber/jaeger-client-go/utils
# github.com/ulikunitz/xz v0.5.10 # github.com/ulikunitz/xz v0.5.10
## explicit
github.com/ulikunitz/xz github.com/ulikunitz/xz
github.com/ulikunitz/xz/internal/hash github.com/ulikunitz/xz/internal/hash
github.com/ulikunitz/xz/internal/xlog 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 v0.0.0-20210615171337-6886f2dfbf5b
golang.org/x/term golang.org/x/term
# golang.org/x/text v0.3.7 # golang.org/x/text v0.3.7
## explicit
golang.org/x/text/encoding golang.org/x/text/encoding
golang.org/x/text/encoding/charmap golang.org/x/text/encoding/charmap
golang.org/x/text/encoding/htmlindex golang.org/x/text/encoding/htmlindex