Merge pull request #8893 from vieux/filter_events
Events filtering (daemon side)
This commit is contained in:
commit
9160e01cef
|
@ -1782,6 +1782,10 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
|||
cmd := cli.Subcmd("events", "", "Get real time events from the server")
|
||||
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
||||
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
||||
|
||||
flFilter := opts.NewListOpts(nil)
|
||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'event=stop')")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -1791,9 +1795,20 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
var (
|
||||
v = url.Values{}
|
||||
loc = time.FixedZone(time.Now().Zone())
|
||||
v = url.Values{}
|
||||
loc = time.FixedZone(time.Now().Zone())
|
||||
eventFilterArgs = filters.Args{}
|
||||
)
|
||||
|
||||
// Consolidate all filter flags, and sanity check them early.
|
||||
// They'll get process in the daemon/server.
|
||||
for _, f := range flFilter.GetAll() {
|
||||
var err error
|
||||
eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var setTime = func(key, value string) {
|
||||
format := timeutils.RFC3339NanoFixed
|
||||
if len(value) < len(format) {
|
||||
|
@ -1811,6 +1826,13 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
|||
if *until != "" {
|
||||
setTime("until", *until)
|
||||
}
|
||||
if len(eventFilterArgs) > 0 {
|
||||
filterJson, err := filters.ToParam(eventFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set("filters", filterJson)
|
||||
}
|
||||
if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -315,6 +315,7 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
|||
streamJSON(job, w, true)
|
||||
job.Setenv("since", r.Form.Get("since"))
|
||||
job.Setenv("until", r.Form.Get("until"))
|
||||
job.Setenv("filters", r.Form.Get("filters"))
|
||||
return job.Run()
|
||||
}
|
||||
|
||||
|
|
|
@ -1388,6 +1388,7 @@ Query Parameters:
|
|||
|
||||
- **since** – timestamp used for polling
|
||||
- **until** – timestamp used for polling
|
||||
- **filters** – a json encoded value of the filters (a map[string][]string) to process on the event list.
|
||||
|
||||
Status Codes:
|
||||
|
||||
|
|
|
@ -614,7 +614,10 @@ For example:
|
|||
Usage: docker events [OPTIONS]
|
||||
|
||||
Get real time events from the server
|
||||
|
||||
-f, --filter=[] Provide filter values. Valid filters:
|
||||
event=<string> - event to filter
|
||||
image=<string> - image to filter
|
||||
container=<string> - container to filter
|
||||
--since="" Show all events created since timestamp
|
||||
--until="" Stream events until this timestamp
|
||||
|
||||
|
@ -626,6 +629,24 @@ and Docker images will report:
|
|||
|
||||
untag, delete
|
||||
|
||||
#### Filtering
|
||||
|
||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If you would like to use
|
||||
multiple filters, pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||
|
||||
Using the same filter multiple times will be handled as a *OR*; for example
|
||||
`--filter container=588a23dac085 --filter container=a8f7720b8c22` will display events for
|
||||
container 588a23dac085 *OR* container a8f7720b8c22
|
||||
|
||||
Using multiple filters will be handled as a *AND*; for example
|
||||
`--filter container=588a23dac085 --filter event=start` will display events for container
|
||||
container 588a23dac085 *AND* the event type is *start*
|
||||
|
||||
Current filters:
|
||||
* event
|
||||
* image
|
||||
* container
|
||||
|
||||
#### Examples
|
||||
|
||||
You'll need two shells for this example.
|
||||
|
@ -634,31 +655,64 @@ You'll need two shells for this example.
|
|||
|
||||
$ sudo docker events
|
||||
|
||||
**Shell 2: Start and Stop a Container:**
|
||||
**Shell 2: Start and Stop containers:**
|
||||
|
||||
$ sudo docker start 4386fb97867d
|
||||
$ sudo docker stop 4386fb97867d
|
||||
$ sudo docker stop 7805c1d35632
|
||||
|
||||
**Shell 1: (Again .. now showing events):**
|
||||
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) start
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
|
||||
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
|
||||
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
|
||||
|
||||
**Show events in the past from a specified time:**
|
||||
|
||||
$ sudo docker events --since 1378216169
|
||||
2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
|
||||
2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
|
||||
2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
|
||||
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
|
||||
2014-03-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
|
||||
|
||||
$ sudo docker events --since '2013-09-03'
|
||||
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) start
|
||||
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
|
||||
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
|
||||
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
|
||||
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
|
||||
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
|
||||
2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
|
||||
|
||||
$ sudo docker events --since '2013-09-03 15:49:29 +0200 CEST'
|
||||
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
|
||||
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
|
||||
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
|
||||
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
|
||||
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
|
||||
|
||||
**Filter events:**
|
||||
|
||||
$ sudo docker events --filter 'event=stop'
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
|
||||
2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
|
||||
|
||||
$ sudo docker events --filter 'image=ubuntu-1:14.04'
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
|
||||
|
||||
$ sudo docker events --filter 'container=7805c1d35632'
|
||||
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
|
||||
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
|
||||
|
||||
$ sudo docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d'
|
||||
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
|
||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
|
||||
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
|
||||
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
|
||||
|
||||
$ sudo docker events --filter 'container=7805c1d35632' --filter 'event=stop'
|
||||
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
|
||||
|
||||
## exec
|
||||
|
||||
|
@ -777,7 +831,7 @@ uses up the `VIRTUAL SIZE` listed only once.
|
|||
|
||||
#### Filtering
|
||||
|
||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If there are more
|
||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||
|
||||
Current filters:
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/parsers/filters"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
|
@ -48,6 +49,11 @@ func (e *Events) Get(job *engine.Job) engine.Status {
|
|||
timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now()))
|
||||
)
|
||||
|
||||
eventFilters, err := filters.FromParam(job.Getenv("filters"))
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
// If no until, disable timeout
|
||||
if until == 0 {
|
||||
timeout.Stop()
|
||||
|
@ -61,7 +67,7 @@ func (e *Events) Get(job *engine.Job) engine.Status {
|
|||
|
||||
// Resend every event in the [since, until] time interval.
|
||||
if since != 0 {
|
||||
if err := e.writeCurrent(job, since, until); err != nil {
|
||||
if err := e.writeCurrent(job, since, until, eventFilters); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +78,7 @@ func (e *Events) Get(job *engine.Job) engine.Status {
|
|||
if !ok {
|
||||
return engine.StatusOK
|
||||
}
|
||||
if err := writeEvent(job, event); err != nil {
|
||||
if err := writeEvent(job, event, eventFilters); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
case <-timeout.C:
|
||||
|
@ -97,7 +103,23 @@ func (e *Events) SubscribersCount(job *engine.Job) engine.Status {
|
|||
return engine.StatusOK
|
||||
}
|
||||
|
||||
func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
|
||||
func writeEvent(job *engine.Job, event *utils.JSONMessage, eventFilters filters.Args) error {
|
||||
isFiltered := func(field string, filter []string) bool {
|
||||
if len(filter) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, v := range filter {
|
||||
if v == field {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if isFiltered(event.Status, eventFilters["event"]) || isFiltered(event.From, eventFilters["image"]) || isFiltered(event.ID, eventFilters["container"]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// When sending an event JSON serialization errors are ignored, but all
|
||||
// other errors lead to the eviction of the listener.
|
||||
if b, err := json.Marshal(event); err == nil {
|
||||
|
@ -108,11 +130,11 @@ func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *Events) writeCurrent(job *engine.Job, since, until int64) error {
|
||||
func (e *Events) writeCurrent(job *engine.Job, since, until int64, eventFilters filters.Args) error {
|
||||
e.mu.RLock()
|
||||
for _, event := range e.events {
|
||||
if event.Time >= since && (event.Time <= until || until == 0) {
|
||||
if err := writeEvent(job, event); err != nil {
|
||||
if err := writeEvent(job, event, eventFilters); err != nil {
|
||||
e.mu.RUnlock()
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestEventsPause(t *testing.T) {
|
|||
t.Fatalf("event should be pause, not %#v", pauseEvent)
|
||||
}
|
||||
if unpauseEvent[len(unpauseEvent)-1] != "unpause" {
|
||||
t.Fatalf("event should be pause, not %#v", unpauseEvent)
|
||||
t.Fatalf("event should be unpause, not %#v", unpauseEvent)
|
||||
}
|
||||
|
||||
waitCmd := exec.Command(dockerBinary, "wait", name)
|
||||
|
@ -138,13 +138,13 @@ func TestEventsContainerEvents(t *testing.T) {
|
|||
t.Fatalf("event should be create, not %#v", createEvent)
|
||||
}
|
||||
if startEvent[len(startEvent)-1] != "start" {
|
||||
t.Fatalf("event should be pause, not %#v", startEvent)
|
||||
t.Fatalf("event should be start, not %#v", startEvent)
|
||||
}
|
||||
if dieEvent[len(dieEvent)-1] != "die" {
|
||||
t.Fatalf("event should be pause, not %#v", dieEvent)
|
||||
t.Fatalf("event should be die, not %#v", dieEvent)
|
||||
}
|
||||
if destroyEvent[len(destroyEvent)-1] != "destroy" {
|
||||
t.Fatalf("event should be pause, not %#v", destroyEvent)
|
||||
t.Fatalf("event should be destroy, not %#v", destroyEvent)
|
||||
}
|
||||
|
||||
logDone("events - container create, start, die, destroy is logged")
|
||||
|
@ -283,3 +283,57 @@ func TestEventsImageImport(t *testing.T) {
|
|||
|
||||
logDone("events - image import is logged")
|
||||
}
|
||||
|
||||
func TestEventsFilters(t *testing.T) {
|
||||
since := time.Now().Unix()
|
||||
cmd(t, "run", "--rm", "busybox", "true")
|
||||
cmd(t, "run", "--rm", "busybox", "true")
|
||||
eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", time.Now().Unix()), "--filter", "event=die")
|
||||
out, exitCode, err := runCommandWithOutput(eventsCmd)
|
||||
if exitCode != 0 || err != nil {
|
||||
t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
|
||||
}
|
||||
events := strings.Split(out, "\n")
|
||||
events = events[:len(events)-1]
|
||||
if len(events) != 2 {
|
||||
t.Fatalf("Expected 2 events, got %d: %v", len(events), events)
|
||||
}
|
||||
dieEvent := strings.Fields(events[len(events)-1])
|
||||
if dieEvent[len(dieEvent)-1] != "die" {
|
||||
t.Fatalf("event should be die, not %#v", dieEvent)
|
||||
}
|
||||
|
||||
dieEvent = strings.Fields(events[len(events)-2])
|
||||
if dieEvent[len(dieEvent)-1] != "die" {
|
||||
t.Fatalf("event should be die, not %#v", dieEvent)
|
||||
}
|
||||
|
||||
eventsCmd = exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", time.Now().Unix()), "--filter", "event=die", "--filter", "event=start")
|
||||
out, exitCode, err = runCommandWithOutput(eventsCmd)
|
||||
if exitCode != 0 || err != nil {
|
||||
t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
|
||||
}
|
||||
events = strings.Split(out, "\n")
|
||||
events = events[:len(events)-1]
|
||||
if len(events) != 4 {
|
||||
t.Fatalf("Expected 4 events, got %d: %v", len(events), events)
|
||||
}
|
||||
startEvent := strings.Fields(events[len(events)-4])
|
||||
if startEvent[len(startEvent)-1] != "start" {
|
||||
t.Fatalf("event should be start, not %#v", startEvent)
|
||||
}
|
||||
dieEvent = strings.Fields(events[len(events)-3])
|
||||
if dieEvent[len(dieEvent)-1] != "die" {
|
||||
t.Fatalf("event should be die, not %#v", dieEvent)
|
||||
}
|
||||
startEvent = strings.Fields(events[len(events)-2])
|
||||
if startEvent[len(startEvent)-1] != "start" {
|
||||
t.Fatalf("event should be start, not %#v", startEvent)
|
||||
}
|
||||
dieEvent = strings.Fields(events[len(events)-1])
|
||||
if dieEvent[len(dieEvent)-1] != "die" {
|
||||
t.Fatalf("event should be die, not %#v", dieEvent)
|
||||
}
|
||||
|
||||
logDone("events - filters")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue