mirror of
https://github.com/containers/podman.git
synced 2025-07-15 03:02:52 +08:00
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:

committed by
Paul Holzinger

parent
59b6f48d90
commit
a3a1b44c31
@ -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,
|
||||
|
@ -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, ...) |
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user