Add podman machine events
Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
parent
1e0c50df38
commit
8da5f3f733
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"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"
|
||||
|
@ -145,11 +146,14 @@ func initMachine(cmd *cobra.Command, args []string) error {
|
|||
// Finished = *, err != nil - Exit with an error message
|
||||
return err
|
||||
}
|
||||
newMachineEvent(events.Init, events.Event{Name: initOpts.Name})
|
||||
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 := ""
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/containers/podman/v4/cmd/podman/common"
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
|
|
@ -4,25 +4,39 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containers/podman/v4/cmd/podman/registry"
|
||||
"github.com/containers/podman/v4/cmd/podman/validate"
|
||||
"github.com/containers/podman/v4/libpod/events"
|
||||
"github.com/containers/podman/v4/pkg/machine"
|
||||
"github.com/containers/podman/v4/pkg/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
noOp = func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
// Pull in configured json library
|
||||
json = registry.JSONLibrary()
|
||||
|
||||
sockPaths []string // Paths to unix domain sockets for publishing
|
||||
openEventSock sync.Once // Singleton support for opening sockets as needed
|
||||
sockets []net.Conn // Opened sockets, if any
|
||||
|
||||
// Command: podman _machine_
|
||||
machineCmd = &cobra.Command{
|
||||
Use: "machine",
|
||||
Short: "Manage a virtual machine",
|
||||
Long: "Manage a virtual machine. Virtual machines are used to run Podman.",
|
||||
PersistentPreRunE: noOp,
|
||||
PersistentPostRunE: noOp,
|
||||
PersistentPreRunE: initMachineEvents,
|
||||
PersistentPostRunE: closeMachineEvents,
|
||||
RunE: validate.SubCommandExists,
|
||||
}
|
||||
)
|
||||
|
@ -64,3 +78,111 @@ func getMachines(toComplete string) ([]string, cobra.ShellCompDirective) {
|
|||
}
|
||||
return suggestions, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func initMachineEvents(cmd *cobra.Command, _ []string) error {
|
||||
logrus.Debugf("Called machine %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " "))
|
||||
|
||||
sockPaths, err := resolveEventSock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// No sockets found, so no need to publish events...
|
||||
if len(sockPaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, path := range sockPaths {
|
||||
conn, err := (&net.Dialer{}).DialContext(registry.Context(), "unix", path)
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to open event socket %q: %v", path, err)
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("Machine event socket %q found", path)
|
||||
sockets = append(sockets, conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveEventSock() ([]string, error) {
|
||||
// Used mostly for testing
|
||||
if sock, found := os.LookupEnv("PODMAN_MACHINE_EVENTS_SOCK"); found {
|
||||
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 {
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case info.IsDir():
|
||||
return nil
|
||||
case info.Type() != os.ModeSocket:
|
||||
return nil
|
||||
case !re.MatchString(info.Name()):
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Machine events will be published on: %q", path)
|
||||
sockPaths = append(sockPaths, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := filepath.WalkDir(filepath.Join(xdg, "podman"), fn); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return sockPaths, nil
|
||||
}
|
||||
|
||||
func newMachineEvent(status events.Status, event events.Event) {
|
||||
openEventSock.Do(func() {
|
||||
// No sockets where found, so no need to publish events...
|
||||
if len(sockPaths) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, path := range sockPaths {
|
||||
conn, err := (&net.Dialer{}).DialContext(registry.Context(), "unix", path)
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to open event socket %q: %v", path, err)
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("Machine event socket %q found", path)
|
||||
sockets = append(sockets, conn)
|
||||
}
|
||||
})
|
||||
|
||||
event.Status = status
|
||||
event.Time = time.Now()
|
||||
event.Type = events.Machine
|
||||
|
||||
payload, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to format machine event: %q", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, sock := range sockets {
|
||||
if _, err := sock.Write(payload); err != nil {
|
||||
logrus.Errorf("Unable to write machine event: %q", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeMachineEvents(cmd *cobra.Command, _ []string) error {
|
||||
logrus.Debugf("Called machine %s.PersistentPostRunE(%s)", cmd.Name(), strings.Join(os.Args, " "))
|
||||
for _, sock := range sockets {
|
||||
_ = sock.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/containers/podman/v4/cmd/podman/registry"
|
||||
"github.com/containers/podman/v4/libpod/events"
|
||||
"github.com/containers/podman/v4/pkg/machine"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -50,7 +51,7 @@ func init() {
|
|||
flags.BoolVar(&destroyOptions.SaveImage, imageFlagName, false, "Do not delete the image file")
|
||||
}
|
||||
|
||||
func rm(cmd *cobra.Command, args []string) error {
|
||||
func rm(_ *cobra.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
vm machine.VM
|
||||
|
@ -83,5 +84,10 @@ func rm(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
return remove()
|
||||
err = remove()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newMachineEvent(events.Remove, events.Event{Name: vmName})
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"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"
|
||||
|
@ -31,7 +32,7 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
func start(cmd *cobra.Command, args []string) error {
|
||||
func start(_ *cobra.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
vm machine.VM
|
||||
|
@ -62,5 +63,6 @@ func start(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
fmt.Printf("Machine %q started successfully\n", vmName)
|
||||
newMachineEvent(events.Start, events.Event{Name: vmName})
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/containers/podman/v4/cmd/podman/registry"
|
||||
"github.com/containers/podman/v4/libpod/events"
|
||||
"github.com/containers/podman/v4/pkg/machine"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -49,5 +50,6 @@ func stop(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
fmt.Printf("Machine %q stopped successfully\n", vmName)
|
||||
newMachineEvent(events.Stop, events.Event{Name: vmName})
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
_ "github.com/containers/podman/v4/cmd/podman/secrets"
|
||||
_ "github.com/containers/podman/v4/cmd/podman/system"
|
||||
_ "github.com/containers/podman/v4/cmd/podman/system/connection"
|
||||
"github.com/containers/podman/v4/cmd/podman/validate"
|
||||
_ "github.com/containers/podman/v4/cmd/podman/volumes"
|
||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||
"github.com/containers/podman/v4/pkg/rootless"
|
||||
|
@ -64,8 +65,8 @@ func parseCommands() *cobra.Command {
|
|||
c.Command.Hidden = true
|
||||
|
||||
// overwrite persistent pre/post function to skip setup
|
||||
c.Command.PersistentPostRunE = noop
|
||||
c.Command.PersistentPreRunE = noop
|
||||
c.Command.PersistentPostRunE = validate.NoOp
|
||||
c.Command.PersistentPreRunE = validate.NoOp
|
||||
addCommand(c)
|
||||
continue
|
||||
}
|
||||
|
@ -120,7 +121,3 @@ func addCommand(c registry.CliCommand) {
|
|||
c.Command.SetUsageTemplate(usageTemplate)
|
||||
c.Command.DisableFlagsInUseLine = true
|
||||
}
|
||||
|
||||
func noop(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,18 +7,15 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// Skip creating engines since this command will obtain connection information to said engines
|
||||
noOp = func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectionCmd skips creating engines (PersistentPreRunE/PersistentPostRunE are No-Op's) since
|
||||
// sub-commands will obtain connection information to said engines
|
||||
ConnectionCmd = &cobra.Command{
|
||||
Use: "connection",
|
||||
Short: "Manage remote ssh destinations",
|
||||
Long: `Manage ssh destination information in podman configuration`,
|
||||
PersistentPreRunE: noOp,
|
||||
Short: "Manage remote API service destinations",
|
||||
Long: `Manage remote API service destination information in podman configuration`,
|
||||
PersistentPreRunE: validate.NoOp,
|
||||
RunE: validate.SubCommandExists,
|
||||
PersistentPostRunE: noOp,
|
||||
PersistentPostRunE: validate.NoOp,
|
||||
TraverseChildren: false,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package validate
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NoOp(_ *cobra.Command, _ []string) error {
|
||||
return nil
|
||||
}
|
|
@ -17,6 +17,8 @@ const (
|
|||
Journald EventerType = iota
|
||||
// Null is a no-op events logger. It does not read or write events.
|
||||
Null EventerType = iota
|
||||
// Memory indicates the event logger will hold events in memory
|
||||
Memory EventerType = iota
|
||||
)
|
||||
|
||||
// Event describes the attributes of a libpod event
|
||||
|
@ -55,7 +57,7 @@ type Details struct {
|
|||
// EventerOptions describe options that need to be passed to create
|
||||
// an eventer
|
||||
type EventerOptions struct {
|
||||
// EventerType describes whether to use journald or a file
|
||||
// EventerType describes whether to use journald, file or memory
|
||||
EventerType string
|
||||
// LogFilePath is the path to where the log file should reside if using
|
||||
// the file logger
|
||||
|
@ -110,6 +112,8 @@ const (
|
|||
System Type = "system"
|
||||
// Volume - event is related to volumes
|
||||
Volume Type = "volume"
|
||||
// Machine - event is related to machine VM's
|
||||
Machine Type = "machine"
|
||||
|
||||
// Attach ...
|
||||
Attach Status = "attach"
|
||||
|
|
|
@ -20,6 +20,8 @@ func (et EventerType) String() string {
|
|||
return "file"
|
||||
case Journald:
|
||||
return "journald"
|
||||
case Memory:
|
||||
return "memory"
|
||||
case Null:
|
||||
return "none"
|
||||
default:
|
||||
|
@ -34,6 +36,8 @@ func IsValidEventer(eventer string) bool {
|
|||
return true
|
||||
case Journald.String():
|
||||
return true
|
||||
case Memory.String():
|
||||
return true
|
||||
case Null.String():
|
||||
return true
|
||||
default:
|
||||
|
@ -41,7 +45,7 @@ func IsValidEventer(eventer string) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// NewEvent creates a event struct and populates with
|
||||
// NewEvent creates an event struct and populates with
|
||||
// the given status and time.
|
||||
func NewEvent(status Status) Event {
|
||||
return Event{
|
||||
|
@ -63,7 +67,7 @@ func (e *Event) ToJSONString() (string, error) {
|
|||
return string(b), err
|
||||
}
|
||||
|
||||
// ToHumanReadable returns human readable event as a formatted string
|
||||
// ToHumanReadable returns human-readable event as a formatted string
|
||||
func (e *Event) ToHumanReadable(truncate bool) string {
|
||||
var humanFormat string
|
||||
id := e.ID
|
||||
|
@ -90,7 +94,7 @@ func (e *Event) ToHumanReadable(truncate bool) string {
|
|||
} else {
|
||||
humanFormat = fmt.Sprintf("%s %s %s", e.Time, e.Type, e.Status)
|
||||
}
|
||||
case Volume:
|
||||
case Volume, Machine:
|
||||
humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name)
|
||||
}
|
||||
return humanFormat
|
||||
|
@ -99,19 +103,19 @@ func (e *Event) ToHumanReadable(truncate bool) string {
|
|||
// NewEventFromString takes stringified json and converts
|
||||
// it to an event
|
||||
func newEventFromJSONString(event string) (*Event, error) {
|
||||
e := Event{}
|
||||
if err := json.Unmarshal([]byte(event), &e); err != nil {
|
||||
e := new(Event)
|
||||
if err := json.Unmarshal([]byte(event), e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// ToString converts a Type to a string
|
||||
// String converts a Type to a string
|
||||
func (t Type) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
// ToString converts a status to a string
|
||||
// String converts a status to a string
|
||||
func (s Status) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
@ -123,6 +127,8 @@ func StringToType(name string) (Type, error) {
|
|||
return Container, nil
|
||||
case Image.String():
|
||||
return Image, nil
|
||||
case Machine.String():
|
||||
return Machine, nil
|
||||
case Network.String():
|
||||
return Network, nil
|
||||
case Pod.String():
|
||||
|
|
|
@ -21,6 +21,8 @@ func NewEventer(options EventerOptions) (Eventer, error) {
|
|||
return EventLogFile{options}, nil
|
||||
case strings.ToUpper(Null.String()):
|
||||
return NewNullEventer(), nil
|
||||
case strings.ToUpper(Memory.String()):
|
||||
return NewMemoryEventer(), nil
|
||||
default:
|
||||
return nil, errors.Errorf("unknown event logger type: %s", strings.ToUpper(options.EventerType))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// EventMemory is the structure for event writing to a channel. It contains the eventer
|
||||
// options and the event itself. Methods for reading and writing are also defined from it.
|
||||
type EventMemory struct {
|
||||
options EventerOptions
|
||||
elements chan *Event
|
||||
}
|
||||
|
||||
// Write event to memory queue
|
||||
func (e EventMemory) Write(event Event) (err error) {
|
||||
e.elements <- &event
|
||||
return
|
||||
}
|
||||
|
||||
// Read event(s) from memory queue
|
||||
func (e EventMemory) Read(ctx context.Context, options ReadOptions) (err error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case event := <-e.elements:
|
||||
options.EventChannel <- event
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns eventer type
|
||||
func (e EventMemory) String() string {
|
||||
return e.options.EventerType
|
||||
}
|
||||
|
||||
// NewMemoryEventer returns configured MemoryEventer
|
||||
func NewMemoryEventer() Eventer {
|
||||
return EventMemory{
|
||||
options: EventerOptions{
|
||||
EventerType: Memory.String(),
|
||||
},
|
||||
elements: make(chan *Event, 100),
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -1063,3 +1064,36 @@ func digShort(container, lookupName string, matchNames []string, p *PodmanTestIn
|
|||
}
|
||||
Fail("dns is not responding")
|
||||
}
|
||||
|
||||
// WaitForFile to be created in defaultWaitTimeout seconds, returns false if file not created
|
||||
func WaitForFile(path string) (err error) {
|
||||
until := time.Now().Add(time.Duration(defaultWaitTimeout) * time.Second)
|
||||
for i := 1; time.Now().Before(until); i++ {
|
||||
_, err = os.Stat(path)
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case errors.Is(err, os.ErrNotExist):
|
||||
time.Sleep(time.Duration(i) * time.Second)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// WaitForService blocks, waiting for some service listening on given host:port
|
||||
func WaitForService(address url.URL) {
|
||||
// Wait for podman to be ready
|
||||
var conn net.Conn
|
||||
var err error
|
||||
for i := 1; i <= 5; i++ {
|
||||
conn, err = net.Dial("tcp", address.Host)
|
||||
if err != nil {
|
||||
// Podman not available yet...
|
||||
time.Sleep(time.Duration(i) * time.Second)
|
||||
}
|
||||
}
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
conn.Close()
|
||||
}
|
||||
|
|
|
@ -122,22 +122,6 @@ var _ = Describe("podman system service", func() {
|
|||
})
|
||||
})
|
||||
|
||||
// WaitForService blocks, waiting for some service listening on given host:port
|
||||
func WaitForService(address url.URL) {
|
||||
// Wait for podman to be ready
|
||||
var conn net.Conn
|
||||
var err error
|
||||
for i := 1; i <= 5; i++ {
|
||||
conn, err = net.Dial("tcp", address.Host)
|
||||
if err != nil {
|
||||
// Podman not available yet...
|
||||
time.Sleep(time.Duration(i) * time.Second)
|
||||
}
|
||||
}
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// randomPort leans on the go net library to find an available port...
|
||||
func randomPort() string {
|
||||
port, err := utils.GetRandomPort()
|
||||
|
|
Loading…
Reference in New Issue