Add event logging to libpod, even display to podman

In lipod, we now log major events that occurr.  These events
can be displayed using the `podman events` command. Each
event contains:

* Type (container, image, volume, pod...)
* Status (create, rm, stop, kill, ....)
* Timestamp in RFC3339Nano format
* Name (if applicable)
* Image (if applicable)

The format of the event and the varlink endpoint are to not
be considered stable until cockpit has done its enablement.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2019-02-28 14:15:56 -06:00
parent 6421208e0f
commit ca1e76ff63
33 changed files with 1173 additions and 64 deletions

View File

@@ -53,6 +53,15 @@ type ImagesValues struct {
Sort string
}
type EventValues struct {
PodmanCommand
Filter []string
Format string
Since string
Stream bool
Until string
}
type TagValues struct {
PodmanCommand
}

48
cmd/podman/events.go Normal file
View File

@@ -0,0 +1,48 @@
package main
import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
eventsCommand cliconfig.EventValues
eventsDescription = "Monitor podman events"
_eventsCommand = &cobra.Command{
Use: "events [flags]",
Short: "show podman events",
Long: eventsDescription,
RunE: func(cmd *cobra.Command, args []string) error {
eventsCommand.InputArgs = args
eventsCommand.GlobalFlags = MainGlobalOpts
return eventsCmd(&eventsCommand)
},
Example: `podman events
podman events --filter event=create
podman events --since 1h30s`,
}
)
func init() {
eventsCommand.Command = _eventsCommand
eventsCommand.SetUsageTemplate(UsageTemplate())
flags := eventsCommand.Flags()
flags.StringArrayVar(&eventsCommand.Filter, "filter", []string{}, "filter output")
flags.StringVar(&eventsCommand.Format, "format", "", "format the output using a Go template")
flags.BoolVar(&eventsCommand.Stream, "stream", true, "stream new events; for testing only")
flags.StringVar(&eventsCommand.Since, "since", "", "show all events created since timestamp")
flags.StringVar(&eventsCommand.Until, "until", "", "show all events until timestamp")
flags.MarkHidden("stream")
}
func eventsCmd(c *cliconfig.EventValues) error {
runtime, err := adapter.GetRuntime(&c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.Shutdown(false)
return runtime.Events(c)
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/logs"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -70,7 +71,7 @@ func logsCmd(c *cliconfig.LogsValues) error {
sinceTime := time.Time{}
if c.Flag("since").Changed {
// parse time, error out if something is wrong
since, err := parseInputTime(c.Since)
since, err := util.ParseInputTime(c.Since)
if err != nil {
return errors.Wrapf(err, "could not parse time: %q", c.Since)
}
@@ -112,25 +113,3 @@ func logsCmd(c *cliconfig.LogsValues) error {
}
return logs.ReadLogs(logPath, ctr, opts)
}
// parseInputTime takes the users input and to determine if it is valid and
// returns a time format and error. The input is compared to known time formats
// or a duration which implies no-duration
func parseInputTime(inputTime string) (time.Time, error) {
timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999",
"2006-01-02Z07:00", "2006-01-02"}
// iterate the supported time formats
for _, tf := range timeFormats {
t, err := time.Parse(tf, inputTime)
if err == nil {
return t, nil
}
}
// input might be a duration
duration, err := time.ParseDuration(inputTime)
if err != nil {
return time.Time{}, errors.Errorf("unable to interpret time value")
}
return time.Now().Add(-duration), nil
}

View File

@@ -36,6 +36,7 @@ var (
// implemented.
var mainCommands = []*cobra.Command{
_buildCommand,
_eventsCommand,
_exportCommand,
_historyCommand,
&_imagesCommand,

115
cmd/podman/shared/events.go Normal file
View File

@@ -0,0 +1,115 @@
package shared
import (
"fmt"
"strings"
"time"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
)
func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool, error) {
switch strings.ToUpper(filter) {
case "CONTAINER":
return func(e *events.Event) bool {
if e.Type != events.Container {
return false
}
if e.Name == filterValue {
return true
}
return strings.HasPrefix(e.ID, filterValue)
}, nil
case "EVENT", "STATUS":
return func(e *events.Event) bool {
return fmt.Sprintf("%s", e.Status) == filterValue
}, nil
case "IMAGE":
return func(e *events.Event) bool {
if e.Type != events.Image {
return false
}
if e.Name == filterValue {
return true
}
return strings.HasPrefix(e.ID, filterValue)
}, nil
case "POD":
return func(e *events.Event) bool {
if e.Type != events.Pod {
return false
}
if e.Name == filterValue {
return true
}
return strings.HasPrefix(e.ID, filterValue)
}, nil
case "VOLUME":
return func(e *events.Event) bool {
if e.Type != events.Volume {
return false
}
return strings.HasPrefix(e.ID, filterValue)
}, nil
case "TYPE":
return func(e *events.Event) bool {
return fmt.Sprintf("%s", e.Type) == filterValue
}, nil
}
return nil, errors.Errorf("%s is an invalid filter", filter)
}
func generateEventSinceOption(timeSince time.Time) func(e *events.Event) bool {
return func(e *events.Event) bool {
return e.Time.After(timeSince)
}
}
func generateEventUntilOption(timeUntil time.Time) func(e *events.Event) bool {
return func(e *events.Event) bool {
return e.Time.Before(timeUntil)
}
}
func parseFilter(filter string) (string, string, error) {
filterSplit := strings.Split(filter, "=")
if len(filterSplit) != 2 {
return "", "", errors.Errorf("%s is an invalid filter", filter)
}
return filterSplit[0], filterSplit[1], nil
}
func GenerateEventOptions(filters []string, since, until string) ([]events.EventFilter, error) {
var options []events.EventFilter
for _, filter := range filters {
key, val, err := parseFilter(filter)
if err != nil {
return nil, err
}
funcFilter, err := generateEventFilter(key, val)
if err != nil {
return nil, err
}
options = append(options, funcFilter)
}
if len(since) > 0 {
timeSince, err := util.ParseInputTime(since)
if err != nil {
return nil, errors.Wrapf(err, "unable to convert since time of %s", since)
}
options = append(options, generateEventSinceOption(timeSince))
}
if len(until) > 0 {
timeUntil, err := util.ParseInputTime(until)
if err != nil {
return nil, errors.Wrapf(err, "unable to convert until time of %s", until)
}
options = append(options, generateEventUntilOption(timeUntil))
}
return options, nil
}

View File

@@ -435,6 +435,23 @@ type Runlabel(
opts: [string]string
)
# Event describes a libpod struct
type Event(
# TODO: make status and type a enum at some point?
# id is the container, volume, pod, image ID
id: string,
# image is the image name where applicable
image: string,
# name is the name of the pod, container, image
name: string,
# status describes the event that happened (i.e. create, remove, ...)
status: string,
# time the event happened
time: string,
# type describes object the event happened with (image, container...)
type: string
)
# GetVersion returns version and build information of the podman service
method GetVersion() -> (
version: string,
@@ -1123,6 +1140,9 @@ method GetPodsByContext(all: bool, latest: bool, args: []string) -> (pods: []str
# LoadImage allows you to load an image into local storage from a tarball.
method LoadImage(name: string, inputFile: string, quiet: bool, deleteFile: bool) -> (reply: MoreResponse)
# GetEvents returns known libpod events filtered by the options provided.
method GetEvents(filter: []string, since: string, stream: bool, until: string) -> (events: Event)
# ImageNotFound means the image could not be found by the provided name or ID in local storage.
error ImageNotFound (id: string, reason: string)
@@ -1152,3 +1172,6 @@ error ErrorOccurred (reason: string)
# RuntimeErrors generally means a runtime could not be found or gotten.
error RuntimeError (reason: string)
# The Podman endpoint requires that you use a streaming connection.
error WantsMoreRequired (reason: string)