diff --git a/docs/source/markdown/podman-events.1.md b/docs/source/markdown/podman-events.1.md index 9eb898c93d6..f7060339e37 100644 --- a/docs/source/markdown/podman-events.1.md +++ b/docs/source/markdown/podman-events.1.md @@ -83,6 +83,13 @@ The *secret* type reports the following statuses: * create * remove + The *artifact* type reports the following statuses: + * add + * extract + * pull + * push + * remove + #### Verbose Create Events Setting `events_container_create_inspect_data=true` in containers.conf(5) instructs Podman to create more verbose container-create events which include a JSON payload with detailed information about the containers. The JSON payload is identical to the one of podman-container-inspect(1). The associated field in journald is named `PODMAN_CONTAINER_INSPECT_DATA`. diff --git a/libpod/events.go b/libpod/events.go index aac29ee5793..137cd7acc98 100644 --- a/libpod/events.go +++ b/libpod/events.go @@ -201,6 +201,18 @@ func (r *Runtime) NewSecretEvent(status events.Status, secretID string) { } } +// NewArtifactEvent creates a new event for a libpod artifact +func (r *Runtime) NewArtifactEvent(status events.Status, name, digest string, attr map[string]string) { + e := events.NewEvent(status) + e.Type = events.Artifact + e.Name = name + e.ID = digest + e.Attributes = attr + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write artifact event: %q", err) + } +} + // Events is a wrapper function for everyone to begin tailing the events log // with options func (r *Runtime) Events(ctx context.Context, options events.ReadOptions) error { diff --git a/libpod/events/config.go b/libpod/events/config.go index 053e7e3c37e..f7199d5270b 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -133,7 +133,11 @@ const ( Machine Type = "machine" // Secret - event is related to secrets Secret Type = "secret" + // Artifact - event is related to artifacts + Artifact Type = "artifact" + // Add ... + Add Status = "add" // Attach ... Attach Status = "attach" // AutoUpdate ... @@ -156,6 +160,8 @@ const ( ExecDied Status = "exec_died" // Exited indicates that a container's process died Exited Status = "died" + // Extract ... + Extract Status = "extract" // Export ... Export Status = "export" // HealthStatus ... diff --git a/libpod/events/events.go b/libpod/events/events.go index 1638ffb62f5..c094bb25091 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -94,6 +94,14 @@ func (e *Event) ToHumanReadable(truncate bool) string { humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name) case Secret: humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, id) + case Artifact: + humanFormat = fmt.Sprintf("%s %s %s %s (name=%s", e.Time, e.Type, e.Status, id, e.Name) + if len(e.Attributes) > 0 { + for k, v := range e.Attributes { + humanFormat += fmt.Sprintf(", %s=%s", k, v) + } + } + humanFormat += ")" } return humanFormat } @@ -127,6 +135,8 @@ func StringToType(name string) (Type, error) { return Volume, nil case Secret.String(): return Secret, nil + case Artifact.String(): + return Artifact, nil case "": return "", ErrEventTypeBlank } @@ -136,6 +146,8 @@ func StringToType(name string) (Type, error) { // StringToStatus converts a string to an Event Status func StringToStatus(name string) (Status, error) { switch name { + case Add.String(): + return Add, nil case Attach.String(): return Attach, nil case AutoUpdate.String(): @@ -156,6 +168,8 @@ func StringToStatus(name string) (Status, error) { return ExecDied, nil case Exited.String(): return Exited, nil + case Extract.String(): + return Extract, nil case Export.String(): return Export, nil case HealthStatus.String(): diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index 0fc1d6f71ea..92b43d0039a 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -74,6 +74,12 @@ func (e EventJournalD) Write(ee Event) error { } case Volume: m["PODMAN_NAME"] = ee.Name + case Artifact: + m["PODMAN_NAME"] = ee.Name + m["PODMAN_ID"] = ee.ID + if err := addLabelsToJournal(m, ee.Details.Attributes); err != nil { + return err + } } // starting with commit 7e6e267329 we set LogLevel=notice for the systemd healthcheck unit @@ -275,6 +281,11 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { if val, ok := entry.Fields["ERROR"]; ok { newEvent.Error = val } + case Artifact: + newEvent.ID = entry.Fields["PODMAN_ID"] + if err := getLabelsFromJournal(entry, &newEvent); err != nil { + return nil, err + } } return &newEvent, nil } diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index 0e892bea5fa..7c3df617282 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -174,7 +174,7 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { continue } switch event.Type { - case Image, Volume, Pod, Container, Network, Secret: + case Image, Volume, Pod, Container, Network, Secret, Artifact: // no-op case System: begin, end, err := e.readRotateEvent(event) diff --git a/libpod/runtime.go b/libpod/runtime.go index 7e83fffc56e..7304b93f5eb 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -531,7 +531,15 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { // Using sync once value to only init the store exactly once and only when it will be actually be used. runtime.ArtifactStore = sync.OnceValues(func() (*artStore.ArtifactStore, error) { - return artStore.NewArtifactStore(filepath.Join(runtime.storageConfig.GraphRoot, "artifacts"), runtime.SystemContext()) + artifactEventCallBack := func(status, name, digest string, attributes map[string]string) { + eventStatus, err := events.StringToStatus(status) + if err != nil { + logrus.Errorf("Unknown artifact event status %q: %v", status, err) + return + } + runtime.NewArtifactEvent(eventStatus, name, digest, attributes) + } + return artStore.NewArtifactStoreWithEventCallback(filepath.Join(runtime.storageConfig.GraphRoot, "artifacts"), runtime.SystemContext(), artifactEventCallBack) }) } diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go index 58b1c8e9f16..c0cd9ee10b8 100644 --- a/test/e2e/events_test.go +++ b/test/e2e/events_test.go @@ -5,7 +5,9 @@ package integration import ( "encoding/json" "fmt" + "sort" "strconv" + "strings" "sync" "time" @@ -309,4 +311,52 @@ var _ = Describe("Podman events", func() { Expect(result.OutputToStringArray()).ToNot(BeEmpty(), "Number of health_status events") }) + It("podman events artifacts", func() { + lock, port, err := setupRegistry(nil) + Expect(err).ToNot(HaveOccurred()) + defer lock.Unlock() + + artifactFile, err := createArtifactFile(4192) + Expect(err).ToNot(HaveOccurred()) + artifactName := fmt.Sprintf("localhost:%s/test/artifact1", port) + + podmanTest.PodmanExitCleanly("artifact", "add", artifactName, artifactFile) + + podmanTest.PodmanExitCleanly("artifact", "extract", artifactName, artifactFile) + + podmanTest.PodmanExitCleanly("artifact", "push", "-q", "--tls-verify=false", artifactName) + + podmanTest.PodmanExitCleanly("artifact", "remove", artifactName) + + podmanTest.PodmanExitCleanly("artifact", "pull", "--tls-verify=false", artifactName) + + result := podmanTest.PodmanExitCleanly("events", "--stream=false", "--filter", "type=artifact") + events := result.OutputToStringArray() + + // sort for remote + sort.Slice(events, func(i, j int) bool { + getStatus := func(log string) string { + status := strings.Fields(log) + return status[5] + } + return getStatus(events[i]) < getStatus(events[j]) + }) + + Expect(events).To(HaveLen(5)) + Expect(events[0]).To(And( + ContainSubstring("artifact add"), + ContainSubstring(fmt.Sprintf("(name=%s, files=%s)", artifactName, "1")))) + Expect(events[1]).To(And( + ContainSubstring("artifact extract"), + ContainSubstring(fmt.Sprintf("(name=%s, target=%s)", artifactName, artifactFile)))) + Expect(events[2]).To(And( + ContainSubstring("artifact pull"), + ContainSubstring(fmt.Sprintf("(name=%s)", artifactName)))) + Expect(events[3]).To(And( + ContainSubstring("artifact push"), + ContainSubstring(fmt.Sprintf("(name=%s)", artifactName)))) + Expect(events[4]).To(And( + ContainSubstring("artifact remove"), + ContainSubstring(fmt.Sprintf("(name=%s)", artifactName)))) + }) })