mirror of
https://github.com/containers/podman.git
synced 2025-10-19 04:03:23 +08:00
Introduce machine inspect
Allow users to inspect their podman virtual machines. This will be helpful for debug and development alike, because more details about the machine can be collected. Signed-off-by: Brent Baude <bbaude@redhat.com> [NO NEW TESTS NEEDED] Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
90
cmd/podman/machine/inspect.go
Normal file
90
cmd/podman/machine/inspect.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//go:build amd64 || arm64
|
||||||
|
// +build amd64 arm64
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v4/cmd/podman/common"
|
||||||
|
"github.com/containers/podman/v4/cmd/podman/registry"
|
||||||
|
"github.com/containers/podman/v4/cmd/podman/utils"
|
||||||
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
|
"github.com/containers/podman/v4/pkg/machine"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
inspectCmd = &cobra.Command{
|
||||||
|
Use: "inspect [options] [MACHINE...]",
|
||||||
|
Short: "Inspect an existing machine",
|
||||||
|
Long: "Provide details on a managed virtual machine",
|
||||||
|
RunE: inspect,
|
||||||
|
Example: `podman machine inspect myvm`,
|
||||||
|
ValidArgsFunction: autocompleteMachine,
|
||||||
|
}
|
||||||
|
inspectFlag = inspectFlagType{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type inspectFlagType struct {
|
||||||
|
format string
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
|
Command: inspectCmd,
|
||||||
|
Parent: machineCmd,
|
||||||
|
})
|
||||||
|
|
||||||
|
flags := inspectCmd.Flags()
|
||||||
|
formatFlagName := "format"
|
||||||
|
flags.StringVar(&inspectFlag.format, formatFlagName, "", "Format volume output using JSON or a Go template")
|
||||||
|
_ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(machine.InspectInfo{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspect(cmd *cobra.Command, args []string) error {
|
||||||
|
var (
|
||||||
|
errs utils.OutputErrors
|
||||||
|
)
|
||||||
|
if len(args) < 1 {
|
||||||
|
args = append(args, defaultMachineName)
|
||||||
|
}
|
||||||
|
vms := make([]machine.InspectInfo, 0, len(args))
|
||||||
|
provider := getSystemDefaultProvider()
|
||||||
|
for _, vmName := range args {
|
||||||
|
vm, err := provider.LoadVMByName(vmName)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state, err := vm.State()
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ii := machine.InspectInfo{
|
||||||
|
State: state,
|
||||||
|
VM: vm,
|
||||||
|
}
|
||||||
|
vms = append(vms, ii)
|
||||||
|
}
|
||||||
|
if len(inspectFlag.format) > 0 {
|
||||||
|
// need jhonce to work his template magic
|
||||||
|
return define.ErrNotImplemented
|
||||||
|
}
|
||||||
|
if err := printJSON(vms); err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
return errs.PrintErrors()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printJSON(data []machine.InspectInfo) error {
|
||||||
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
// by default, json marshallers will force utf=8 from
|
||||||
|
// a string. this breaks healthchecks that use <,>, &&.
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
return enc.Encode(data)
|
||||||
|
}
|
35
docs/source/markdown/podman-machine-inspect.1.md
Normal file
35
docs/source/markdown/podman-machine-inspect.1.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
% podman-machine-inspect(1)
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
podman\-machine\-inspect - Inspect one or more virtual machines
|
||||||
|
|
||||||
|
## SYNOPSIS
|
||||||
|
**podman machine inspect** [*options] *name* ...
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
|
||||||
|
Inspect one or more virtual machines
|
||||||
|
|
||||||
|
Obtain greater detail about Podman virtual machines. More than one virtual machine can be
|
||||||
|
inspected at once.
|
||||||
|
|
||||||
|
## OPTIONS
|
||||||
|
#### **--format**
|
||||||
|
|
||||||
|
Print results with a Go template.
|
||||||
|
|
||||||
|
#### **--help**
|
||||||
|
|
||||||
|
Print usage statement.
|
||||||
|
|
||||||
|
## EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
$ podman machine inspect podman-machine-default
|
||||||
|
```
|
||||||
|
|
||||||
|
## SEE ALSO
|
||||||
|
**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**
|
||||||
|
|
||||||
|
## HISTORY
|
||||||
|
April 2022, Originally compiled by Brent Baude <bbaude@redhat.com>
|
@ -11,18 +11,19 @@ podman\-machine - Manage Podman's virtual machine
|
|||||||
|
|
||||||
## SUBCOMMANDS
|
## SUBCOMMANDS
|
||||||
|
|
||||||
| Command | Man Page | Description |
|
| Command | Man Page | Description |
|
||||||
| ------- | ------------------------------------------------------- | --------------------------------- |
|
|---------|------------------------------------------------------|-----------------------------------|
|
||||||
| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine |
|
| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine |
|
||||||
| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines |
|
| inspect | [podman-machine-inspect(1)](podman-machine-inspect.1.md) | Inspect one or more virtual machines |
|
||||||
| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine |
|
| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines |
|
||||||
| set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting |
|
| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine |
|
||||||
| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine |
|
| set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting |
|
||||||
| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine |
|
| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine |
|
||||||
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |
|
| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine |
|
||||||
|
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |
|
||||||
|
|
||||||
## SEE ALSO
|
## SEE ALSO
|
||||||
**[podman(1)](podman.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**
|
**[podman(1)](podman.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)**
|
||||||
|
|
||||||
## HISTORY
|
## HISTORY
|
||||||
March 2021, Originally compiled by Ashley Cui <acui@redhat.com>
|
March 2021, Originally compiled by Ashley Cui <acui@redhat.com>
|
||||||
|
@ -33,14 +33,14 @@ type InitOptions struct {
|
|||||||
UID string
|
UID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type QemuMachineStatus = string
|
type Status = string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Running indicates the qemu vm is running.
|
// Running indicates the qemu vm is running.
|
||||||
Running QemuMachineStatus = "running"
|
Running Status = "running"
|
||||||
// Stopped indicates the vm has stopped.
|
// Stopped indicates the vm has stopped.
|
||||||
Stopped QemuMachineStatus = "stopped"
|
Stopped Status = "stopped"
|
||||||
DefaultMachineName string = "podman-machine-default"
|
DefaultMachineName string = "podman-machine-default"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
@ -113,12 +113,15 @@ type RemoveOptions struct {
|
|||||||
SaveIgnition bool
|
SaveIgnition bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InspectOptions struct{}
|
||||||
|
|
||||||
type VM interface {
|
type VM interface {
|
||||||
Init(opts InitOptions) (bool, error)
|
Init(opts InitOptions) (bool, error)
|
||||||
Remove(name string, opts RemoveOptions) (string, func() error, error)
|
Remove(name string, opts RemoveOptions) (string, func() error, error)
|
||||||
Set(name string, opts SetOptions) error
|
Set(name string, opts SetOptions) 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
|
||||||
|
State() (Status, error)
|
||||||
Stop(name string, opts StopOptions) error
|
Stop(name string, opts StopOptions) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +129,10 @@ type DistributionDownload interface {
|
|||||||
HasUsableCache() (bool, error)
|
HasUsableCache() (bool, error)
|
||||||
Get() *Download
|
Get() *Download
|
||||||
}
|
}
|
||||||
|
type InspectInfo struct {
|
||||||
|
State Status
|
||||||
|
VM
|
||||||
|
}
|
||||||
|
|
||||||
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
|
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
|
||||||
//TODO Should this function have input verification?
|
//TODO Should this function have input verification?
|
||||||
|
@ -439,12 +439,12 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
running, err := v.isRunning()
|
state, err := v.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if running {
|
if state == machine.Running {
|
||||||
suffix := ""
|
suffix := ""
|
||||||
if v.Name != machine.DefaultMachineName {
|
if v.Name != machine.DefaultMachineName {
|
||||||
suffix = " " + v.Name
|
suffix = " " + v.Name
|
||||||
@ -581,14 +581,14 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(v.Mounts) > 0 {
|
if len(v.Mounts) > 0 {
|
||||||
running, err := v.isRunning()
|
state, err := v.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
listening := v.isListening()
|
listening := v.isListening()
|
||||||
for !running || !listening {
|
for state != machine.Running || !listening {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
running, err = v.isRunning()
|
state, err = v.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -634,7 +634,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.QemuMachineStatus, error) {
|
func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, error) {
|
||||||
// this is the format returned from the monitor
|
// this is the format returned from the monitor
|
||||||
// {"return": {"status": "running", "singlestep": false, "running": true}}
|
// {"return": {"status": "running", "singlestep": false, "running": true}}
|
||||||
|
|
||||||
@ -748,11 +748,11 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
|
|||||||
disconnected = true
|
disconnected = true
|
||||||
waitInternal := 250 * time.Millisecond
|
waitInternal := 250 * time.Millisecond
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
running, err := v.isRunning()
|
state, err := v.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !running {
|
if state != machine.Running {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(waitInternal)
|
time.Sleep(waitInternal)
|
||||||
@ -800,11 +800,11 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
|
|||||||
)
|
)
|
||||||
|
|
||||||
// cannot remove a running vm unless --force is used
|
// cannot remove a running vm unless --force is used
|
||||||
running, err := v.isRunning()
|
state, err := v.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
if running && !opts.Force {
|
if state == machine.Running && !opts.Force {
|
||||||
return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
|
return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,10 +858,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
|
|||||||
confirmationMessage += "\n"
|
confirmationMessage += "\n"
|
||||||
return confirmationMessage, func() error {
|
return confirmationMessage, func() error {
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if err := os.Remove(f); err != nil {
|
if err := os.Remove(f); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -869,19 +866,19 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *MachineVM) isRunning() (bool, error) {
|
func (v *MachineVM) State() (machine.Status, error) {
|
||||||
// Check if qmp socket path exists
|
// Check if qmp socket path exists
|
||||||
if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
|
if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
|
||||||
return false, nil
|
return "", nil
|
||||||
}
|
}
|
||||||
// Check if we can dial it
|
// Check if we can dial it
|
||||||
monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
|
monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FIXME: this error should probably be returned
|
// FIXME: this error should probably be returned
|
||||||
return false, nil // nolint: nilerr
|
return "", err
|
||||||
}
|
}
|
||||||
if err := monitor.Connect(); err != nil {
|
if err := monitor.Connect(); err != nil {
|
||||||
return false, err
|
return "", err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := monitor.Disconnect(); err != nil {
|
if err := monitor.Disconnect(); err != nil {
|
||||||
@ -889,14 +886,7 @@ func (v *MachineVM) isRunning() (bool, error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// If there is a monitor, lets see if we can query state
|
// If there is a monitor, lets see if we can query state
|
||||||
state, err := v.checkStatus(monitor)
|
return v.checkStatus(monitor)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if state == machine.Running {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *MachineVM) isListening() bool {
|
func (v *MachineVM) isListening() bool {
|
||||||
@ -912,11 +902,11 @@ func (v *MachineVM) isListening() bool {
|
|||||||
// SSH opens an interactive SSH session to the vm specified.
|
// SSH opens an interactive SSH session to the vm specified.
|
||||||
// Added ssh function to VM interface: pkg/machine/config/go : line 58
|
// Added ssh function to VM interface: pkg/machine/config/go : line 58
|
||||||
func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
|
func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
|
||||||
running, err := v.isRunning()
|
state, err := v.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !running {
|
if state != machine.Running {
|
||||||
return errors.Errorf("vm %q is not running.", v.Name)
|
return errors.Errorf("vm %q is not running.", v.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1037,11 +1027,11 @@ func getVMInfos() ([]*machine.ListResponse, error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
listEntry.LastUp = fi.ModTime()
|
listEntry.LastUp = fi.ModTime()
|
||||||
running, err := vm.isRunning()
|
state, err := vm.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if running {
|
if state == machine.Running {
|
||||||
listEntry.Running = true
|
listEntry.Running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
"github.com/containers/podman/v4/pkg/machine"
|
"github.com/containers/podman/v4/pkg/machine"
|
||||||
"github.com/containers/podman/v4/utils"
|
"github.com/containers/podman/v4/utils"
|
||||||
"github.com/containers/storage/pkg/homedir"
|
"github.com/containers/storage/pkg/homedir"
|
||||||
@ -1013,6 +1014,12 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We need to rename isRunning to State(); I do not have a
|
||||||
|
// windows system to test this on.
|
||||||
|
func (v *MachineVM) State() (machine.Status, error) {
|
||||||
|
return "", define.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
func stopWinProxy(v *MachineVM) error {
|
func stopWinProxy(v *MachineVM) error {
|
||||||
pid, tid, tidFile, err := readWinProxyTid(v)
|
pid, tid, tidFile, err := readWinProxyTid(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Reference in New Issue
Block a user