libpod/events: Update event time format and add timeNano

Add new event type in cmd/podman to better match the docker format.

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>
Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
AhmedGrati
2023-12-16 14:06:34 +01:00
committed by Paul Holzinger
parent 59b6f48d90
commit a3a1b44c31
5 changed files with 146 additions and 31 deletions

View File

@ -2,8 +2,10 @@ package system
import (
"context"
jsonencoding "encoding/json"
"fmt"
"os"
"time"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/report"
@ -12,6 +14,7 @@ import (
"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/containers/storage/pkg/stringid"
"github.com/spf13/cobra"
)
@ -49,6 +52,97 @@ var (
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"`
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(),
}
}
func (e *Event) ToJSONString() (string, error) {
b, err := jsonencoding.Marshal(e)
return string(b), err
}
func (e *Event) ToHumanReadable(truncate bool) string {
if e == nil {
return ""
}
var humanFormat string
id := e.ID
if truncate {
id = stringid.TruncateID(id)
}
timeUnix := time.Unix(0, e.TimeNano)
switch e.Type {
case events.Container, events.Pod:
humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s", timeUnix, e.Type, e.Status, id, e.Image, e.Name)
if e.PodID != "" {
humanFormat += fmt.Sprintf(", pod_id=%s", e.PodID)
}
if e.HealthStatus != "" {
humanFormat += fmt.Sprintf(", health_status=%s", e.HealthStatus)
}
// check if the container has labels and add it to the output
if len(e.Attributes) > 0 {
for k, v := range e.Attributes {
humanFormat += fmt.Sprintf(", %s=%s", k, v)
}
}
humanFormat += ")"
case events.Network:
humanFormat = fmt.Sprintf("%s %s %s %s (container=%s, name=%s)", timeUnix, e.Type, e.Status, id, id, e.Network)
case events.Image:
humanFormat = fmt.Sprintf("%s %s %s %s %s", timeUnix, e.Type, e.Status, id, e.Name)
case events.System:
if e.Name != "" {
humanFormat = fmt.Sprintf("%s %s %s %s", timeUnix, e.Type, e.Status, e.Name)
} else {
humanFormat = fmt.Sprintf("%s %s %s", timeUnix, e.Type, e.Status)
}
case events.Volume, events.Machine:
humanFormat = fmt.Sprintf("%s %s %s %s", timeUnix, e.Type, e.Status, e.Name)
}
return humanFormat
}
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: systemEventsCommand,
@ -70,7 +164,7 @@ func eventsFlags(cmd *cobra.Command) {
formatFlagName := "format"
flags.StringVar(&eventFormat, formatFlagName, "", "format the output using a Go template")
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&events.Event{}))
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&Event{}))
flags.BoolVar(&eventOptions.Stream, "stream", true, "stream events and do not exit when returning the last known event")
@ -123,9 +217,10 @@ func eventsCmd(cmd *cobra.Command, _ []string) error {
// channel was closed we can exit
return nil
}
e := newEventFromLibpodEvent(*event)
switch {
case doJSON:
jsonStr, err := event.ToJSONString()
jsonStr, err := e.ToJSONString()
if err != nil {
return err
}
@ -135,7 +230,7 @@ func eventsCmd(cmd *cobra.Command, _ []string) error {
return err
}
default:
fmt.Println(event.ToHumanReadable(!noTrunc))
fmt.Println(e.ToHumanReadable(!noTrunc))
}
case err := <-errChannel:
// only exit in case of an error,

View File

@ -105,7 +105,7 @@ In the case where an ID is used, the ID may be in its full or shortened form. T
Format the output to JSON Lines or using the given Go template.
| **Placeholder** | **Description** |
|-------------------------|-----------------------------------------------|
|-------------------------|----------------------------------------------- ---|
| .Attributes ... | created_at, _by, labels, and more (map[]) |
| .ContainerExitCode | Exit code (int) |
| .ContainerInspectData | Payload of the container's inspect |
@ -117,6 +117,7 @@ Format the output to JSON Lines or using the given Go template.
| .PodID | ID of pod associated with container, if any |
| .Status | Event status (e.g., create, start, died, ...) |
| .Time ... | Event timestamp (string) |
| .TimeNano | Event timestamp with nanosecond precision (int64) |
| .ToHumanReadable *bool* | If true, truncates CID in output |
| .Type | Event type (e.g., image, container, pod, ...) |

View File

@ -7,7 +7,7 @@ import (
"sync"
"time"
"github.com/containers/podman/v5/libpod/events"
"github.com/containers/podman/v5/cmd/podman/system"
. "github.com/containers/podman/v5/test/utils"
"github.com/containers/storage/pkg/stringid"
. "github.com/onsi/ginkgo/v2"
@ -119,7 +119,10 @@ var _ = Describe("Podman events", func() {
})
It("podman events format", func() {
_, ec, _ := podmanTest.RunLsContainer("")
start := time.Now()
ctrName := "testCtr"
_, ec, _ := podmanTest.RunLsContainer(ctrName)
end := time.Now()
Expect(ec).To(Equal(0))
test := podmanTest.Podman([]string{"events", "--stream=false", "--format", "json"})
@ -129,21 +132,37 @@ var _ = Describe("Podman events", func() {
jsonArr := test.OutputToStringArray()
Expect(test.OutputToStringArray()).ShouldNot(BeEmpty())
event := events.Event{}
event := system.Event{}
err := json.Unmarshal([]byte(jsonArr[0]), &event)
Expect(err).ToNot(HaveOccurred())
test = podmanTest.Podman([]string{"events", "--stream=false", "--format", "{{json.}}"})
test = podmanTest.Podman([]string{
"events",
"--stream=false",
"--since", strconv.FormatInt(start.Unix(), 10),
"--filter", fmt.Sprintf("container=%s", ctrName),
"--format", "{{json.}}",
})
test.WaitWithDefaultTimeout()
Expect(test).To(ExitCleanly())
jsonArr = test.OutputToStringArray()
Expect(test.OutputToStringArray()).ShouldNot(BeEmpty())
event = events.Event{}
event = system.Event{}
err = json.Unmarshal([]byte(jsonArr[0]), &event)
Expect(err).ToNot(HaveOccurred())
Expect(event.Time).To(BeNumerically(">=", start.Unix()))
Expect(event.Time).To(BeNumerically("<=", end.Unix()))
Expect(event.TimeNano).To(BeNumerically(">=", start.UnixNano()))
Expect(event.TimeNano).To(BeNumerically("<=", end.UnixNano()))
Expect(time.Unix(0, event.TimeNano).Unix()).To(BeEquivalentTo(event.Time))
date := time.Unix(0, event.TimeNano).Format("2006-01-02")
Expect(event.ToHumanReadable(false)).To(HavePrefix(date))
test = podmanTest.Podman([]string{"events", "--stream=false", "--filter=type=container", "--format", "ID: {{.ID}}"})
test.WaitWithDefaultTimeout()
Expect(test).To(ExitCleanly())

View File

@ -223,9 +223,9 @@ EOF
# same amount of events. We checked the contents before.
CONTAINERS_CONF_OVERRIDE=$containersConf run_podman events --stream=false --since="2022-03-06T11:26:42.723667984+02:00" --format=json
assert "${#lines[@]}" = 52 "Number of events returned"
is "${lines[0]}" "{\"Name\":\"$eventsFile\",\"Status\":\"log-rotation\",\"Time\":\".*\",\"Type\":\"system\",\"Attributes\":{\"io.podman.event.rotate\":\"begin\"}}"
is "${lines[-2]}" "{\"Name\":\"$eventsFile\",\"Status\":\"log-rotation\",\"Time\":\".*\",\"Type\":\"system\",\"Attributes\":{\"io.podman.event.rotate\":\"end\"}}"
is "${lines[-1]}" "{\"ID\":\"$ctrID\",\"Image\":\"$IMAGE\",\"Name\":\".*\",\"Status\":\"remove\",\"Time\":\".*\",\"Type\":\"container\",\"Attributes\":{.*}}"
is "${lines[0]}" "{\"Name\":\"$eventsFile\",\"Status\":\"log-rotation\",\"time\":[0-9]\+,\"timeNano\":[0-9]\+,\"Type\":\"system\",\"Attributes\":{\"io.podman.event.rotate\":\"begin\"}}"
is "${lines[-2]}" "{\"Name\":\"$eventsFile\",\"Status\":\"log-rotation\",\"time\":[0-9]\+,\"timeNano\":[0-9]\+,\"Type\":\"system\",\"Attributes\":{\"io.podman.event.rotate\":\"end\"}}"
is "${lines[-1]}" "{\"ID\":\"$ctrID\",\"Image\":\"$IMAGE\",\"Name\":\".*\",\"Status\":\"remove\",\"time\":[0-9]\+,\"timeNano\":[0-9]\+,\"Type\":\"container\",\"Attributes\":{.*}}"
}
@test "events log-file no duplicates" {
@ -292,10 +292,10 @@ EOF
# Make sure that the JSON stream looks as expected. That means it has all
# events and no duplicates.
run cat $eventsJSON
is "${lines[0]}" "{\"Name\":\"busybox\",\"Status\":\"pull\",\"Time\":\"2022-04-06T11:26:42.7236679+02:00\",\"Type\":\"image\",\"Attributes\":null}"
is "${lines[99]}" "{\"Name\":\"busybox\",\"Status\":\"pull\",\"Time\":\"2022-04-06T11:26:42.723667999+02:00\",\"Type\":\"image\",\"Attributes\":null}"
is "${lines[100]}" "{\"Name\":\"$eventsFile\",\"Status\":\"log-rotation\",\"Time\":\".*\",\"Type\":\"system\",\"Attributes\":{\"io.podman.event.rotate\":\"end\"}}"
is "${lines[103]}" "{\"ID\":\"$ctrID\",\"Image\":\"$IMAGE\",\"Name\":\".*\",\"Status\":\"remove\",\"Time\":\".*\",\"Type\":\"container\",\"Attributes\":{.*}}"
is "${lines[0]}" "{\"Name\":\"busybox\",\"Status\":\"pull\",\"time\":1649237202,\"timeNano\":1649237202723[0-9]\+,\"Type\":\"image\",\"Attributes\":null}"
is "${lines[99]}" "{\"Name\":\"busybox\",\"Status\":\"pull\",\"time\":1649237202,\"timeNano\":1649237202723[0-9]\+,\"Type\":\"image\",\"Attributes\":null}"
is "${lines[100]}" "{\"Name\":\"$eventsFile\",\"Status\":\"log-rotation\",\"time\":[0-9]\+,\"timeNano\":[0-9]\+,\"Type\":\"system\",\"Attributes\":{\"io.podman.event.rotate\":\"end\"}}"
is "${lines[103]}" "{\"ID\":\"$ctrID\",\"Image\":\"$IMAGE\",\"Name\":\".*\",\"Status\":\"remove\",\"time\":[0-9]\+,\"timeNano\":[0-9]\+,\"Type\":\"container\",\"Attributes\":{.*}}"
}
# Prior to #15633, container labels would not appear in 'die' log events