mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-10-06 14:07:50 +00:00
One of the problems with the Events() API was that you had to call it in
a new goroutine. This meant the the error returned by it had to be read
back via a second channel. This cuased other bugs in the past but here
the biggest problem is that basic errors such as invalid since/until
options were not directly returned to the caller.
It meant in the API we were not able to write http code 200 quickly
because we always waited for the first event or error from the
channels. This in turn made some clients not happy as they assume the
server hangs on time out if no such events are generated.
To fix this we resturcture the entire event flow. First we spawn the
goroutine inside the eventer Read() function so not all the callers have
to. Then we can return the basic error quickly without the goroutine.
The caller then checks the error like any normal function and the API
can use this one to decide which status code to return.
Second we now return errors/event in one channel then the callers can
decide to ignore or log them which makes it a bit more clear.
Fixes c46884aa93 ("podman events: check for an error after we finish reading events")
Fixes #23712
Signed-off-by: Paul Holzinger <pholzing@redhat.com>
191 lines
5.6 KiB
Go
191 lines
5.6 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/sirupsen/logrus"
|
|
"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.ReadResult, 1)
|
|
eventOptions.EventChan = eventChannel
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
err := registry.ContainerEngine().Events(context.Background(), eventOptions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for evt := range eventChannel {
|
|
if evt.Error != nil {
|
|
logrus.Errorf("Failed to read event: %v", evt.Error)
|
|
continue
|
|
}
|
|
switch {
|
|
case doJSON:
|
|
e := newEventFromLibpodEvent(evt.Event)
|
|
jsonStr, err := e.ToJSONString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(jsonStr)
|
|
case cmd.Flags().Changed("format"):
|
|
if err := rpt.Execute(newEventFromLibpodEvent(evt.Event)); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
fmt.Println(evt.Event.ToHumanReadable(!noTrunc))
|
|
}
|
|
}
|
|
return nil
|
|
}
|