mirror of
https://github.com/containers/podman.git
synced 2025-06-20 00:51:16 +08:00
Merge pull request #12503 from n1hility/wsl-machine
Introduce Windows WSL implementation of podman machine
This commit is contained in:
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build !amd64 amd64,windows
|
// +build !amd64,!arm64
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
12
cmd/podman/machine/platform.go
Normal file
12
cmd/podman/machine/platform.go
Normal 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()
|
||||||
|
}
|
10
cmd/podman/machine/platform_windows.go
Normal file
10
cmd/podman/machine/platform_windows.go
Normal 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()
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
4
go.mod
@ -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
4
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 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=
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// +build amd64,!windows arm64,!windows
|
//go:build amd64 || arm64
|
||||||
|
// +build amd64 arm64
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
@ -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
122
pkg/machine/fedora.go
Normal 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
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// +build amd64,!windows arm64,!windows
|
// +build amd64 arm64
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build amd64,!windows arm64,!windows
|
// +build amd64 arm64
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
7
pkg/machine/ignition_windows.go
Normal file
7
pkg/machine/ignition_windows.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//+build windows
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
func getLocalTimeZone() (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
// +build !amd64 amd64,windows
|
// +build !amd64,!arm64
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
|
||||||
)
|
)
|
||||||
|
@ -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")
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
// +build !amd64 amd64,windows
|
// +build !amd64,!arm64 windows
|
||||||
|
|
||||||
package qemu
|
package qemu
|
||||||
|
1119
pkg/machine/wsl/machine.go
Normal file
1119
pkg/machine/wsl/machine.go
Normal file
File diff suppressed because it is too large
Load Diff
3
pkg/machine/wsl/machine_unsupported.go
Normal file
3
pkg/machine/wsl/machine_unsupported.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package wsl
|
338
pkg/machine/wsl/util_windows.go
Normal file
338
pkg/machine/wsl/util_windows.go
Normal 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, " ")
|
||||||
|
}
|
2
vendor/github.com/containers/common/pkg/config/config.go
generated
vendored
2
vendor/github.com/containers/common/pkg/config/config.go
generated
vendored
@ -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
|
||||||
|
5
vendor/github.com/containers/common/pkg/config/containers.conf
generated
vendored
5
vendor/github.com/containers/common/pkg/config/containers.conf
generated
vendored
@ -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
|
||||||
|
3
vendor/github.com/containers/common/pkg/config/default.go
generated
vendored
3
vendor/github.com/containers/common/pkg/config/default.go
generated
vendored
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
vendor/github.com/containers/common/pkg/config/default_linux.go
generated
vendored
11
vendor/github.com/containers/common/pkg/config/default_linux.go
generated
vendored
@ -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 {
|
||||||
|
13
vendor/github.com/containers/common/pkg/config/default_unsupported.go
generated
vendored
13
vendor/github.com/containers/common/pkg/config/default_unsupported.go
generated
vendored
@ -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 {
|
||||||
|
28
vendor/github.com/containers/common/pkg/config/default_windows.go
generated
vendored
Normal file
28
vendor/github.com/containers/common/pkg/config/default_windows.go
generated
vendored
Normal 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
4
vendor/modules.txt
vendored
@ -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
|
||||||
|
Reference in New Issue
Block a user