Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/podman/common/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -1580,7 +1580,7 @@ func getMethodNames(f reflect.Value, prefix string) []formatSuggestion {
}

// AutocompleteEventFilter - Autocomplete event filter flag options.
// -> "container=", "event=", "image=", "pod=", "volume=", "type="
// -> "container=", "event=", "image=", "pod=", "volume=", "type=", "artifact="
func AutocompleteEventFilter(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
event := func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{
Expand All @@ -1599,11 +1599,13 @@ func AutocompleteEventFilter(cmd *cobra.Command, _ []string, toComplete string)
return []string{
events.Container.String(), events.Image.String(), events.Network.String(),
events.Pod.String(), events.System.String(), events.Volume.String(), events.Secret.String(),
events.Artifact.String(),
}, cobra.ShellCompDirectiveNoFileComp
}
kv := keyValueCompletion{
"container=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
"image=": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) },
"artifact=": func(s string) ([]string, cobra.ShellCompDirective) { return getArtifacts(cmd, s) },
"pod=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeDefault) },
"volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) },
"event=": event,
Expand Down
11 changes: 9 additions & 2 deletions docs/source/markdown/podman-events.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ The *image* event type reports the following statuses:
* unmount
* untag

The *artifact* event type reports the following statuses:
* create
* pull
* push
* remove

The *system* type reports the following statuses:
* refresh
* renumber
Expand Down Expand Up @@ -102,6 +108,7 @@ filters are supported:

| **Filter** | **Description** |
|------------|-------------------------------------|
| artifact | [Name or ID] Artifact name or ID |
| container | [Name or ID] Container's name or ID |
| event | event_status (described above) |
| image | [Name or ID] Image name or ID |
Expand Down Expand Up @@ -167,8 +174,8 @@ The journald events-backend of Podman uses the following journald identifiers.
| PODMAN_EVENT | The event status as described above |
| PODMAN_TYPE | The event type as described above |
| PODMAN_TIME | The time stamp when the event was written |
| PODMAN_NAME | Name of the event object (e.g., container, image) |
| PODMAN_ID | ID of the event object (e.g., container, image) |
| PODMAN_NAME | Name of the event object (e.g., container, image, artifact) |
| PODMAN_ID | ID of the event object (e.g., container, image, artifact) |
| PODMAN_EXIT_CODE | Exit code of the container |
| PODMAN_POD_ID | Pod ID of the container |
| PODMAN_LABELS | Labels of the container |
Expand Down
30 changes: 30 additions & 0 deletions libpod/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"fmt"
"path/filepath"
"time"

"github.com/sirupsen/logrus"
"go.podman.io/podman/v6/libpod/define"
Expand Down Expand Up @@ -257,3 +258,32 @@ func (r *Runtime) GetExecDiedEvent(ctx context.Context, nameOrID, execSessionID
}
return containerEvents[len(containerEvents)-1], nil
}

// spawnEventForwarder starts a goroutine that reads events from the given channel, transforms and forwards them to the eventer.
func spawnEventForwarder[T any](eventer events.Eventer, toLibpodEvent func(libEvent T) events.Event, eventChannel <-chan T, shutdownChan chan bool) {
go func() {
sawShutdown := false
for {
// Make sure to read and write all events before
// shutting down.
for len(eventChannel) > 0 {
libEvent := <-eventChannel
libpodEvent := toLibpodEvent(libEvent)
if err := eventer.Write(libpodEvent); err != nil {
logrus.Errorf("Unable to write event of type %T: %q", libEvent, err)
}
}

if sawShutdown {
close(shutdownChan)
return
}

select {
case <-shutdownChan:
sawShutdown = true
case <-time.After(100 * time.Millisecond):
}
}
}()
}
2 changes: 2 additions & 0 deletions libpod/events/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ const (
Container Type = "container"
// Image - event is related to images
Image Type = "image"
// Artifact - event is related to artifacts
Artifact Type = "artifact"
// Network - event is related to networks
Network Type = "network"
// Pod - event is related to pods
Expand Down
4 changes: 3 additions & 1 deletion libpod/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (e *Event) ToHumanReadable(truncate bool) string {
} else {
humanFormat = fmt.Sprintf("%s %s %s %s (container=%s, name=%s)", e.Time, e.Type, e.Status, id, id, e.Network)
}
case Image:
case Image, Artifact:
humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, id, e.Name)
if e.Error != "" {
humanFormat += " " + e.Error
Expand Down Expand Up @@ -111,6 +111,8 @@ func (s Status) String() string {
// StringToType converts string to an EventType
func StringToType(name string) (Type, error) {
switch name {
case Artifact.String():
return Artifact, nil
case Container.String():
return Container, nil
case Image.String():
Expand Down
10 changes: 10 additions & 0 deletions libpod/events/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ func generateEventFilter(filter, filterValue string) (func(e *Event) bool, error
}
return strings.HasPrefix(e.ID, filterValue)
}, nil
case "ARTIFACT":
return func(e *Event) bool {
if e.Type != Artifact {
return false
}
if e.Name == filterValue {
return true
}
return strings.HasPrefix(e.ID, filterValue)
}, nil
case "POD":
return func(e *Event) bool {
if e.Type != Pod {
Expand Down
4 changes: 2 additions & 2 deletions libpod/events/journal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (e EventJournalD) Write(ee Event) error {

// Add specialized information based on the podman type
switch ee.Type {
case Image:
case Image, Artifact:
m["PODMAN_NAME"] = ee.Name
m["PODMAN_ID"] = ee.ID
if ee.Error != "" {
Expand Down Expand Up @@ -281,7 +281,7 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) {
if err := getLabelsFromJournal(entry, &newEvent); err != nil {
return nil, err
}
case Image:
case Image, Artifact:
newEvent.ID = entry.Fields["PODMAN_ID"]
if val, ok := entry.Fields["ERROR"]; ok {
newEvent.Error = val
Expand Down
2 changes: 1 addition & 1 deletion libpod/events/logfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
153 changes: 93 additions & 60 deletions libpod/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"strings"
"sync"
"syscall"
"time"

"github.com/hashicorp/go-multierror"
jsoniter "github.com/json-iterator/go"
Expand Down Expand Up @@ -69,21 +68,23 @@ type Runtime struct {
storageConfig storage.StoreOptions
storageSet storageSet

state State
store storage.Store
storageService *storageService
imageContext types.SystemContext
defaultOCIRuntime OCIRuntime
ociRuntimes map[string]OCIRuntime
runtimeFlags []string
network nettypes.ContainerNetwork
conmonPath string
libimageRuntime *libimage.Runtime
libimageEventsShutdown chan bool
lockManager lock.Manager
state State
store storage.Store
storageService *storageService
imageContext types.SystemContext
defaultOCIRuntime OCIRuntime
ociRuntimes map[string]OCIRuntime
runtimeFlags []string
network nettypes.ContainerNetwork
conmonPath string
libimageRuntime *libimage.Runtime
libimageEventsShutdown chan bool
libartifactEventsShutdown chan bool
lockManager lock.Manager

// ArtifactStore returns the artifact store created from the runtime.
ArtifactStore func() (*artStore.ArtifactStore, error)
ArtifactStore func() (*artStore.ArtifactStore, error)
shutdownArtifactStore func()

// Worker
workerChannel chan func()
Expand Down Expand Up @@ -452,6 +453,16 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
return fmt.Errorf("namespaces are not supported by this version of Libpod, please unset the `namespace` field in containers.conf: %w", define.ErrNotImplemented)
}

// Set up the eventer
// WARN: This must be done synchronously before spawning any event listeners
// (e.g., libimageEvents, libartifactEvents) to prevent race conditions.
// TODO: Remove this temporal coupling.
eventer, err := runtime.newEventer()
if err != nil {
return err
}
runtime.eventer = eventer

needsUserns := os.Geteuid() != 0
if !needsUserns {
hasCapSysAdmin, err := unshare.HasCapSysAdmin()
Expand Down Expand Up @@ -485,13 +496,6 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
}
}()

// Set up the eventer
eventer, err := runtime.newEventer()
if err != nil {
return err
}
runtime.eventer = eventer

// Set up containers/image
if runtime.imageContext.BigFilesTemporaryDir == "" {
runtime.imageContext.BigFilesTemporaryDir = parse.GetTempDir()
Expand Down Expand Up @@ -558,7 +562,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())
artifactStore, err := artStore.NewArtifactStore(filepath.Join(runtime.storageConfig.GraphRoot, "artifacts"), runtime.SystemContext())
if err != nil {
return nil, err
}
runtime.libartifactEvents(artifactStore)
runtime.shutdownArtifactStore = func() {
artifactStore.CloseEventChannel()
}
return artifactStore, nil
})
}

Expand Down Expand Up @@ -743,50 +755,59 @@ var libimageEventsMap = map[libimage.EventType]events.Status{
// when the main() exists.
func (r *Runtime) libimageEvents() {
r.libimageEventsShutdown = make(chan bool)

toLibpodEventStatus := func(e *libimage.Event) events.Status {
status, found := libimageEventsMap[e.Type]
eventChannel := r.libimageRuntime.EventChannel()
toLibpodEventFunc := func(libimageEvent *libimage.Event) events.Event {
status, found := libimageEventsMap[libimageEvent.Type]
if !found {
return "Unknown"
status = "Unknown"
}
event := events.Event{
ID: libimageEvent.ID,
Name: libimageEvent.Name,
Status: status,
Time: libimageEvent.Time,
Type: events.Image,
}
return status
if libimageEvent.Error != nil {
event.Error = libimageEvent.Error.Error()
}
return event
}
spawnEventForwarder(r.eventer, toLibpodEventFunc, eventChannel, r.libimageEventsShutdown)
}

eventChannel := r.libimageRuntime.EventChannel()
go func() {
sawShutdown := false
for {
// Make sure to read and write all events before
// shutting down.
for len(eventChannel) > 0 {
libimageEvent := <-eventChannel
e := events.Event{
ID: libimageEvent.ID,
Name: libimageEvent.Name,
Status: toLibpodEventStatus(libimageEvent),
Time: libimageEvent.Time,
Type: events.Image,
}
if libimageEvent.Error != nil {
e.Error = libimageEvent.Error.Error()
}
if err := r.eventer.Write(e); err != nil {
logrus.Errorf("Unable to write image event: %q", err)
}
}

if sawShutdown {
close(r.libimageEventsShutdown)
return
}
// libartifactEventsMap translates a libartifact event type to a libpod event status.
var libartifactEventsMap = map[artStore.EventType]events.Status{
artStore.EventTypeArtifactPull: events.Pull,
artStore.EventTypeArtifactPush: events.Push,
artStore.EventTypeArtifactRemove: events.Remove,
artStore.EventTypeArtifactAdd: events.Create,
}

select {
case <-r.libimageEventsShutdown:
sawShutdown = true
case <-time.After(100 * time.Millisecond):
}
// libartifactEvents spawns a goroutine which will listen for events on
// the artStore.ArtifactStore. The goroutine will be cleaned up implicitly
// when the main() exists.
func (r *Runtime) libartifactEvents(store *artStore.ArtifactStore) {
r.libartifactEventsShutdown = make(chan bool)
eventChannel := store.EventChannel()
toLibpodEventFunc := func(artStoreEvent *artStore.Event) events.Event {
status, found := libartifactEventsMap[artStoreEvent.Type]
if !found {
status = "Unknown"
}
}()
event := events.Event{
ID: artStoreEvent.ID,
Name: artStoreEvent.Name,
Status: status,
Time: artStoreEvent.Time,
Type: events.Artifact,
}
if artStoreEvent.Error != nil {
event.Error = artStoreEvent.Error.Error()
}
return event
}
spawnEventForwarder(r.eventer, toLibpodEventFunc, eventChannel, r.libartifactEventsShutdown)
}

// DeferredShutdown shuts down the runtime without exposing any
Expand Down Expand Up @@ -843,6 +864,18 @@ func (r *Runtime) Shutdown(force bool) error {
lastError = fmt.Errorf("shutting down container storage: %w", err)
}
}

// Shutdown the artifact store if it exists
if r.libartifactEventsShutdown != nil {
// Tell loop to shutdown
r.libartifactEventsShutdown <- true
// Wait for close to signal shutdown
<-r.libartifactEventsShutdown
}
if r.shutdownArtifactStore != nil {
r.shutdownArtifactStore()
}

if err := r.state.Close(); err != nil {
if lastError != nil {
logrus.Error(lastError)
Expand Down
Loading