aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xAPI.md47
-rw-r--r--cmd/podman/cliconfig/config.go9
-rw-r--r--cmd/podman/diff.go2
-rw-r--r--cmd/podman/events.go48
-rw-r--r--cmd/podman/formats/formats_test.go42
-rw-r--r--cmd/podman/history.go2
-rw-r--r--cmd/podman/images.go2
-rw-r--r--cmd/podman/info.go2
-rw-r--r--cmd/podman/inspect.go2
-rw-r--r--cmd/podman/logs.go25
-rw-r--r--cmd/podman/main.go1
-rw-r--r--cmd/podman/mount.go2
-rw-r--r--cmd/podman/pod_ps.go2
-rw-r--r--cmd/podman/pod_stats.go2
-rw-r--r--cmd/podman/ps.go2
-rw-r--r--cmd/podman/search.go2
-rw-r--r--cmd/podman/shared/events.go115
-rw-r--r--cmd/podman/stats.go2
-rw-r--r--cmd/podman/trust_set_show.go2
-rw-r--r--cmd/podman/varlink/io.podman.varlink23
-rw-r--r--cmd/podman/version.go2
-rw-r--r--cmd/podman/volume_ls.go2
-rw-r--r--commands.md1
-rw-r--r--completions/bash/podman17
-rw-r--r--docs/podman-events.1.md139
-rw-r--r--libpod/container_api.go34
-rw-r--r--libpod/container_commit.go2
-rw-r--r--libpod/container_internal.go4
-rw-r--r--libpod/container_internal_linux.go13
-rw-r--r--libpod/events.go81
-rw-r--r--libpod/events/events.go264
-rw-r--r--libpod/image/image.go42
-rw-r--r--libpod/image/prune.go6
-rw-r--r--libpod/image/pull.go5
-rw-r--r--libpod/options.go1
-rw-r--r--libpod/pod_api.go13
-rw-r--r--libpod/runtime.go16
-rw-r--r--libpod/runtime_ctr.go4
-rw-r--r--libpod/runtime_pod_linux.go5
-rw-r--r--libpod/runtime_volume.go6
-rw-r--r--libpod/runtime_volume_linux.go5
-rw-r--r--pkg/adapter/runtime.go53
-rw-r--r--pkg/adapter/runtime_remote.go68
-rw-r--r--pkg/spec/spec.go9
-rw-r--r--pkg/util/utils.go23
-rw-r--r--pkg/varlinkapi/events.go56
-rw-r--r--test/e2e/common_test.go2
-rw-r--r--test/e2e/events_test.go116
-rw-r--r--test/e2e/libpod_suite_test.go4
-rw-r--r--transfer.md2
-rw-r--r--troubleshooting.md18
-rw-r--r--vendor.conf2
-rw-r--r--vendor/github.com/containers/buildah/imagebuildah/build.go2
-rw-r--r--vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go10
-rw-r--r--vendor/github.com/containers/buildah/pkg/formats/formats.go (renamed from cmd/podman/formats/formats.go)0
-rw-r--r--vendor/github.com/containers/buildah/pkg/formats/templates.go (renamed from cmd/podman/formats/templates.go)0
-rw-r--r--vendor/github.com/containers/buildah/run.go90
57 files changed, 1283 insertions, 168 deletions
diff --git a/API.md b/API.md
index fc0361195..da6615de8 100755
--- a/API.md
+++ b/API.md
@@ -45,6 +45,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[func GetContainersByContext(all: bool, latest: bool, args: []string) []string](#GetContainersByContext)
+[func GetEvents(options: EventInput) Event](#GetEvents)
+
[func GetImage(id: string) Image](#GetImage)
[func GetInfo() PodmanInfo](#GetInfo)
@@ -165,6 +167,10 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[type CreateResourceConfig](#CreateResourceConfig)
+[type Event](#Event)
+
+[type EventInput](#EventInput)
+
[type IDMap](#IDMap)
[type IDMappingOptions](#IDMappingOptions)
@@ -231,8 +237,12 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[error RuntimeError](#RuntimeError)
+[error StreamEnded](#StreamEnded)
+
[error VolumeNotFound](#VolumeNotFound)
+[error WantsMoreRequired](#WantsMoreRequired)
+
## Methods
### <a name="BuildImage"></a>func BuildImage
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
@@ -469,6 +479,11 @@ method GetContainersByContext(all: [bool](https://godoc.org/builtin#bool), lates
GetContainersByContext allows you to get a list of container ids depending on all, latest, or a list of
container names. The definition of latest container means the latest by creation date. In a multi-
user environment, results might differ from what you expect.
+### <a name="GetEvents"></a>func GetEvents
+<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
+
+method GetEvents(options: [EventInput](#EventInput)) [Event](#Event)</div>
+GetEvents returns known libpod events filtered by the options provided.
### <a name="GetImage"></a>func GetImage
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
@@ -1396,6 +1411,32 @@ pids_limit [int](https://godoc.org/builtin#int)
shm_size [int](https://godoc.org/builtin#int)
ulimit [[]string](#[]string)
+### <a name="Event"></a>type Event
+
+Event describes a libpod struct
+
+id [string](https://godoc.org/builtin#string)
+
+image [string](https://godoc.org/builtin#string)
+
+name [string](https://godoc.org/builtin#string)
+
+status [string](https://godoc.org/builtin#string)
+
+time [string](https://godoc.org/builtin#string)
+
+type [string](https://godoc.org/builtin#string)
+### <a name="EventInput"></a>type EventInput
+
+EventInput describes the input to obtain libpod events
+
+filter [[]string](#[]string)
+
+since [string](https://godoc.org/builtin#string)
+
+stream [bool](https://godoc.org/builtin#bool)
+
+until [string](https://godoc.org/builtin#string)
### <a name="IDMap"></a>type IDMap
IDMap is used to describe user name spaces during container creation
@@ -1752,6 +1793,12 @@ PodNotFound means the pod could not be found by the provided name or ID in local
### <a name="RuntimeError"></a>type RuntimeError
RuntimeErrors generally means a runtime could not be found or gotten.
+### <a name="StreamEnded"></a>type StreamEnded
+
+The Podman endpoint has closed because the stream ended.
### <a name="VolumeNotFound"></a>type VolumeNotFound
VolumeNotFound means the volume could not be found by the name or ID in local storage.
+### <a name="WantsMoreRequired"></a>type WantsMoreRequired
+
+The Podman endpoint requires that you use a streaming connection.
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index d58964489..ec08eedb5 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -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
}
diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go
index bd3a985b7..e77e562d4 100644
--- a/cmd/podman/diff.go
+++ b/cmd/podman/diff.go
@@ -2,8 +2,8 @@ package main
import (
"fmt"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
diff --git a/cmd/podman/events.go b/cmd/podman/events.go
new file mode 100644
index 000000000..dda9a03f9
--- /dev/null
+++ b/cmd/podman/events.go
@@ -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)
+}
diff --git a/cmd/podman/formats/formats_test.go b/cmd/podman/formats/formats_test.go
deleted file mode 100644
index c75109d65..000000000
--- a/cmd/podman/formats/formats_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package formats
-
-import (
- "bytes"
- "strings"
- "testing"
-
- "github.com/containers/libpod/pkg/inspect"
-)
-
-func TestSetJSONFormatEncoder(t *testing.T) {
- tt := []struct {
- name string
- imageData *inspect.ImageData
- expected string
- isTerminal bool
- }{
- {
- name: "HTML tags are not escaped",
- imageData: &inspect.ImageData{Author: "dave <dave@corp.io>"},
- expected: `"Author": "dave <dave@corp.io>"`,
- isTerminal: true,
- },
- {
- name: "HTML tags are escaped",
- imageData: &inspect.ImageData{Author: "dave <dave@corp.io>"},
- expected: `"Author": "dave \u003cdave@corp.io\u003e"`,
- isTerminal: false,
- },
- }
-
- for _, tc := range tt {
- buf := bytes.NewBuffer(nil)
- enc := setJSONFormatEncoder(tc.isTerminal, buf)
- if err := enc.Encode(tc.imageData); err != nil {
- t.Errorf("test %#v failed encoding: %s", tc.name, err)
- }
- if !strings.Contains(buf.String(), tc.expected) {
- t.Errorf("test %#v expected output to contain %#v. Output:\n%v\n", tc.name, tc.expected, buf.String())
- }
- }
-}
diff --git a/cmd/podman/history.go b/cmd/podman/history.go
index f6cfe91b6..4b76ef0ca 100644
--- a/cmd/podman/history.go
+++ b/cmd/podman/history.go
@@ -6,8 +6,8 @@ import (
"strings"
"time"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/adapter"
"github.com/docker/go-units"
diff --git a/cmd/podman/images.go b/cmd/podman/images.go
index f92e5d44d..6133450be 100644
--- a/cmd/podman/images.go
+++ b/cmd/podman/images.go
@@ -9,8 +9,8 @@ import (
"time"
"unicode"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/imagefilters"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/adapter"
diff --git a/cmd/podman/info.go b/cmd/podman/info.go
index de20eb009..195267c7f 100644
--- a/cmd/podman/info.go
+++ b/cmd/podman/info.go
@@ -4,8 +4,8 @@ import (
"fmt"
rt "runtime"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter"
"github.com/containers/libpod/version"
diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go
index 0af96088f..e14f25c24 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -5,8 +5,8 @@ import (
"encoding/json"
"strings"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/pkg/adapter"
cc "github.com/containers/libpod/pkg/spec"
diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go
index 9df7281fc..c3416fe57 100644
--- a/cmd/podman/logs.go
+++ b/cmd/podman/logs.go
@@ -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
-}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index bbeb72397..669860341 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -36,6 +36,7 @@ var (
// implemented.
var mainCommands = []*cobra.Command{
_buildCommand,
+ _eventsCommand,
_exportCommand,
_historyCommand,
&_imagesCommand,
diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go
index c5b7e2404..4381074ab 100644
--- a/cmd/podman/mount.go
+++ b/cmd/podman/mount.go
@@ -5,8 +5,8 @@ import (
"fmt"
"os"
+ of "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- of "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go
index e30a03005..a956882cf 100644
--- a/cmd/podman/pod_ps.go
+++ b/cmd/podman/pod_ps.go
@@ -8,8 +8,8 @@ import (
"strings"
"time"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter"
diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go
index 5c30e0595..701051938 100644
--- a/cmd/podman/pod_stats.go
+++ b/cmd/podman/pod_stats.go
@@ -11,8 +11,8 @@ import (
"encoding/json"
tm "github.com/buger/goterm"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go
index 6caac2406..de6966c3b 100644
--- a/cmd/podman/ps.go
+++ b/cmd/podman/ps.go
@@ -12,8 +12,8 @@ import (
"text/tabwriter"
"time"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
diff --git a/cmd/podman/search.go b/cmd/podman/search.go
index e508c2bcf..25f5a98b7 100644
--- a/cmd/podman/search.go
+++ b/cmd/podman/search.go
@@ -3,9 +3,9 @@ package main
import (
"strings"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod/image"
"github.com/pkg/errors"
"github.com/spf13/cobra"
diff --git a/cmd/podman/shared/events.go b/cmd/podman/shared/events.go
new file mode 100644
index 000000000..c62044271
--- /dev/null
+++ b/cmd/podman/shared/events.go
@@ -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
+}
diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go
index 3e2e114a9..d379dbad7 100644
--- a/cmd/podman/stats.go
+++ b/cmd/podman/stats.go
@@ -8,8 +8,8 @@ import (
"time"
tm "github.com/buger/goterm"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
"github.com/docker/go-units"
diff --git a/cmd/podman/trust_set_show.go b/cmd/podman/trust_set_show.go
index 5a70c21cc..d7a4ea6d6 100644
--- a/cmd/podman/trust_set_show.go
+++ b/cmd/podman/trust_set_show.go
@@ -7,9 +7,9 @@ import (
"sort"
"strings"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/trust"
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index fbc3f594e..791790e2e 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -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)
diff --git a/cmd/podman/version.go b/cmd/podman/version.go
index b3615ce23..336be892e 100644
--- a/cmd/podman/version.go
+++ b/cmd/podman/version.go
@@ -6,8 +6,8 @@ import (
"text/tabwriter"
"time"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod"
"github.com/pkg/errors"
"github.com/spf13/cobra"
diff --git a/cmd/podman/volume_ls.go b/cmd/podman/volume_ls.go
index 5a36f4f7d..2f35462a3 100644
--- a/cmd/podman/volume_ls.go
+++ b/cmd/podman/volume_ls.go
@@ -4,8 +4,8 @@ import (
"reflect"
"strings"
+ "github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
diff --git a/commands.md b/commands.md
index 3fd27ad5d..6c5fad2f6 100644
--- a/commands.md
+++ b/commands.md
@@ -19,6 +19,7 @@
| [podman-cp(1)](/docs/podman-cp.1.md) | Copy files/folders between a container and the local filesystem ||
| [podman-create(1)](/docs/podman-create.1.md) | Create a new container ||
| [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)|
+| [podman-events(1)](/docs/podman-events.1.md) | Monitor Podman events ||
| [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container
| [podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)|
| [podman-generate(1)](/docs/podman-generate.1.md) | Generate structured output based on Podman containers and pods | |
diff --git a/completions/bash/podman b/completions/bash/podman
index a6445e14e..d8354fa80 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -2438,6 +2438,22 @@ _podman_play_kube() {
esac
}
+_podman_events() {
+ local options_with_args="
+ --help
+ --h
+ --filter
+ --format
+ --since
+ --until
+ "
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$options_with_args" -- "$cur"))
+ ;;
+ esac
+}
+
_podman_container_runlabel() {
local options_with_args="
--authfile
@@ -3027,6 +3043,7 @@ _podman_podman() {
cp
create
diff
+ events
exec
export
generate
diff --git a/docs/podman-events.1.md b/docs/podman-events.1.md
new file mode 100644
index 000000000..b4ebe7649
--- /dev/null
+++ b/docs/podman-events.1.md
@@ -0,0 +1,139 @@
+% podman-events(1)
+
+## NAME
+podman\-events- Monitor Podman events
+
+## SYNOPSIS
+**podman events** [*options*]
+
+## DESCRIPTION
+
+Monitor and print events that occur in Podman. Each event will include a timestamp,
+a type, a status, name (if applicable), and image (if applicable).
+
+The *container* event type will report the follow statuses:
+ * attach
+ * checkpoint
+ * cleanup
+ * commit
+ * create
+ * exec
+ * export
+ * import
+ * init
+ * kill
+ * mount
+ * pause
+ * prune
+ * remove
+ * restore
+ * start
+ * stop
+ * sync
+ * unmount
+ * unpause
+ * wait
+
+The *pod* event type will report the follow statuses:
+ * create
+ * kill
+ * pause
+ * remove
+ * start
+ * stop
+ * unpause
+
+The *image* event type will report the following statuses:
+ * prune
+ * pull
+ * push
+ * remove
+ * save
+ * tag
+ * untag
+
+The *volume* type will report the following statuses:
+ * create
+ * prune
+ * remove
+
+
+## OPTIONS
+
+**--help**
+
+Print usage statement.
+
+**--format**
+
+Format the output using the given Go template. An output value of *json* is not supported.
+
+
+**--filter**=[]
+
+Filter events that are displayed. They must be in the format of "filter=value". The following
+filters are supported:
+ * container=name_or_id
+ * event=event_status (described above)
+ * image=name_or_id
+ * pod=name_or_id
+ * volume=name_or_id
+ * type=event_type (described above)
+
+In the case where an ID is used, the ID may be in its full or shortened form.
+
+**--since**=[]
+
+Show all events created since the given timestamp
+
+
+**--until**=[]
+
+Show all events created until the given timestamp
+
+The *since* and *until* values can be RFC3339Nano time stamps or a Go duration string such as 10m, 5h. If no
+*since* or *until* values are provided, only new events will be shown.
+
+## EXAMPLES
+
+Showing podman events
+```
+$ podman events
+2019-03-02 10:33:42.312377447 -0600 CST container create 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen)
+2019-03-02 10:33:46.958768077 -0600 CST container init 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen)
+2019-03-02 10:33:46.973661968 -0600 CST container start 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen)
+2019-03-02 10:33:50.833761479 -0600 CST container stop 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen)
+2019-03-02 10:33:51.047104966 -0600 CST container cleanup 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen)
+```
+
+Show only podman create events
+```
+$ podman events --filter event=create
+2019-03-02 10:36:01.375685062 -0600 CST container create 20dc581f6fbf (image=docker.io/library/alpine:latest, name=sharp_morse)
+2019-03-02 10:36:08.561188337 -0600 CST container create 58e7e002344c (image=k8s.gcr.io/pause:3.1, name=3e701f270d54-infra)
+2019-03-02 10:36:13.146899437 -0600 CST volume create cad6dc50e087 (image=, name=cad6dc50e0879568e7d656bd004bd343d6035e7fc4024e1711506fe2fd459e6f)
+2019-03-02 10:36:29.978806894 -0600 CST container create d81e30f1310f (image=docker.io/library/busybox:latest, name=musing_newton)
+```
+
+Show only podman pod create events
+```
+$ podman events --filter event=create --filter type=pod
+2019-03-02 10:44:29.601746633 -0600 CST pod create 1df5ebca7b44 (image=, name=confident_hawking)
+2019-03-02 10:44:42.374637304 -0600 CST pod create ca731231718e (image=, name=webapp)
+2019-03-02 10:44:47.486759133 -0600 CST pod create 71e807fc3a8e (image=, name=reverent_swanson)
+```
+
+Show only podman events created in the last five minutes:
+```
+$ sudo podman events --since 5m
+2019-03-02 10:44:29.598835409 -0600 CST container create b629d10d3831 (image=k8s.gcr.io/pause:3.1, name=1df5ebca7b44-infra)
+2019-03-02 10:44:29.601746633 -0600 CST pod create 1df5ebca7b44 (image=, name=confident_hawking)
+2019-03-02 10:44:42.371100253 -0600 CST container create 170a0f457d00 (image=k8s.gcr.io/pause:3.1, name=ca731231718e-infra)
+2019-03-02 10:44:42.374637304 -0600 CST pod create ca731231718e (image=, name=webapp)
+```
+
+## SEE ALSO
+podman(1)
+
+## HISTORY
+March 2019, Originally compiled by Brent Baude <bbaude@redhat.com>
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 4a76e1434..3698a15ec 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -10,6 +10,7 @@ import (
"time"
"github.com/containers/libpod/libpod/driver"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/inspect"
"github.com/containers/libpod/pkg/lookup"
"github.com/containers/storage/pkg/stringid"
@@ -88,6 +89,7 @@ func (c *Container) Start(ctx context.Context, recursive bool) (err error) {
}
// Start the container
+ defer c.newContainerEvent(events.Start)
return c.start()
}
@@ -125,7 +127,8 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *AttachStreams,
}
close(attachChan)
}()
-
+ c.newContainerEvent(events.Start)
+ c.newContainerEvent(events.Attach)
return attachChan, nil
}
@@ -180,7 +183,7 @@ func (c *Container) StopWithTimeout(timeout uint) error {
c.state.State == ContainerStateExited {
return ErrCtrStopped
}
-
+ defer c.newContainerEvent(events.Stop)
return c.stop(timeout)
}
@@ -198,7 +201,7 @@ func (c *Container) Kill(signal uint) error {
if c.state.State != ContainerStateRunning {
return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers")
}
-
+ defer c.newContainerEvent(events.Kill)
return c.runtime.ociRuntime.killContainer(c, signal)
}
@@ -321,7 +324,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
// TODO handle this better
return errors.Wrapf(err, "error saving exec sessions %s for container %s", sessionID, c.ID())
}
-
+ c.newContainerEvent(events.Exec)
logrus.Debugf("Successfully started exec session %s in container %s", sessionID, c.ID())
// Unlock so other processes can use the container
@@ -351,7 +354,6 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
if err := c.save(); err != nil {
logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err)
}
-
return waitErr
}
@@ -390,7 +392,7 @@ func (c *Container) Attach(streams *AttachStreams, keys string, resize <-chan re
c.state.State != ContainerStateExited {
return errors.Wrapf(ErrCtrStateInvalid, "can only attach to created or running containers")
}
-
+ defer c.newContainerEvent(events.Attach)
return c.attach(streams, keys, resize, false)
}
@@ -405,7 +407,7 @@ func (c *Container) Mount() (string, error) {
return "", err
}
}
-
+ defer c.newContainerEvent(events.Mount)
return c.mount()
}
@@ -435,6 +437,7 @@ func (c *Container) Unmount(force bool) error {
return errors.Wrapf(ErrInternal, "can't unmount %s last mount, it is still in use", c.ID())
}
}
+ defer c.newContainerEvent(events.Unmount)
return c.unmount(force)
}
@@ -455,7 +458,7 @@ func (c *Container) Pause() error {
if c.state.State != ContainerStateRunning {
return errors.Wrapf(ErrCtrStateInvalid, "%q is not running, can't pause", c.state.State)
}
-
+ defer c.newContainerEvent(events.Pause)
return c.pause()
}
@@ -473,7 +476,7 @@ func (c *Container) Unpause() error {
if c.state.State != ContainerStatePaused {
return errors.Wrapf(ErrCtrStateInvalid, "%q is not paused, can't unpause", c.ID())
}
-
+ defer c.newContainerEvent(events.Unpause)
return c.unpause()
}
@@ -488,7 +491,7 @@ func (c *Container) Export(path string) error {
return err
}
}
-
+ defer c.newContainerEvent(events.Export)
return c.export(path)
}
@@ -542,7 +545,6 @@ func (c *Container) Inspect(size bool) (*inspect.ContainerInspectData, error) {
if err != nil {
return nil, errors.Wrapf(err, "error getting graph driver info %q", c.ID())
}
-
return c.getContainerInspectData(size, driverData)
}
@@ -574,6 +576,7 @@ func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) {
return 0, err
}
exitCode := c.state.ExitCode
+ c.newContainerEvent(events.Wait)
return exitCode, nil
}
@@ -597,7 +600,7 @@ func (c *Container) Cleanup(ctx context.Context) error {
if len(c.state.ExecSessions) != 0 {
return errors.Wrapf(ErrCtrStateInvalid, "container %s has active exec sessions, refusing to clean up", c.ID())
}
-
+ defer c.newContainerEvent(events.Cleanup)
return c.cleanup(ctx)
}
@@ -667,7 +670,7 @@ func (c *Container) Sync() error {
}
}
}
-
+ defer c.newContainerEvent(events.Sync)
return nil
}
@@ -772,7 +775,6 @@ func (c *Container) Refresh(ctx context.Context) error {
return err
}
}
-
return nil
}
@@ -800,7 +802,7 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO
return err
}
}
-
+ defer c.newContainerEvent(events.Checkpoint)
return c.checkpoint(ctx, options)
}
@@ -815,6 +817,6 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti
return err
}
}
-
+ defer c.newContainerEvent(events.Restore)
return c.restore(ctx, options)
}
diff --git a/libpod/container_commit.go b/libpod/container_commit.go
index 5c4fd1a31..0604a550b 100644
--- a/libpod/container_commit.go
+++ b/libpod/container_commit.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/buildah"
"github.com/containers/buildah/util"
is "github.com/containers/image/storage"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -177,5 +178,6 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai
if err != nil {
return nil, err
}
+ defer c.newContainerEvent(events.Commit)
return c.runtime.imageRuntime.NewFromLocal(id)
}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 00e6786f9..330745314 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -12,6 +12,7 @@ import (
"strings"
"time"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/ctime"
"github.com/containers/libpod/pkg/hooks"
"github.com/containers/libpod/pkg/hooks/exec"
@@ -824,7 +825,7 @@ func (c *Container) init(ctx context.Context) error {
if err := c.save(); err != nil {
return err
}
-
+ defer c.newContainerEvent(events.Init)
return c.completeNetworkSetup()
}
@@ -1022,7 +1023,6 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e
return err
}
}
-
return c.start()
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index c9f35dd75..3f3b22b6b 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -26,6 +26,7 @@ import (
"github.com/containers/libpod/pkg/resolvconf"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage/pkg/idtools"
+ "github.com/cyphar/filepath-securejoin"
"github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
@@ -366,6 +367,18 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
// For private volumes any root propagation value should work.
rootPropagation := ""
for _, m := range mounts {
+ // We need to remove all symlinks from tmpfs mounts.
+ // Runc and other runtimes may choke on them.
+ // Easy solution: use securejoin to do a scoped evaluation of
+ // the links, then trim off the mount prefix.
+ if m.Type == "tmpfs" {
+ finalPath, err := securejoin.SecureJoin(c.state.Mountpoint, m.Destination)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error resolving symlinks for mount destination %s", m.Destination)
+ }
+ trimmedPath := strings.TrimPrefix(finalPath, strings.TrimSuffix(c.state.Mountpoint, "/"))
+ m.Destination = trimmedPath
+ }
g.AddMount(m)
for _, opt := range m.Options {
switch opt {
diff --git a/libpod/events.go b/libpod/events.go
new file mode 100644
index 000000000..9806c117b
--- /dev/null
+++ b/libpod/events.go
@@ -0,0 +1,81 @@
+package libpod
+
+import (
+ "github.com/containers/libpod/libpod/events"
+ "github.com/hpcloud/tail"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// newContainerEvent creates a new event based on a container
+func (c *Container) newContainerEvent(status events.Status) {
+ e := events.NewEvent(status)
+ e.ID = c.ID()
+ e.Name = c.Name()
+ e.Image = c.config.RootfsImageName
+ e.Type = events.Container
+ if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil {
+ logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath)
+ }
+}
+
+// newPodEvent creates a new event for a libpod pod
+func (p *Pod) newPodEvent(status events.Status) {
+ e := events.NewEvent(status)
+ e.ID = p.ID()
+ e.Name = p.Name()
+ e.Type = events.Pod
+ if err := e.Write(p.runtime.config.EventsLogFilePath); err != nil {
+ logrus.Errorf("unable to write event to %s", p.runtime.config.EventsLogFilePath)
+ }
+}
+
+// newVolumeEvent creates a new event for a libpod volume
+func (v *Volume) newVolumeEvent(status events.Status) {
+ e := events.NewEvent(status)
+ e.Name = v.Name()
+ e.Type = events.Volume
+ if err := e.Write(v.runtime.config.EventsLogFilePath); err != nil {
+ logrus.Errorf("unable to write event to %s", v.runtime.config.EventsLogFilePath)
+ }
+}
+
+// Events is a wrapper function for everyone to begin tailing the events log
+// with options
+func (r *Runtime) Events(fromStart, stream bool, options []events.EventFilter, eventChannel chan *events.Event) error {
+ t, err := r.getTail(fromStart, stream)
+ if err != nil {
+ return err
+ }
+ for line := range t.Lines {
+ event, err := events.NewEventFromString(line.Text)
+ if err != nil {
+ return err
+ }
+ switch event.Type {
+ case events.Image, events.Volume, events.Pod, events.Container:
+ // no-op
+ default:
+ return errors.Errorf("event type %s is not valid in %s", event.Type.String(), r.GetConfig().EventsLogFilePath)
+ }
+ include := true
+ for _, filter := range options {
+ include = include && filter(event)
+ }
+ if include {
+ eventChannel <- event
+ }
+ }
+ close(eventChannel)
+ return nil
+}
+
+func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) {
+ reopen := true
+ seek := tail.SeekInfo{Offset: 0, Whence: 2}
+ if fromStart || !stream {
+ seek.Whence = 0
+ reopen = false
+ }
+ return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek})
+}
diff --git a/libpod/events/events.go b/libpod/events/events.go
new file mode 100644
index 000000000..186790500
--- /dev/null
+++ b/libpod/events/events.go
@@ -0,0 +1,264 @@
+package events
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/pkg/errors"
+)
+
+// Event describes the attributes of a libpod event
+type Event struct {
+ // ContainerExitCode is for storing the exit code of a container which can
+ // be used for "internal" event notification
+ ContainerExitCode int
+ // ID can be for the container, image, volume, etc
+ ID string
+ // Image used where applicable
+ Image string
+ // Name where applicable
+ Name string
+ // Status describes the event that occurred
+ Status Status
+ // Time the event occurred
+ Time time.Time
+ // Type of event that occurred
+ Type Type
+}
+
+// Type of event that occurred (container, volume, image, pod, etc)
+type Type string
+
+// Status describes the actual event action (stop, start, create, kill)
+type Status string
+
+const (
+ // If you add or subtract any values to the following lists, make sure you also update
+ // the switch statements below and the enums for EventType or EventStatus in the
+ // varlink description file.
+
+ // Container - event is related to containers
+ Container Type = "container"
+ // Image - event is related to images
+ Image Type = "image"
+ // Pod - event is related to pods
+ Pod Type = "pod"
+ // Volume - event is related to volumes
+ Volume Type = "volume"
+
+ // Attach ...
+ Attach Status = "attach"
+ // Checkpoint ...
+ Checkpoint Status = "checkpoint"
+ // Cleanup ...
+ Cleanup Status = "cleanup"
+ // Commit ...
+ Commit Status = "commit"
+ // Create ...
+ Create Status = "create"
+ // Exec ...
+ Exec Status = "exec"
+ // Export ...
+ Export Status = "export"
+ // History ...
+ History Status = "history"
+ // Import ...
+ Import Status = "import"
+ // Init ...
+ Init Status = "init"
+ // Kill ...
+ Kill Status = "kill"
+ // LoadFromArchive ...
+ LoadFromArchive Status = "status"
+ // Mount ...
+ Mount Status = "mount"
+ // Pause ...
+ Pause Status = "pause"
+ // Prune ...
+ Prune Status = "prune"
+ // Pull ...
+ Pull Status = "pull"
+ // Push ...
+ Push Status = "push"
+ // Remove ...
+ Remove Status = "remove"
+ // Restore ...
+ Restore Status = "restore"
+ // Save ...
+ Save Status = "save"
+ // Start ...
+ Start Status = "start"
+ // Stop ...
+ Stop Status = "stop"
+ // Sync ...
+ Sync Status = "sync"
+ // Tag ...
+ Tag Status = "tag"
+ // Unmount ...
+ Unmount Status = "unmount"
+ // Unpause ...
+ Unpause Status = "unpause"
+ // Untag ...
+ Untag Status = "untag"
+ // Wait ...
+ Wait Status = "wait"
+)
+
+// EventFilter for filtering events
+type EventFilter func(*Event) bool
+
+// NewEvent creates a event struct and populates with
+// the given status and time.
+func NewEvent(status Status) Event {
+ return Event{
+ Status: status,
+ Time: time.Now(),
+ }
+}
+
+// Write will record the event to the given path
+func (e *Event) Write(path string) error {
+ f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ eventJSONString, err := e.ToJSONString()
+ if err != nil {
+ return err
+ }
+ if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Recycle checks if the event log has reach a limit and if so
+// renames the current log and starts a new one. The remove bool
+// indicates the old log file should be deleted.
+func (e *Event) Recycle(path string, remove bool) error {
+ return errors.New("not implemented")
+}
+
+// ToJSONString returns the event as a json'ified string
+func (e *Event) ToJSONString() (string, error) {
+ b, err := json.Marshal(e)
+ return string(b), err
+}
+
+// ToHumanReadable returns human readable event as a formatted string
+func (e *Event) ToHumanReadable() string {
+ var humanFormat string
+ switch e.Type {
+ case Container, Pod:
+ humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s)", e.Time, e.Type, e.Status, e.ID, e.Image, e.Name)
+ case Image:
+ humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, e.ID, e.Name)
+ case Volume:
+ humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name)
+ }
+ return humanFormat
+}
+
+// NewEventFromString takes stringified json and converts
+// it to an event
+func NewEventFromString(event string) (*Event, error) {
+ e := Event{}
+ if err := json.Unmarshal([]byte(event), &e); err != nil {
+ return nil, err
+ }
+ return &e, nil
+
+}
+
+// ToString converts a Type to a string
+func (t Type) String() string {
+ return string(t)
+}
+
+// ToString converts a status to a string
+func (s Status) String() string {
+ return string(s)
+}
+
+// StringToType converts string to an EventType
+func StringToType(name string) (Type, error) {
+ switch name {
+ case Container.String():
+ return Container, nil
+ case Image.String():
+ return Image, nil
+ case Pod.String():
+ return Pod, nil
+ case Volume.String():
+ return Volume, nil
+ }
+ return "", errors.Errorf("unknown event type %s", name)
+}
+
+// StringToStatus converts a string to an Event Status
+// TODO if we add more events, we might consider a go-generator to
+// create the switch statement
+func StringToStatus(name string) (Status, error) {
+ switch name {
+ case Attach.String():
+ return Attach, nil
+ case Checkpoint.String():
+ return Checkpoint, nil
+ case Restore.String():
+ return Restore, nil
+ case Cleanup.String():
+ return Cleanup, nil
+ case Commit.String():
+ return Commit, nil
+ case Create.String():
+ return Create, nil
+ case Exec.String():
+ return Exec, nil
+ case Export.String():
+ return Export, nil
+ case History.String():
+ return History, nil
+ case Import.String():
+ return Import, nil
+ case Init.String():
+ return Init, nil
+ case Kill.String():
+ return Kill, nil
+ case LoadFromArchive.String():
+ return LoadFromArchive, nil
+ case Mount.String():
+ return Mount, nil
+ case Pause.String():
+ return Pause, nil
+ case Prune.String():
+ return Prune, nil
+ case Pull.String():
+ return Pull, nil
+ case Push.String():
+ return Push, nil
+ case Remove.String():
+ return Remove, nil
+ case Save.String():
+ return Save, nil
+ case Start.String():
+ return Start, nil
+ case Stop.String():
+ return Stop, nil
+ case Sync.String():
+ return Sync, nil
+ case Tag.String():
+ return Tag, nil
+ case Unmount.String():
+ return Unmount, nil
+ case Unpause.String():
+ return Unpause, nil
+ case Untag.String():
+ return Untag, nil
+ case Wait.String():
+ return Wait, nil
+ }
+ return "", errors.Errorf("unknown event status %s", name)
+}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 8c98de3d3..72f07dad1 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -24,12 +24,13 @@ import (
"github.com/containers/image/types"
"github.com/containers/libpod/libpod/common"
"github.com/containers/libpod/libpod/driver"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/inspect"
"github.com/containers/libpod/pkg/registries"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
"github.com/containers/storage/pkg/reexec"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opentracing/opentracing-go"
@@ -64,6 +65,7 @@ type Image struct {
type Runtime struct {
store storage.Store
SignaturePolicyPath string
+ EventsLogFilePath string
}
// ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store
@@ -195,7 +197,7 @@ func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.Im
newImage.image = img
newImages = append(newImages, &newImage)
}
-
+ ir.newImageEvent(events.LoadFromArchive, "")
return newImages, nil
}
@@ -371,6 +373,7 @@ func (i *Image) Remove(force bool) error {
}
parent = nextParent
}
+ defer i.newImageEvent(events.Remove)
return nil
}
@@ -494,6 +497,7 @@ func (i *Image) TagImage(tag string) error {
return err
}
i.reloadImage()
+ defer i.newImageEvent(events.Tag)
return nil
}
@@ -514,6 +518,7 @@ func (i *Image) UntagImage(tag string) error {
return err
}
i.reloadImage()
+ defer i.newImageEvent(events.Untag)
return nil
}
@@ -562,6 +567,7 @@ func (i *Image) PushImageToReference(ctx context.Context, dest types.ImageRefere
if err != nil {
return errors.Wrapf(err, "Error copying image to the remote destination")
}
+ defer i.newImageEvent(events.Push)
return nil
}
@@ -711,7 +717,6 @@ func (i *Image) History(ctx context.Context) ([]*History, error) {
Comment: oci.History[i].Comment,
})
}
-
return allHistory, nil
}
@@ -927,7 +932,11 @@ func (ir *Runtime) Import(ctx context.Context, path, reference string, writer io
if err != nil {
return nil, err
}
- return ir.NewFromLocal(reference)
+ newImage, err := ir.NewFromLocal(reference)
+ if err == nil {
+ defer newImage.newImageEvent(events.Import)
+ }
+ return newImage, err
}
// MatchRepoTag takes a string and tries to match it against an
@@ -1148,7 +1157,7 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
}
return errors.Wrapf(err, "unable to save %q", source)
}
-
+ defer i.newImageEvent(events.Save)
return nil
}
@@ -1180,3 +1189,26 @@ func (i *Image) GetHealthCheck(ctx context.Context) (*manifest.Schema2HealthConf
}
return configBlob.ContainerConfig.Healthcheck, nil
}
+
+// newImageEvent creates a new event based on an image
+func (ir *Runtime) newImageEvent(status events.Status, name string) {
+ e := events.NewEvent(status)
+ e.Type = events.Image
+ e.Name = name
+ if err := e.Write(ir.EventsLogFilePath); err != nil {
+ logrus.Infof("unable to write event to %s", ir.EventsLogFilePath)
+ }
+}
+
+// newImageEvent creates a new event based on an image
+func (i *Image) newImageEvent(status events.Status) {
+ e := events.NewEvent(status)
+ e.ID = i.ID()
+ e.Type = events.Image
+ if len(i.Names()) > 0 {
+ e.Name = i.Names()[0]
+ }
+ if err := e.Write(i.imageruntime.EventsLogFilePath); err != nil {
+ logrus.Infof("unable to write event to %s", i.imageruntime.EventsLogFilePath)
+ }
+}
diff --git a/libpod/image/prune.go b/libpod/image/prune.go
index 8602c222c..5bd3c2c99 100644
--- a/libpod/image/prune.go
+++ b/libpod/image/prune.go
@@ -1,6 +1,9 @@
package image
-import "github.com/pkg/errors"
+import (
+ "github.com/containers/libpod/libpod/events"
+ "github.com/pkg/errors"
+)
// GetPruneImages returns a slice of images that have no names/unused
func (ir *Runtime) GetPruneImages(all bool) ([]*Image, error) {
@@ -41,6 +44,7 @@ func (ir *Runtime) PruneImages(all bool) ([]string, error) {
if err := p.Remove(true); err != nil {
return nil, errors.Wrap(err, "failed to prune image")
}
+ defer p.newImageEvent(events.Prune)
prunedCids = append(prunedCids, p.ID())
}
return prunedCids, nil
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index 607829771..a3b716e65 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -17,6 +17,7 @@ import (
"github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/registries"
multierror "github.com/hashicorp/go-multierror"
opentracing "github.com/opentracing/opentracing-go"
@@ -273,6 +274,7 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa
}
} else {
if !goal.pullAllPairs {
+ ir.newImageEvent(events.Pull, "")
return []string{imageInfo.image}, nil
}
images = append(images, imageInfo.image)
@@ -293,6 +295,9 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa
}
return nil, pullErrors
}
+ if len(images) > 0 {
+ defer ir.newImageEvent(events.Pull, images[0])
+ }
return images, nil
}
diff --git a/libpod/options.go b/libpod/options.go
index 64b425c57..1bf3ff9e6 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -286,7 +286,6 @@ func WithTmpDir(dir string) RuntimeOption {
if rt.valid {
return ErrRuntimeFinalized
}
-
rt.config.TmpDir = dir
rt.configuredFrom.libpodTmpDirSet = true
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index cbac2420f..b9a11000e 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -3,6 +3,7 @@ package libpod
import (
"context"
+ "github.com/containers/libpod/libpod/events"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/ulule/deepcopier"
@@ -58,7 +59,7 @@ func (p *Pod) Start(ctx context.Context) (map[string]error, error) {
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(ErrCtrExists, "error starting some containers")
}
-
+ defer p.newPodEvent(events.Start)
return nil, nil
}
@@ -138,7 +139,7 @@ func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(ErrCtrExists, "error stopping some containers")
}
-
+ defer p.newPodEvent(events.Stop)
return nil, nil
}
@@ -197,7 +198,7 @@ func (p *Pod) Pause() (map[string]error, error) {
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(ErrCtrExists, "error pausing some containers")
}
-
+ defer p.newPodEvent(events.Pause)
return nil, nil
}
@@ -257,6 +258,7 @@ func (p *Pod) Unpause() (map[string]error, error) {
return ctrErrors, errors.Wrapf(ErrCtrExists, "error unpausing some containers")
}
+ defer p.newPodEvent(events.Unpause)
return nil, nil
}
@@ -309,7 +311,8 @@ func (p *Pod) Restart(ctx context.Context) (map[string]error, error) {
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(ErrCtrExists, "error stopping some containers")
}
-
+ p.newPodEvent(events.Stop)
+ p.newPodEvent(events.Start)
return nil, nil
}
@@ -367,7 +370,7 @@ func (p *Pod) Kill(signal uint) (map[string]error, error) {
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(ErrCtrExists, "error killing some containers")
}
-
+ defer p.newPodEvent(events.Kill)
return nil, nil
}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 535b6f41b..fa208a2ca 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -223,6 +223,9 @@ type RuntimeConfig struct {
// NumLocks is the number of locks to make available for containers and
// pods.
NumLocks uint32 `toml:"num_locks,omitempty"`
+
+ // EventsLogFilePath is where the events log is stored.
+ EventsLogFilePath string `toml:-"events_logfile_path"`
}
// runtimeConfiguredFrom is a struct used during early runtime init to help
@@ -459,7 +462,6 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
}
}
}
-
return runtime, nil
}
@@ -535,6 +537,7 @@ func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime
// Make a new runtime based on the given configuration
// Sets up containers/storage, state store, OCI runtime
func makeRuntime(runtime *Runtime) (err error) {
+ runtime.config.EventsLogFilePath = filepath.Join(runtime.config.TmpDir, "events", "events.log")
// Backward compatibility for `runtime_path`
if runtime.config.RuntimePath != nil {
@@ -736,6 +739,9 @@ func makeRuntime(runtime *Runtime) (err error) {
// Setting signaturepolicypath
ir.SignaturePolicyPath = runtime.config.SignaturePolicyPath
+ // Set logfile path for events
+ ir.EventsLogFilePath = runtime.config.EventsLogFilePath
+
defer func() {
if err != nil && store != nil {
// Don't forcibly shut down
@@ -768,6 +774,14 @@ func makeRuntime(runtime *Runtime) (err error) {
}
}
+ // Create events log dir
+ if err := os.MkdirAll(filepath.Dir(runtime.config.EventsLogFilePath), 0700); err != nil {
+ // The directory is allowed to exist
+ if !os.IsExist(err) {
+ return errors.Wrapf(err, "error creating events dirs %s", filepath.Dir(runtime.config.EventsLogFilePath))
+ }
+ }
+
// Make an OCI runtime to perform container operations
ociRuntime, err := newOCIRuntime(runtime.ociRuntimePath,
runtime.conmonPath, runtime.config.ConmonEnvVars,
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index cfa4f9654..c6f119913 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -10,6 +10,7 @@ import (
"strings"
"time"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
@@ -228,6 +229,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
return nil, err
}
}
+ ctr.newContainerEvent(events.Create)
return ctr, nil
}
@@ -239,7 +241,6 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool, removeVolume bool) error {
r.lock.Lock()
defer r.lock.Unlock()
-
return r.removeContainer(ctx, c, force, removeVolume)
}
@@ -430,6 +431,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool,
}
}
+ c.newContainerEvent(events.Remove)
return cleanupErr
}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index 9063390bd..0011c771a 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -10,6 +10,7 @@ import (
"strings"
"github.com/containerd/cgroups"
+ "github.com/containers/libpod/libpod/events"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -121,7 +122,7 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (*Pod,
return nil, err
}
}
-
+ pod.newPodEvent(events.Create)
return pod, nil
}
@@ -307,6 +308,6 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool)
// Mark pod invalid
p.valid = false
-
+ p.newPodEvent(events.Remove)
return nil
}
diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go
index 11f37ad4b..68c6c107e 100644
--- a/libpod/runtime_volume.go
+++ b/libpod/runtime_volume.go
@@ -2,9 +2,11 @@ package libpod
import (
"context"
+ "strings"
+
+ "github.com/containers/libpod/libpod/events"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
- "strings"
)
// Contains the public Runtime API for volumes
@@ -34,7 +36,6 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool) error
return nil
}
}
-
return r.removeVolume(ctx, v, force)
}
@@ -171,6 +172,7 @@ func (r *Runtime) PruneVolumes(ctx context.Context) ([]string, []error) {
}
continue
}
+ vol.newVolumeEvent(events.Prune)
prunedIDs = append(prunedIDs, vol.Name())
}
return prunedIDs, pruneErrors
diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go
index 838c0167a..b51bb8213 100644
--- a/libpod/runtime_volume_linux.go
+++ b/libpod/runtime_volume_linux.go
@@ -8,6 +8,7 @@ import (
"path/filepath"
"strings"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/storage/pkg/stringid"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
@@ -73,7 +74,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
if err := r.state.AddVolume(volume); err != nil {
return nil, errors.Wrapf(err, "error adding volume to state")
}
-
+ defer volume.newVolumeEvent(events.Create)
return volume, nil
}
@@ -118,7 +119,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error
return errors.Wrapf(err, "error cleaning up volume storage for %q", v.Name())
}
+ defer v.newVolumeEvent(events.Remove)
logrus.Debugf("Removed volume %s", v.Name())
-
return nil
}
diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go
index 482b6119a..a0951f677 100644
--- a/pkg/adapter/runtime.go
+++ b/pkg/adapter/runtime.go
@@ -3,11 +3,13 @@
package adapter
import (
+ "bufio"
"context"
"io"
"io/ioutil"
"os"
"strconv"
+ "text/template"
"github.com/containers/buildah"
"github.com/containers/buildah/imagebuildah"
@@ -16,7 +18,9 @@ import (
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
@@ -377,3 +381,52 @@ func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) {
return rootless.BecomeRootInUserNSWithOpts(&opts)
}
+
+// Events is a wrapper to libpod to obtain libpod/podman events
+func (r *LocalRuntime) Events(c *cliconfig.EventValues) error {
+ var (
+ fromStart bool
+ eventsError error
+ )
+ options, err := shared.GenerateEventOptions(c.Filter, c.Since, c.Until)
+ if err != nil {
+ return errors.Wrapf(err, "unable to generate event options")
+ }
+ tmpl, err := template.New("events").Parse(c.Format)
+ if err != nil {
+ return err
+ }
+ if len(c.Since) > 0 || len(c.Until) > 0 {
+ fromStart = true
+ }
+ eventChannel := make(chan *events.Event)
+ go func() {
+ eventsError = r.Runtime.Events(fromStart, c.Stream, options, eventChannel)
+ }()
+
+ if eventsError != nil {
+ return eventsError
+ }
+ if err != nil {
+ return errors.Wrapf(err, "unable to tail the events log")
+ }
+ w := bufio.NewWriter(os.Stdout)
+ for event := range eventChannel {
+ if len(c.Format) > 0 {
+ if err := tmpl.Execute(w, event); err != nil {
+ return err
+ }
+ } else {
+ if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil {
+ return err
+ }
+ }
+ if _, err := w.Write([]byte("\n")); err != nil {
+ return err
+ }
+ if err := w.Flush(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go
index 9ca4e245f..01f774dbd 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -10,6 +10,7 @@ import (
"io/ioutil"
"os"
"strings"
+ "text/template"
"time"
"github.com/containers/buildah/imagebuildah"
@@ -18,6 +19,7 @@ import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/utils"
"github.com/containers/storage/pkg/archive"
@@ -758,3 +760,69 @@ func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) {
// Nothing to do in the remote case
return true, 0, nil
}
+
+// Events monitors libpod/podman events over a varlink connection
+func (r *LocalRuntime) Events(c *cliconfig.EventValues) error {
+ reply, err := iopodman.GetEvents().Send(r.Conn, uint64(varlink.More), c.Filter, c.Since, c.Stream, c.Until)
+ if err != nil {
+ return errors.Wrapf(err, "unable to obtain events")
+ }
+
+ w := bufio.NewWriter(os.Stdout)
+ tmpl, err := template.New("events").Parse(c.Format)
+ if err != nil {
+ return err
+ }
+
+ for {
+ returnedEvent, flags, err := reply()
+ if err != nil {
+ // When the error handling is back into podman, we can flip this to a better way to check
+ // for problems. For now, this works.
+ return err
+ }
+ if returnedEvent.Time == "" && returnedEvent.Status == "" && returnedEvent.Type == "" {
+ // We got a blank event return, signals end of stream in certain cases
+ break
+ }
+ eTime, err := time.Parse(time.RFC3339Nano, returnedEvent.Time)
+ if err != nil {
+ return errors.Wrapf(err, "unable to parse time of event %s", returnedEvent.Time)
+ }
+ eType, err := events.StringToType(returnedEvent.Type)
+ if err != nil {
+ return err
+ }
+ eStatus, err := events.StringToStatus(returnedEvent.Status)
+ if err != nil {
+ return err
+ }
+ event := events.Event{
+ ID: returnedEvent.Id,
+ Image: returnedEvent.Image,
+ Name: returnedEvent.Name,
+ Status: eStatus,
+ Time: eTime,
+ Type: eType,
+ }
+ if len(c.Format) > 0 {
+ if err := tmpl.Execute(w, event); err != nil {
+ return err
+ }
+ } else {
+ if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil {
+ return err
+ }
+ }
+ if _, err := w.Write([]byte("\n")); err != nil {
+ return err
+ }
+ if err := w.Flush(); err != nil {
+ return err
+ }
+ if flags&varlink.Continues == 0 {
+ break
+ }
+ }
+ return nil
+}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 28a636fa6..32d47732b 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -454,10 +454,6 @@ func findMount(target string, mounts []*pmount.Info) (*pmount.Info, error) {
}
func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) {
- if config.PidMode.IsHost() && rootless.IsRootless() {
- return
- }
-
if !config.Privileged {
for _, mp := range []string{
"/proc/acpi",
@@ -469,10 +465,15 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator)
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware",
+ "/sys/fs/selinux",
} {
g.AddLinuxMaskedPaths(mp)
}
+ if config.PidMode.IsHost() && rootless.IsRootless() {
+ return
+ }
+
for _, rp := range []string{
"/proc/asound",
"/proc/bus",
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index a4576191b..d7e1ddd38 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
"syscall"
+ "time"
"github.com/BurntSushi/toml"
"github.com/containers/image/types"
@@ -347,3 +348,25 @@ func StorageConfigFile() string {
}
return storage.DefaultConfigFile
}
+
+// 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
+}
diff --git a/pkg/varlinkapi/events.go b/pkg/varlinkapi/events.go
new file mode 100644
index 000000000..d3fe3d65f
--- /dev/null
+++ b/pkg/varlinkapi/events.go
@@ -0,0 +1,56 @@
+package varlinkapi
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/cmd/podman/varlink"
+ "github.com/containers/libpod/libpod/events"
+)
+
+// GetEvents is a remote endpoint to get events from the event log
+func (i *LibpodAPI) GetEvents(call iopodman.VarlinkCall, filter []string, since string, stream bool, until string) error {
+ var (
+ fromStart bool
+ eventsError error
+ event *events.Event
+ )
+ if call.WantsMore() {
+ call.Continues = true
+ }
+ filters, err := shared.GenerateEventOptions(filter, since, until)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ if len(since) > 0 || len(until) > 0 {
+ fromStart = true
+ }
+ eventChannel := make(chan *events.Event)
+ go func() {
+ eventsError = i.Runtime.Events(fromStart, stream, filters, eventChannel)
+ }()
+ if eventsError != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ for {
+ event = <-eventChannel
+ if event == nil {
+ call.Continues = false
+ break
+ }
+ call.ReplyGetEvents(iopodman.Event{
+ Id: event.ID,
+ Image: event.Image,
+ Name: event.Name,
+ Status: fmt.Sprintf("%s", event.Status),
+ Time: event.Time.Format(time.RFC3339Nano),
+ Type: fmt.Sprintf("%s", event.Type),
+ })
+ if !call.Continues {
+ // For a one-shot on events, we break out here
+ break
+ }
+ }
+ return call.ReplyGetEvents(iopodman.Event{})
+}
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index ecd6d812f..afd6d3cf3 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -45,6 +45,7 @@ type PodmanTestIntegration struct {
CgroupManager string
Host HostOS
Timings []string
+ TmpDir string
}
var LockTmpDir string
@@ -245,6 +246,7 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration {
},
ConmonBinary: conmonBinary,
CrioRoot: filepath.Join(tempDir, "crio"),
+ TmpDir: tempDir,
CNIConfigDir: CNIConfigDir,
OCIRuntime: ociRuntime,
RunRoot: filepath.Join(tempDir, "crio-run"),
diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go
new file mode 100644
index 000000000..321d93757
--- /dev/null
+++ b/test/e2e/events_test.go
@@ -0,0 +1,116 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman events", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+
+ })
+
+ // For most, all, of these tests we do not "live" test following a log because it may make a fragile test
+ // system more complex. Instead we run the "events" and then verify that the events are processed correctly.
+ // Perhaps a future version of this test would put events in a go func and send output back over a channel
+ // while events occur.
+ It("podman events", func() {
+ _, ec, _ := podmanTest.RunLsContainer("")
+ Expect(ec).To(Equal(0))
+ result := podmanTest.Podman([]string{"events", "--stream=false"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(BeZero())
+ })
+
+ It("podman events with an event filter", func() {
+ SkipIfRemote()
+ _, ec, _ := podmanTest.RunLsContainer("")
+ Expect(ec).To(Equal(0))
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=start"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(Equal(1))
+ })
+
+ It("podman events with an event filter and container=cid", func() {
+ SkipIfRemote()
+ _, ec, cid := podmanTest.RunLsContainer("")
+ Expect(ec).To(Equal(0))
+ _, ec2, cid2 := podmanTest.RunLsContainer("")
+ Expect(ec2).To(Equal(0))
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=start", "--filter", fmt.Sprintf("container=%s", cid)})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(Equal(1))
+ Expect(!strings.Contains(result.OutputToString(), cid2))
+ })
+
+ It("podman events with a type", func() {
+ SkipIfRemote()
+ _, ec, _ := podmanTest.RunLsContainer("")
+ Expect(ec).To(Equal(0))
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(Equal(0))
+ })
+
+ It("podman events with a type", func() {
+ SkipIfRemote()
+ setup := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:foobar", ALPINE, "top"})
+ setup.WaitWithDefaultTimeout()
+ stop := podmanTest.Podman([]string{"pod", "stop", "foobar"})
+ stop.WaitWithDefaultTimeout()
+ Expect(stop.ExitCode()).To(Equal(0))
+ Expect(setup.ExitCode()).To(Equal(0))
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ fmt.Println(result.OutputToStringArray())
+ Expect(len(result.OutputToStringArray())).To(Equal(2))
+ })
+
+ It("podman events --since", func() {
+ _, ec, _ := podmanTest.RunLsContainer("")
+ Expect(ec).To(Equal(0))
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1m"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(BeZero())
+ })
+
+ It("podman events --until", func() {
+ _, ec, _ := podmanTest.RunLsContainer("")
+ Expect(ec).To(Equal(0))
+ test := podmanTest.Podman([]string{"events", "--help"})
+ test.WaitWithDefaultTimeout()
+ fmt.Println(test.OutputToStringArray())
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1h"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(BeZero())
+ })
+
+})
diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go
index 33e05b872..1a3f37e23 100644
--- a/test/e2e/libpod_suite_test.go
+++ b/test/e2e/libpod_suite_test.go
@@ -206,8 +206,8 @@ func PodmanTestCreate(tempDir string) *PodmanTestIntegration {
//MakeOptions assembles all the podman main options
func (p *PodmanTestIntegration) makeOptions(args []string) []string {
- podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s",
- p.CrioRoot, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ")
+ podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s --tmpdir %s",
+ p.CrioRoot, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager, p.TmpDir), " ")
if os.Getenv("HOOK_OPTION") != "" {
podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION"))
}
diff --git a/transfer.md b/transfer.md
index eec63d146..998a0a9e7 100644
--- a/transfer.md
+++ b/transfer.md
@@ -44,6 +44,7 @@ There are other equivalents for these tools
| `docker container`|[`podman container`](./docs/podman-container.1.md) |
| `docker create` | [`podman create`](./docs/podman-create.1.md) |
| `docker diff` | [`podman diff`](./docs/podman-diff.1.md) |
+| `docker events` | [`podman events`](./docs/podman-events.1.md) |
| `docker export` | [`podman export`](./docs/podman-export.1.md) |
| `docker history` | [`podman history`](./docs/podman-history.1.md) |
| `docker image` | [`podman image`](./docs/podman-image.1.md) |
@@ -89,7 +90,6 @@ Those Docker commands currently do not have equivalents in `podman`:
| Missing command | Description|
| :--- | :--- |
-| `docker events` ||
| `docker network` ||
| `docker node` ||
| `docker plugin` | podman does not support plugins. We recommend you use alternative OCI Runtimes or OCI Runtime Hooks to alter behavior of podman.|
diff --git a/troubleshooting.md b/troubleshooting.md
index 33434cdbb..74b2e76df 100644
--- a/troubleshooting.md
+++ b/troubleshooting.md
@@ -293,3 +293,21 @@ tells SELinux to apply the labels to the actual content.
Now all new content created in these directories will automatically be created
with the correct label.
+
+### 12) Running Podman inside a container causes container crashes and inconsistent states
+
+Running Podman in a container and forwarding some, but not all, of the required host directories can cause inconsistent container behavior.
+
+#### Symptom
+
+After creating a container with Podman's storage directories mounted in from the host and running Podman inside a container, all containers show their state as "configured" or "created", even if they were running or stopped.
+
+#### Solution
+
+When running Podman inside a container, it is recommended to mount at a minimum `/var/lib/containers/storage/` as a volume.
+Typically, you will not mount in the host version of the directory, but if you wish to share containers with the host, you can do so.
+If you do mount in the host's `/var/lib/containers/storage`, however, you must also mount in the host's `/var/run/libpod` and `/var/run/containers/storage` directories.
+Not doing this will cause Podman in the container to detect that temporary files have been cleared, leading it to assume a system restart has taken place.
+This can cause Podman to reset container states and lose track of running containers.
+
+For running containers on the host from inside a container, we also recommend the [Podman remote client](remote_client.md), which only requires a single socket to be mounted into the container.
diff --git a/vendor.conf b/vendor.conf
index e7aec5e35..1fd60f701 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -93,7 +93,7 @@ k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apim
k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go
github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7
github.com/varlink/go 3ac79db6fd6aec70924193b090962f92985fe199
-github.com/containers/buildah 11dd2197dfffedb40687de1d667e6c9fb0708de9
+github.com/containers/buildah 345ffc2b29b4255a83cfa763db88799d8ec9c569 https://github.com/QiWang19/buildah
# TODO: Gotty has not been updated since 2012. Can we find replacement?
github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512
# do not go beyond the below commit as the next one requires a more recent
diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go
index d69eab52f..4f0ffac1c 100644
--- a/vendor/github.com/containers/buildah/imagebuildah/build.go
+++ b/vendor/github.com/containers/buildah/imagebuildah/build.go
@@ -293,7 +293,7 @@ func (b *Executor) Preserve(path string) error {
// Try and resolve the symlink (if one exists)
// Set archivedPath and path based on whether a symlink is found or not
- if symLink, err := ResolveSymLink(b.mountPoint, path); err == nil {
+ if symLink, err := resolveSymlink(b.mountPoint, path); err == nil {
archivedPath = filepath.Join(b.mountPoint, symLink)
path = symLink
} else {
diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go
index 6feedf6a5..86bf7653b 100644
--- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go
+++ b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go
@@ -24,9 +24,7 @@ func init() {
reexec.Register(symlinkModifiedTime, resolveSymlinkTimeModified)
}
-// main() for grandparent subprocess. Its main job is to shuttle stdio back
-// and forth, managing a pseudo-terminal if we want one, for our child, the
-// parent subprocess.
+// main() for resolveSymlink()'s subprocess.
func resolveChrootedSymlinks() {
status := 0
flag.Parse()
@@ -57,9 +55,9 @@ func resolveChrootedSymlinks() {
os.Exit(status)
}
-// ResolveSymLink (in the grandparent process) resolves any symlink in filename
+// resolveSymlink uses a child subprocess to resolve any symlinks in filename
// in the context of rootdir.
-func ResolveSymLink(rootdir, filename string) (string, error) {
+func resolveSymlink(rootdir, filename string) (string, error) {
// The child process expects a chroot and one path that
// will be consulted relative to the chroot directory and evaluated
// for any symbolic links present.
@@ -253,7 +251,7 @@ func hasSymlink(path string) (bool, string, error) {
}
// if the symlink points to a relative path, prepend the path till now to the resolved path
if !filepath.IsAbs(targetDir) {
- targetDir = filepath.Join(path, targetDir)
+ targetDir = filepath.Join(filepath.Dir(path), targetDir)
}
// run filepath.Clean to remove the ".." from relative paths
return true, filepath.Clean(targetDir), nil
diff --git a/cmd/podman/formats/formats.go b/vendor/github.com/containers/buildah/pkg/formats/formats.go
index 37f9b8a20..37f9b8a20 100644
--- a/cmd/podman/formats/formats.go
+++ b/vendor/github.com/containers/buildah/pkg/formats/formats.go
diff --git a/cmd/podman/formats/templates.go b/vendor/github.com/containers/buildah/pkg/formats/templates.go
index c2582552a..c2582552a 100644
--- a/cmd/podman/formats/templates.go
+++ b/vendor/github.com/containers/buildah/pkg/formats/templates.go
diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go
index 4d6d28380..f56ce30b1 100644
--- a/vendor/github.com/containers/buildah/run.go
+++ b/vendor/github.com/containers/buildah/run.go
@@ -1,7 +1,6 @@
package buildah
import (
- "bufio"
"bytes"
"encoding/json"
"fmt"
@@ -272,36 +271,6 @@ func addRlimits(ulimit []string, g *generate.Generator) error {
return nil
}
-func addHosts(hosts []string, w io.Writer) error {
- buf := bufio.NewWriter(w)
- for _, host := range hosts {
- values := strings.SplitN(host, ":", 2)
- if len(values) != 2 {
- return errors.Errorf("unable to parse host entry %q: incorrect format", host)
- }
- if values[0] == "" {
- return errors.Errorf("hostname in host entry %q is empty", host)
- }
- if values[1] == "" {
- return errors.Errorf("IP address in host entry %q is empty", host)
- }
- fmt.Fprintf(buf, "%s\t%s\n", values[1], values[0])
- }
- return buf.Flush()
-}
-
-func addHostsToFile(hosts []string, filename string) error {
- if len(hosts) == 0 {
- return nil
- }
- file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
- if err != nil {
- return errors.Wrapf(err, "error creating hosts file %q", filename)
- }
- defer file.Close()
- return addHosts(hosts, file)
-}
-
func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator) error {
// Resources - CPU
if commonOpts.CPUPeriod != 0 {
@@ -638,6 +607,59 @@ func (b *Builder) addNetworkConfig(rdir, hostPath string, chownOpts *idtools.IDP
return cfile, nil
}
+// generateHosts creates a containers hosts file
+func (b *Builder) generateHosts(rdir, hostname string, addHosts []string, chownOpts *idtools.IDPair) (string, error) {
+ hostPath := "/etc/hosts"
+ stat, err := os.Stat(hostPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "error statting %q for container %q", hostPath, b.ContainerID)
+ }
+
+ hosts := bytes.NewBufferString("# Generated by Buildah\n")
+ orig, err := ioutil.ReadFile(hostPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "unable to read %s", hostPath)
+ }
+ hosts.Write(orig)
+ for _, host := range addHosts {
+ // verify the host format
+ values := strings.SplitN(host, ":", 2)
+ if len(values) != 2 {
+ return "", errors.Errorf("unable to parse host entry %q: incorrect format", host)
+ }
+ if values[0] == "" {
+ return "", errors.Errorf("hostname in host entry %q is empty", host)
+ }
+ if values[1] == "" {
+ return "", errors.Errorf("IP address in host entry %q is empty", host)
+ }
+ hosts.Write([]byte(fmt.Sprintf("%s\t%s\n", values[1], values[0])))
+ }
+
+ if hostname != "" {
+ hosts.Write([]byte(fmt.Sprintf("127.0.0.1 %s\n", hostname)))
+ hosts.Write([]byte(fmt.Sprintf("::1 %s\n", hostname)))
+ }
+ cfile := filepath.Join(rdir, filepath.Base(hostPath))
+ if err = ioutils.AtomicWriteFile(cfile, hosts.Bytes(), stat.Mode().Perm()); err != nil {
+ return "", errors.Wrapf(err, "error writing /etc/hosts into the container")
+ }
+ uid := int(stat.Sys().(*syscall.Stat_t).Uid)
+ gid := int(stat.Sys().(*syscall.Stat_t).Gid)
+ if chownOpts != nil {
+ uid = chownOpts.UID
+ gid = chownOpts.GID
+ }
+ if err = os.Chown(cfile, uid, gid); err != nil {
+ return "", errors.Wrapf(err, "error chowning file %q for container %q", cfile, b.ContainerID)
+ }
+ if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
+ return "", errors.Wrapf(err, "error relabeling %q in container %q", cfile, b.ContainerID)
+ }
+
+ return cfile, nil
+}
+
func setupMaskedPaths(g *generate.Generator) {
for _, mp := range []string{
"/proc/acpi",
@@ -1081,15 +1103,11 @@ func (b *Builder) Run(command []string, options RunOptions) error {
volumes := b.Volumes()
if !contains(volumes, "/etc/hosts") {
- hostFile, err := b.addNetworkConfig(path, "/etc/hosts", rootIDPair)
+ hostFile, err := b.generateHosts(path, spec.Hostname, b.CommonBuildOpts.AddHost, rootIDPair)
if err != nil {
return err
}
bindFiles["/etc/hosts"] = hostFile
-
- if err := addHostsToFile(b.CommonBuildOpts.AddHost, hostFile); err != nil {
- return err
- }
}
if !contains(volumes, "/etc/resolv.conf") {