mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-08-10 19:05:42 +00:00
The events code makes use of two channels, one for the events and one
for the resulting error. Then in the main file we have a loop reading
from both channels that should exit on first error it gets.
However in case the event channel is closed before the error channel
cotains the error it could caused an early exit as it looked like all
events were done. Commit c46884aa93 fixed that somewhat by checking for
an error in the error channel before exiting. This however was still
racy as it added a default case in the select which means the channel
check is non blocking. Thus the error was not yet send into the channel.
To fix this we should make it a blocking read to wait for the error in
the channel. Also the err != nil check can be removed as we either
return err or nil anyway.
And as last step make sure the error channel is closed, that prevents us
from blocking forever in case the main select already processed the nil
error.
Fixes #23165
Signed-off-by: Paul Holzinger <pholzing@redhat.com>
200 lines
5.9 KiB
Go
200 lines
5.9 KiB
Go
package system
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/containers/common/pkg/completion"
|
|
"github.com/containers/common/pkg/report"
|
|
"github.com/containers/podman/v5/cmd/podman/common"
|
|
"github.com/containers/podman/v5/cmd/podman/registry"
|
|
"github.com/containers/podman/v5/cmd/podman/validate"
|
|
"github.com/containers/podman/v5/libpod/events"
|
|
"github.com/containers/podman/v5/pkg/domain/entities"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
eventsDescription = `Monitor podman system events.
|
|
|
|
By default, streaming mode is used, printing new events as they occur. Previous events can be listed via --since and --until.`
|
|
eventsCommand = &cobra.Command{
|
|
Use: "events [options]",
|
|
Args: validate.NoArgs,
|
|
Short: "Show podman system events",
|
|
Long: eventsDescription,
|
|
RunE: eventsCmd,
|
|
ValidArgsFunction: completion.AutocompleteNone,
|
|
Example: `podman events
|
|
podman events --filter event=create
|
|
podman events --format {{.Image}}
|
|
podman events --since 1h30s`,
|
|
}
|
|
|
|
systemEventsCommand = &cobra.Command{
|
|
Args: eventsCommand.Args,
|
|
Use: eventsCommand.Use,
|
|
Short: eventsCommand.Short,
|
|
Long: eventsCommand.Long,
|
|
RunE: eventsCommand.RunE,
|
|
ValidArgsFunction: eventsCommand.ValidArgsFunction,
|
|
Example: `podman system events`,
|
|
}
|
|
)
|
|
|
|
var (
|
|
eventOptions entities.EventsOptions
|
|
eventFormat string
|
|
noTrunc bool
|
|
)
|
|
|
|
type Event struct {
|
|
// containerExitCode is for storing the exit code of a container which can
|
|
// be used for "internal" event notification
|
|
ContainerExitCode *int `json:",omitempty"`
|
|
// ID can be for the container, image, volume, etc
|
|
ID string `json:",omitempty"`
|
|
// Image used where applicable
|
|
Image string `json:",omitempty"`
|
|
// Name where applicable
|
|
Name string `json:",omitempty"`
|
|
// Network is the network name in a network event
|
|
Network string `json:"network,omitempty"`
|
|
// Status describes the event that occurred
|
|
Status events.Status
|
|
// Time the event occurred
|
|
Time int64 `json:"time,omitempty"`
|
|
// timeNano the event occurred in nanoseconds
|
|
TimeNano int64 `json:"timeNano,omitempty"`
|
|
// Type of event that occurred
|
|
Type events.Type
|
|
// Health status of the current container
|
|
HealthStatus string `json:"health_status,omitempty"`
|
|
// Error code for certain events involving errors.
|
|
Error string `json:",omitempty"`
|
|
|
|
events.Details
|
|
}
|
|
|
|
func newEventFromLibpodEvent(e *events.Event) Event {
|
|
return Event{
|
|
ContainerExitCode: e.ContainerExitCode,
|
|
ID: e.ID,
|
|
Image: e.Image,
|
|
Name: e.Name,
|
|
Network: e.Network,
|
|
Status: e.Status,
|
|
Time: e.Time.Unix(),
|
|
Type: e.Type,
|
|
HealthStatus: e.HealthStatus,
|
|
Details: e.Details,
|
|
TimeNano: e.Time.UnixNano(),
|
|
Error: e.Error,
|
|
}
|
|
}
|
|
|
|
func (e *Event) ToJSONString() (string, error) {
|
|
b, err := json.Marshal(e)
|
|
return string(b), err
|
|
}
|
|
|
|
func init() {
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Command: systemEventsCommand,
|
|
Parent: systemCmd,
|
|
})
|
|
eventsFlags(systemEventsCommand)
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Command: eventsCommand,
|
|
})
|
|
eventsFlags(eventsCommand)
|
|
}
|
|
|
|
func eventsFlags(cmd *cobra.Command) {
|
|
flags := cmd.Flags()
|
|
|
|
filterFlagName := "filter"
|
|
flags.StringArrayVarP(&eventOptions.Filter, filterFlagName, "f", []string{}, "filter output")
|
|
_ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteEventFilter)
|
|
|
|
formatFlagName := "format"
|
|
flags.StringVar(&eventFormat, formatFlagName, "", "format the output using a Go template")
|
|
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&Event{}))
|
|
|
|
flags.BoolVar(&eventOptions.Stream, "stream", true, "stream events and do not exit when returning the last known event")
|
|
|
|
sinceFlagName := "since"
|
|
flags.StringVar(&eventOptions.Since, sinceFlagName, "", "show all events created since timestamp")
|
|
_ = cmd.RegisterFlagCompletionFunc(sinceFlagName, completion.AutocompleteNone)
|
|
|
|
flags.BoolVar(&noTrunc, "no-trunc", true, "do not truncate the output")
|
|
|
|
untilFlagName := "until"
|
|
flags.StringVar(&eventOptions.Until, untilFlagName, "", "show all events until timestamp")
|
|
_ = cmd.RegisterFlagCompletionFunc(untilFlagName, completion.AutocompleteNone)
|
|
}
|
|
|
|
func eventsCmd(cmd *cobra.Command, _ []string) error {
|
|
if len(eventOptions.Since) > 0 || len(eventOptions.Until) > 0 {
|
|
eventOptions.FromStart = true
|
|
}
|
|
eventChannel := make(chan *events.Event, 1)
|
|
eventOptions.EventChan = eventChannel
|
|
errChannel := make(chan error)
|
|
|
|
var (
|
|
rpt *report.Formatter
|
|
doJSON bool
|
|
)
|
|
|
|
if cmd.Flags().Changed("format") {
|
|
doJSON = report.IsJSON(eventFormat)
|
|
if !doJSON {
|
|
var err error
|
|
// Use OriginUnknown so it does not add an extra range since it
|
|
// will only be called for each single element and not a slice.
|
|
rpt, err = report.New(os.Stdout, cmd.Name()).Parse(report.OriginUnknown, eventFormat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
go func() {
|
|
errChannel <- registry.ContainerEngine().Events(context.Background(), eventOptions)
|
|
close(errChannel)
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case event, ok := <-eventChannel:
|
|
if !ok {
|
|
// channel was closed we can exit
|
|
// read the error channel blocking to make sure we are not missing any errors (#23165)
|
|
return <-errChannel
|
|
}
|
|
switch {
|
|
case doJSON:
|
|
e := newEventFromLibpodEvent(event)
|
|
jsonStr, err := e.ToJSONString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(jsonStr)
|
|
case cmd.Flags().Changed("format"):
|
|
if err := rpt.Execute(newEventFromLibpodEvent(event)); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
fmt.Println(event.ToHumanReadable(!noTrunc))
|
|
}
|
|
case err := <-errChannel:
|
|
// only exit in case of an error,
|
|
// otherwise keep reading events until the event channel is closed
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|