diff options
-rw-r--r-- | cmd/podman/machine/init.go | 4 | ||||
-rw-r--r-- | cmd/podman/machine/inspect.go | 1 | ||||
-rw-r--r-- | cmd/podman/machine/list.go | 1 | ||||
-rw-r--r-- | cmd/podman/machine/machine.go | 132 | ||||
-rw-r--r-- | cmd/podman/machine/rm.go | 10 | ||||
-rw-r--r-- | cmd/podman/machine/start.go | 4 | ||||
-rw-r--r-- | cmd/podman/machine/stop.go | 2 | ||||
-rw-r--r-- | cmd/podman/main.go | 9 | ||||
-rw-r--r-- | cmd/podman/system/connection.go | 15 | ||||
-rw-r--r-- | cmd/podman/validate/noop.go | 9 | ||||
-rw-r--r-- | libpod/events/config.go | 6 | ||||
-rw-r--r-- | libpod/events/events.go | 22 | ||||
-rw-r--r-- | libpod/events/events_linux.go | 2 | ||||
-rw-r--r-- | libpod/events/memory.go | 49 | ||||
-rw-r--r-- | test/e2e/common_test.go | 34 | ||||
-rw-r--r-- | test/e2e/system_service_test.go | 16 |
16 files changed, 266 insertions, 50 deletions
diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 4991c6aa3..733266b85 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -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 := "" diff --git a/cmd/podman/machine/inspect.go b/cmd/podman/machine/inspect.go index 21e5074b7..0ddcec8b5 100644 --- a/cmd/podman/machine/inspect.go +++ b/cmd/podman/machine/inspect.go @@ -4,7 +4,6 @@ package machine import ( - "encoding/json" "os" "github.com/containers/podman/v4/cmd/podman/common" diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index c987bf71a..861feee07 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -4,7 +4,6 @@ package machine import ( - "encoding/json" "os" "sort" "strconv" diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go index d3775f022..4c566b11f 100644 --- a/cmd/podman/machine/machine.go +++ b/cmd/podman/machine/machine.go @@ -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 +} diff --git a/cmd/podman/machine/rm.go b/cmd/podman/machine/rm.go index 617a70a76..e678eb629 100644 --- a/cmd/podman/machine/rm.go +++ b/cmd/podman/machine/rm.go @@ -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 } diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go index 56acb09cb..2b0c8213d 100644 --- a/cmd/podman/machine/start.go +++ b/cmd/podman/machine/start.go @@ -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 } diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index e6bf3cf2b..dcb124dd4 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -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 } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 8f580601e..929c8a757 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -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 -} diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go index 5164de78c..5dbe50fc9 100644 --- a/cmd/podman/system/connection.go +++ b/cmd/podman/system/connection.go @@ -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, } ) diff --git a/cmd/podman/validate/noop.go b/cmd/podman/validate/noop.go new file mode 100644 index 000000000..2243ef5c4 --- /dev/null +++ b/cmd/podman/validate/noop.go @@ -0,0 +1,9 @@ +package validate + +import ( + "github.com/spf13/cobra" +) + +func NoOp(_ *cobra.Command, _ []string) error { + return nil +} diff --git a/libpod/events/config.go b/libpod/events/config.go index 35680a275..00cdca007 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -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" diff --git a/libpod/events/events.go b/libpod/events/events.go index 1745095fb..04417fd8d 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -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(): diff --git a/libpod/events/events_linux.go b/libpod/events/events_linux.go index 482d7d6dd..4320f2190 100644 --- a/libpod/events/events_linux.go +++ b/libpod/events/events_linux.go @@ -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)) } diff --git a/libpod/events/memory.go b/libpod/events/memory.go new file mode 100644 index 000000000..b3e03d86b --- /dev/null +++ b/libpod/events/memory.go @@ -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), + } +} diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index a61ef8640..28991af7f 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -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() +} diff --git a/test/e2e/system_service_test.go b/test/e2e/system_service_test.go index 2bc7756d6..4c16ea788 100644 --- a/test/e2e/system_service_test.go +++ b/test/e2e/system_service_test.go @@ -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() |