Skip to content

Commit

Permalink
Merge pull request #14762 from ashley-cui/machinfo
Browse files Browse the repository at this point in the history
Podman machine info
  • Loading branch information
openshift-ci[bot] authored Jul 7, 2022
2 parents 1855b63 + 9d6efb3 commit dd0418a
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 9 deletions.
182 changes: 182 additions & 0 deletions cmd/podman/machine/info.go
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 13 additions & 7 deletions cmd/podman/machine/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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)

Expand Down
36 changes: 36 additions & 0 deletions docs/source/markdown/podman-machine-info.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
% podman-machine-info(1)

## NAME
podman\-machine\-info - Display machine host info

## SYNOPSIS
**podman machine info**

## DESCRIPTION

Display information pertaining to the machine host.
Rootless only, as all `podman machine` commands can be only be used with rootless Podman.

## OPTIONS

#### **--format**=*format*, **-f**

Change output format to "json" or a Go template.

#### **--help**

Print usage statement.

## EXAMPLES

```
$ podman machine info
$ podman machine info --format json
$ podman machine info --format {{.Host.Arch}}
```

## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**

## HISTORY
June 2022, Originally compiled by Ashley Cui <[email protected]>
3 changes: 2 additions & 1 deletion docs/source/markdown/podman-machine.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ All `podman machine` commands are rootless only.

| Command | Man Page | Description |
|---------|------------------------------------------------------|-----------------------------------|
| info | [podman-machine-info(1)](podman-machine-info.1.md) | Display machine host info |
| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine |
| inspect | [podman-machine-inspect(1)](podman-machine-inspect.1.md) | Inspect one or more virtual machines |
| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines |
Expand All @@ -30,7 +31,7 @@ All `podman machine` commands are rootless only.
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |

## 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-machine-inspect(1)](podman-machine-inspect.1.md)**
**[podman(1)](podman.1.md)**, **[podman-machine-info(1)](podman-machine-info.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
March 2021, Originally compiled by Ashley Cui <[email protected]>
1 change: 1 addition & 0 deletions pkg/machine/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Provider interface {
IsValidVMName(name string) (bool, error)
CheckExclusiveActiveVM() (bool, string, error)
RemoveAndCleanMachines() error
VMType() string
}

type RemoteConnectionType string
Expand Down
20 changes: 20 additions & 0 deletions pkg/machine/e2e/config_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package e2e

type infoMachine struct {
format string
cmd []string
}

func (i *infoMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"machine", "info"}
if len(i.format) > 0 {
cmd = append(cmd, "--format", i.format)
}
i.cmd = cmd
return cmd
}

func (i *infoMachine) withFormat(format string) *infoMachine {
i.format = format
return i
}
58 changes: 58 additions & 0 deletions pkg/machine/e2e/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package e2e

import (
"github.com/containers/podman/v4/cmd/podman/machine"
jsoniter "github.com/json-iterator/go"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
)

var _ = Describe("podman machine info", func() {
var (
mb *machineTestBuilder
testDir string
)

BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})

It("machine info", func() {
info := new(infoMachine)
infoSession, err := mb.setCmd(info).run()
Expect(err).NotTo(HaveOccurred())
Expect(infoSession).Should(Exit(0))

// Verify go template works and check for no running machines
info = new(infoMachine)
infoSession, err = mb.setCmd(info.withFormat("{{.Host.NumberOfMachines}}")).run()
Expect(err).NotTo(HaveOccurred())
Expect(infoSession).Should(Exit(0))
Expect(infoSession.outputToString()).To(Equal("0"))

// Create a machine and check if info has been updated
i := new(initMachine)
initSession, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(initSession).To(Exit(0))

info = new(infoMachine)
infoSession, err = mb.setCmd(info.withFormat("{{.Host.NumberOfMachines}}")).run()
Expect(err).NotTo(HaveOccurred())
Expect(infoSession).Should(Exit(0))
Expect(infoSession.outputToString()).To(Equal("1"))

// Check if json is in correct format
infoSession, err = mb.setCmd(info.withFormat("json")).run()
Expect(err).NotTo(HaveOccurred())
Expect(infoSession).Should(Exit(0))

infoReport := &machine.Info{}
err = jsoniter.Unmarshal(infoSession.Bytes(), infoReport)
Expect(err).To(BeNil())
})
})
5 changes: 4 additions & 1 deletion pkg/machine/qemu/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,9 @@ func isProcessAlive(pid int) bool {
if err == nil || err == unix.EPERM {
return true
}

return false
}

func (p *Provider) VMType() string {
return vmtype
}
4 changes: 4 additions & 0 deletions pkg/machine/wsl/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -1655,3 +1655,7 @@ func (p *Provider) RemoveAndCleanMachines() error {
}
return prevErr
}

func (p *Provider) VMType() string {
return vmtype
}

0 comments on commit dd0418a

Please sign in to comment.