diff options
Diffstat (limited to 'cmd/podman/machine')
-rw-r--r-- | cmd/podman/machine/info.go | 182 | ||||
-rw-r--r-- | cmd/podman/machine/init.go | 28 | ||||
-rw-r--r-- | cmd/podman/machine/inspect.go | 1 | ||||
-rw-r--r-- | cmd/podman/machine/list.go | 52 | ||||
-rw-r--r-- | cmd/podman/machine/machine.go | 20 | ||||
-rw-r--r-- | cmd/podman/machine/machine_unix.go | 11 | ||||
-rw-r--r-- | cmd/podman/machine/machine_windows.go | 9 | ||||
-rw-r--r-- | cmd/podman/machine/rm.go | 1 | ||||
-rw-r--r-- | cmd/podman/machine/set.go | 1 | ||||
-rw-r--r-- | cmd/podman/machine/ssh.go | 17 | ||||
-rw-r--r-- | cmd/podman/machine/start.go | 6 | ||||
-rw-r--r-- | cmd/podman/machine/stop.go | 1 |
12 files changed, 263 insertions, 66 deletions
diff --git a/cmd/podman/machine/info.go b/cmd/podman/machine/info.go new file mode 100644 index 000000000..9932027d8 --- /dev/null +++ b/cmd/podman/machine/info.go @@ -0,0 +1,182 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + +package machine + +import ( + "fmt" + "html/template" + "os" + "runtime" + + "github.com/containers/common/pkg/completion" + "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/report" + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/validate" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/machine" + "github.com/ghodss/yaml" + "github.com/spf13/cobra" +) + +var infoDescription = `Display information pertaining to the machine host.` + +var ( + infoCmd = &cobra.Command{ + Use: "info [options]", + Short: "Display machine host info", + Long: infoDescription, + PersistentPreRunE: rootlessOnly, + RunE: info, + Args: validate.NoArgs, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman machine info`, + } +) + +var ( + inFormat string +) + +// Info contains info on the machine host and version info +type Info struct { + Host *HostInfo `json:"Host"` + Version define.Version `json:"Version"` +} + +// HostInfo contains info on the machine host +type HostInfo struct { + Arch string `json:"Arch"` + CurrentMachine string `json:"CurrentMachine"` + DefaultMachine string `json:"DefaultMachine"` + EventsDir string `json:"EventsDir"` + MachineConfigDir string `json:"MachineConfigDir"` + MachineImageDir string `json:"MachineImageDir"` + MachineState string `json:"MachineState"` + NumberOfMachines int `json:"NumberOfMachines"` + OS string `json:"OS"` + VMType string `json:"VMType"` +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: infoCmd, + Parent: machineCmd, + }) + + flags := infoCmd.Flags() + formatFlagName := "format" + flags.StringVarP(&inFormat, formatFlagName, "f", "", "Change the output format to JSON or a Go template") + _ = infoCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&define.Info{})) +} + +func info(cmd *cobra.Command, args []string) error { + info := Info{} + version, err := define.GetVersion() + if err != nil { + return fmt.Errorf("error getting version info %w", err) + } + info.Version = version + + host, err := hostInfo() + if err != nil { + return err + } + info.Host = host + + switch { + case report.IsJSON(inFormat): + b, err := json.MarshalIndent(info, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + case cmd.Flags().Changed("format"): + tmpl := template.New(cmd.Name()).Funcs(template.FuncMap(report.DefaultFuncs)) + inFormat = report.NormalizeFormat(inFormat) + tmpl, err := tmpl.Parse(inFormat) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, info) + default: + b, err := yaml.Marshal(info) + if err != nil { + return err + } + fmt.Println(string(b)) + } + + return nil +} + +func hostInfo() (*HostInfo, error) { + host := HostInfo{} + + host.Arch = runtime.GOARCH + host.OS = runtime.GOOS + + provider := GetSystemDefaultProvider() + var listOpts machine.ListOptions + listResponse, err := provider.List(listOpts) + if err != nil { + return nil, fmt.Errorf("failed to get machines %w", err) + } + + host.NumberOfMachines = len(listResponse) + + cfg, err := config.ReadCustomConfig() + if err != nil { + return nil, err + } + + // Default state of machine is stopped + host.MachineState = "Stopped" + for _, vm := range listResponse { + // Set default machine if found + if vm.Name == cfg.Engine.ActiveService { + host.DefaultMachine = vm.Name + } + // If machine is running or starting, it is automatically the current machine + if vm.Running { + host.CurrentMachine = vm.Name + host.MachineState = "Running" + } else if vm.Starting { + host.CurrentMachine = vm.Name + host.MachineState = "Starting" + } + } + // If no machines are starting or running, set current machine to default machine + // If no default machines are found, do not report a default machine or a state + if host.CurrentMachine == "" { + if host.DefaultMachine == "" { + host.MachineState = "" + } else { + host.CurrentMachine = host.DefaultMachine + } + } + + host.VMType = provider.VMType() + + dataDir, err := machine.GetDataDir(host.VMType) + if err != nil { + return nil, fmt.Errorf("failed to get machine image dir") + } + host.MachineImageDir = dataDir + + confDir, err := machine.GetConfDir(host.VMType) + if err != nil { + return nil, fmt.Errorf("failed to get machine config dir %w", err) + } + host.MachineConfigDir = confDir + + eventsDir, err := eventSockDir() + if err != nil { + return nil, fmt.Errorf("failed to get events dir: %w", err) + } + host.EventsDir = eventsDir + + return &host, nil +} diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 6c31f3531..def3334e8 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -11,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/machine" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -20,21 +19,20 @@ var ( Use: "init [options] [NAME]", Short: "Initialize a virtual machine", Long: "initialize a virtual machine ", + PersistentPreRunE: rootlessOnly, RunE: initMachine, Args: cobra.MaximumNArgs(1), Example: `podman machine init myvm`, ValidArgsFunction: completion.AutocompleteNone, } -) -var ( initOpts = machine.InitOptions{} defaultMachineName = machine.DefaultMachineName now bool ) // maxMachineNameSize is set to thirty to limit huge machine names primarily -// because macos has a much smaller file size limit. +// because macOS has a much smaller file size limit. const maxMachineNameSize = 30 func init() { @@ -111,7 +109,6 @@ func init() { flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution") } -// TODO should we allow for a users to append to the qemu cmdline? func initMachine(cmd *cobra.Command, args []string) error { var ( err error @@ -122,12 +119,12 @@ func initMachine(cmd *cobra.Command, args []string) error { initOpts.Name = defaultMachineName if len(args) > 0 { if len(args[0]) > maxMachineNameSize { - return errors.New("machine name must be 30 characters or less") + return fmt.Errorf("machine name %q must be %d characters or less", args[0], maxMachineNameSize) } initOpts.Name = args[0] } if _, err := provider.LoadVMByName(initOpts.Name); err == nil { - return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name) + return fmt.Errorf("%s: %w", initOpts.Name, machine.ErrVMAlreadyExists) } for idx, vol := range initOpts.Volumes { initOpts.Volumes[idx] = os.ExpandEnv(vol) @@ -150,17 +147,12 @@ func initMachine(cmd *cobra.Command, args []string) error { fmt.Println("Machine init complete") if now { - err = vm.Start(initOpts.Name, machine.StartOptions{}) - if err == nil { - fmt.Printf("Machine %q started successfully\n", initOpts.Name) - newMachineEvent(events.Start, events.Event{Name: 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 start(cmd, args) + } + extra := "" + if initOpts.Name != defaultMachineName { + extra = " " + initOpts.Name } + fmt.Printf("To start your machine run:\n\n\tpodman machine start%s\n\n", extra) return err } diff --git a/cmd/podman/machine/inspect.go b/cmd/podman/machine/inspect.go index 4600a2b6d..d69c382f2 100644 --- a/cmd/podman/machine/inspect.go +++ b/cmd/podman/machine/inspect.go @@ -20,6 +20,7 @@ var ( Use: "inspect [options] [MACHINE...]", Short: "Inspect an existing machine", Long: "Provide details on a managed virtual machine", + PersistentPreRunE: rootlessOnly, RunE: inspect, Example: `podman machine inspect myvm`, ValidArgsFunction: autocompleteMachine, diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index 5254d50cf..dd4a86697 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -4,6 +4,7 @@ package machine import ( + "fmt" "os" "sort" "strconv" @@ -15,9 +16,9 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" + "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/machine" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -27,6 +28,7 @@ var ( Aliases: []string{"ls"}, Short: "List machines", Long: "List managed virtual machines.", + PersistentPreRunE: rootlessOnly, RunE: list, Args: validate.NoArgs, ValidArgsFunction: completion.AutocompleteNone, @@ -43,22 +45,6 @@ type listFlagType struct { quiet bool } -type ListReporter struct { - Name string - Default bool - Created string - Running bool - LastUp string - Stream string - VMType string - CPUs uint64 - Memory string - DiskSize string - Port int - RemoteUsername string - IdentityPath string -} - func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: lsCmd, @@ -68,7 +54,7 @@ func init() { flags := lsCmd.Flags() formatFlagName := "format" flags.StringVar(&listFlag.format, formatFlagName, "{{.Name}}\t{{.VMType}}\t{{.Created}}\t{{.LastUp}}\t{{.CPUs}}\t{{.Memory}}\t{{.DiskSize}}\n", "Format volume output using JSON or a Go template") - _ = lsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&ListReporter{})) + _ = lsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.ListReporter{})) flags.BoolVar(&listFlag.noHeading, "noheading", false, "Do not print headers") flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Show only machine names") } @@ -87,7 +73,7 @@ func list(cmd *cobra.Command, args []string) error { provider := GetSystemDefaultProvider() listResponse, err = provider.List(opts) if err != nil { - return errors.Wrap(err, "error listing vms") + return fmt.Errorf("listing vms: %w", err) } // Sort by last run @@ -121,8 +107,8 @@ func list(cmd *cobra.Command, args []string) error { return outputTemplate(cmd, machineReporter) } -func outputTemplate(cmd *cobra.Command, responses []*ListReporter) error { - headers := report.Headers(ListReporter{}, map[string]string{ +func outputTemplate(cmd *cobra.Command, responses []*entities.ListReporter) error { + headers := report.Headers(entities.ListReporter{}, map[string]string{ "LastUp": "LAST UP", "VmType": "VM TYPE", "CPUs": "CPUS", @@ -137,7 +123,7 @@ func outputTemplate(cmd *cobra.Command, responses []*ListReporter) error { switch { case cmd.Flags().Changed("format"): row = cmd.Flag("format").Value.String() - listFlag.noHeading = !report.HasTable(row) + printHeader = report.HasTable(row) row = report.NormalizeFormat(row) default: row = cmd.Flag("format").Value.String() @@ -156,7 +142,7 @@ func outputTemplate(cmd *cobra.Command, responses []*ListReporter) error { defer w.Flush() if printHeader { if err := tmpl.Execute(w, headers); err != nil { - return errors.Wrapf(err, "failed to write report column headers") + return fmt.Errorf("failed to write report column headers: %w", err) } } return tmpl.Execute(w, responses) @@ -181,15 +167,15 @@ func streamName(imageStream string) string { return imageStream } -func toMachineFormat(vms []*machine.ListResponse) ([]*ListReporter, error) { +func toMachineFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, error) { cfg, err := config.ReadCustomConfig() if err != nil { return nil, err } - machineResponses := make([]*ListReporter, 0, len(vms)) + machineResponses := make([]*entities.ListReporter, 0, len(vms)) for _, vm := range vms { - response := new(ListReporter) + response := new(entities.ListReporter) response.Default = vm.Name == cfg.Engine.ActiveService response.Name = vm.Name response.Running = vm.Running @@ -209,25 +195,29 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*ListReporter, error) { return machineResponses, nil } -func toHumanFormat(vms []*machine.ListResponse) ([]*ListReporter, error) { +func toHumanFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, error) { cfg, err := config.ReadCustomConfig() if err != nil { return nil, err } - humanResponses := make([]*ListReporter, 0, len(vms)) + humanResponses := make([]*entities.ListReporter, 0, len(vms)) for _, vm := range vms { - response := new(ListReporter) + response := new(entities.ListReporter) if vm.Name == cfg.Engine.ActiveService { response.Name = vm.Name + "*" response.Default = true } else { response.Name = vm.Name } - if vm.Running { + switch { + case vm.Running: response.LastUp = "Currently running" response.Running = true - } else { + case vm.Starting: + response.LastUp = "Currently starting" + response.Starting = true + default: response.LastUp = units.HumanDuration(time.Since(vm.LastUp)) + " ago" } response.Created = units.HumanDuration(time.Since(vm.CreatedAt)) + " ago" diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go index 5a8a06b9d..0618337cc 100644 --- a/cmd/podman/machine/machine.go +++ b/cmd/podman/machine/machine.go @@ -101,12 +101,6 @@ func resolveEventSock() ([]string, error) { return []string{sock}, nil } - xdg, err := util.GetRuntimeDir() - if err != nil { - logrus.Warnf("Failed to get runtime dir, machine events will not be published: %s", err) - return nil, nil - } - re := regexp.MustCompile(`machine_events.*\.sock`) sockPaths := make([]string, 0) fn := func(path string, info os.DirEntry, err error) error { @@ -125,8 +119,12 @@ func resolveEventSock() ([]string, error) { sockPaths = append(sockPaths, path) return nil } + sockDir, err := eventSockDir() + if err != nil { + logrus.Warnf("Failed to get runtime dir, machine events will not be published: %s", err) + } - if err := filepath.WalkDir(filepath.Join(xdg, "podman"), fn); err != nil { + if err := filepath.WalkDir(sockDir, fn); err != nil { if errors.Is(err, os.ErrNotExist) { return nil, nil } @@ -135,6 +133,14 @@ func resolveEventSock() ([]string, error) { return sockPaths, nil } +func eventSockDir() (string, error) { + xdg, err := util.GetRuntimeDir() + if err != nil { + return "", err + } + return filepath.Join(xdg, "podman"), nil +} + func newMachineEvent(status events.Status, event events.Event) { openEventSock.Do(initMachineEvents) diff --git a/cmd/podman/machine/machine_unix.go b/cmd/podman/machine/machine_unix.go index b56d081ec..a2d9b9d8e 100644 --- a/cmd/podman/machine/machine_unix.go +++ b/cmd/podman/machine/machine_unix.go @@ -4,9 +4,20 @@ package machine import ( + "fmt" "os" + + "github.com/containers/podman/v4/pkg/rootless" + "github.com/spf13/cobra" ) func isUnixSocket(file os.DirEntry) bool { return file.Type()&os.ModeSocket != 0 } + +func rootlessOnly(cmd *cobra.Command, args []string) error { + if !rootless.IsRootless() { + return fmt.Errorf("cannot run command %q as root", cmd.CommandPath()) + } + return nil +} diff --git a/cmd/podman/machine/machine_windows.go b/cmd/podman/machine/machine_windows.go index ffd5d8827..bf1240868 100644 --- a/cmd/podman/machine/machine_windows.go +++ b/cmd/podman/machine/machine_windows.go @@ -3,9 +3,18 @@ package machine import ( "os" "strings" + + "github.com/spf13/cobra" ) func isUnixSocket(file os.DirEntry) bool { // Assume a socket on Windows, since sock mode is not supported yet https://github.com/golang/go/issues/33357 return !file.Type().IsDir() && strings.HasSuffix(file.Name(), ".sock") } + +func rootlessOnly(cmd *cobra.Command, args []string) error { + // Rootless is not relevant on Windows. In the future rootless.IsRootless + // could be switched to return true on Windows, and other codepaths migrated + + return nil +} diff --git a/cmd/podman/machine/rm.go b/cmd/podman/machine/rm.go index a6e66265c..362c9a7d3 100644 --- a/cmd/podman/machine/rm.go +++ b/cmd/podman/machine/rm.go @@ -20,6 +20,7 @@ var ( Use: "rm [options] [MACHINE]", Short: "Remove an existing machine", Long: "Remove a managed virtual machine ", + PersistentPreRunE: rootlessOnly, RunE: rm, Args: cobra.MaximumNArgs(1), Example: `podman machine rm myvm`, diff --git a/cmd/podman/machine/set.go b/cmd/podman/machine/set.go index 5777882da..1b9e1b2bd 100644 --- a/cmd/podman/machine/set.go +++ b/cmd/podman/machine/set.go @@ -18,6 +18,7 @@ var ( Use: "set [options] [NAME]", Short: "Sets a virtual machine setting", Long: "Sets an updatable virtual machine setting", + PersistentPreRunE: rootlessOnly, RunE: setMachine, Args: cobra.MaximumNArgs(1), Example: `podman machine set --rootful=false`, diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index 4a86da67a..cb2f62f51 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -4,22 +4,24 @@ package machine import ( + "fmt" "net/url" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/machine" - "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( sshCmd = &cobra.Command{ - Use: "ssh [options] [NAME] [COMMAND [ARG ...]]", - Short: "SSH into an existing machine", - Long: "SSH into a managed virtual machine ", - RunE: ssh, + Use: "ssh [options] [NAME] [COMMAND [ARG ...]]", + Short: "SSH into an existing machine", + Long: "SSH into a managed virtual machine ", + PersistentPreRunE: rootlessOnly, + RunE: ssh, Example: `podman machine ssh myvm podman machine ssh myvm echo hello`, ValidArgsFunction: autocompleteMachineSSH, @@ -87,9 +89,10 @@ func ssh(cmd *cobra.Command, args []string) error { vm, err = provider.LoadVMByName(vmName) if err != nil { - return errors.Wrapf(err, "vm %s not found", vmName) + return fmt.Errorf("vm %s not found: %w", vmName, err) } - return vm.SSH(vmName, sshOpts) + err = vm.SSH(vmName, sshOpts) + return utils.HandleOSExecError(err) } func remoteConnectionUsername() (string, error) { diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go index c9b99e63b..15a75522a 100644 --- a/cmd/podman/machine/start.go +++ b/cmd/podman/machine/start.go @@ -9,7 +9,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/machine" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -18,6 +17,7 @@ var ( Use: "start [MACHINE]", Short: "Start an existing machine", Long: "Start a managed virtual machine ", + PersistentPreRunE: rootlessOnly, RunE: start, Args: cobra.MaximumNArgs(1), Example: `podman machine start myvm`, @@ -54,9 +54,9 @@ func start(_ *cobra.Command, args []string) error { } if active { if vmName == activeName { - return errors.Wrapf(machine.ErrVMAlreadyRunning, "cannot start VM %s", vmName) + return fmt.Errorf("cannot start VM %s: %w", vmName, machine.ErrVMAlreadyRunning) } - return errors.Wrapf(machine.ErrMultipleActiveVM, "cannot start VM %s. VM %s is currently running", vmName, activeName) + return fmt.Errorf("cannot start VM %s. VM %s is currently running or starting: %w", vmName, activeName, machine.ErrMultipleActiveVM) } fmt.Printf("Starting machine %q\n", vmName) if err := vm.Start(vmName, machine.StartOptions{}); err != nil { diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index 993662792..ce87a44c4 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -17,6 +17,7 @@ var ( Use: "stop [MACHINE]", Short: "Stop an existing machine", Long: "Stop a managed virtual machine ", + PersistentPreRunE: rootlessOnly, RunE: stop, Args: cobra.MaximumNArgs(1), Example: `podman machine stop myvm`, |