mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
events filtering
Signed-off-by: Victor Vieux <vieux@docker.com>
This commit is contained in:
parent
998b591a71
commit
7ff3b81054
6 changed files with 179 additions and 24 deletions
|
@ -1760,6 +1760,10 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
cmd := cli.Subcmd("events", "", "Get real time events from the server")
|
cmd := cli.Subcmd("events", "", "Get real time events from the server")
|
||||||
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
||||||
until := cmd.String([]string{"-until"}, "", "Stream events until this 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 {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1769,9 +1773,20 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
v = url.Values{}
|
v = url.Values{}
|
||||||
loc = time.FixedZone(time.Now().Zone())
|
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) {
|
var setTime = func(key, value string) {
|
||||||
format := timeutils.RFC3339NanoFixed
|
format := timeutils.RFC3339NanoFixed
|
||||||
if len(value) < len(format) {
|
if len(value) < len(format) {
|
||||||
|
@ -1789,6 +1804,13 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
if *until != "" {
|
if *until != "" {
|
||||||
setTime("until", *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 {
|
if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,6 +315,7 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||||
streamJSON(job, w, true)
|
streamJSON(job, w, true)
|
||||||
job.Setenv("since", r.Form.Get("since"))
|
job.Setenv("since", r.Form.Get("since"))
|
||||||
job.Setenv("until", r.Form.Get("until"))
|
job.Setenv("until", r.Form.Get("until"))
|
||||||
|
job.Setenv("filters", r.Form.Get("filters"))
|
||||||
return job.Run()
|
return job.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1386,6 +1386,7 @@ Query Parameters:
|
||||||
|
|
||||||
- **since** – timestamp used for polling
|
- **since** – timestamp used for polling
|
||||||
- **until** – 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:
|
Status Codes:
|
||||||
|
|
||||||
|
|
|
@ -610,7 +610,10 @@ For example:
|
||||||
Usage: docker events [OPTIONS]
|
Usage: docker events [OPTIONS]
|
||||||
|
|
||||||
Get real time events from the server
|
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
|
--since="" Show all events created since timestamp
|
||||||
--until="" Stream events until this timestamp
|
--until="" Stream events until this timestamp
|
||||||
|
|
||||||
|
@ -622,6 +625,24 @@ and Docker images will report:
|
||||||
|
|
||||||
untag, delete
|
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` only when the event type is `start`
|
||||||
|
|
||||||
|
Current filters:
|
||||||
|
* event
|
||||||
|
* image
|
||||||
|
* container
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
You'll need two shells for this example.
|
You'll need two shells for this example.
|
||||||
|
@ -630,31 +651,64 @@ You'll need two shells for this example.
|
||||||
|
|
||||||
$ sudo docker events
|
$ sudo docker events
|
||||||
|
|
||||||
**Shell 2: Start and Stop a Container:**
|
**Shell 2: Start and Stop containers:**
|
||||||
|
|
||||||
$ sudo docker start 4386fb97867d
|
$ sudo docker start 4386fb97867d
|
||||||
$ sudo docker stop 4386fb97867d
|
$ sudo docker stop 4386fb97867d
|
||||||
|
$ sudo docker stop 7805c1d35632
|
||||||
|
|
||||||
**Shell 1: (Again .. now showing events):**
|
**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 ubuntu-1:14.04) start
|
||||||
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
|
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) 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) 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:**
|
**Show events in the past from a specified time:**
|
||||||
|
|
||||||
$ sudo docker events --since 1378216169
|
$ 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 ubuntu-1:14.04) die
|
||||||
2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
|
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'
|
$ 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 ubuntu-1:14.04) start
|
||||||
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
|
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
|
||||||
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
|
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'
|
$ 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 ubuntu-1:14.04) die
|
||||||
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
|
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
|
## exec
|
||||||
|
|
||||||
|
@ -768,7 +822,7 @@ by default.
|
||||||
|
|
||||||
#### Filtering
|
#### 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"`)
|
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||||
|
|
||||||
Current filters:
|
Current filters:
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
"github.com/docker/docker/utils"
|
"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()))
|
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 no until, disable timeout
|
||||||
if until == 0 {
|
if until == 0 {
|
||||||
timeout.Stop()
|
timeout.Stop()
|
||||||
|
@ -61,7 +67,7 @@ func (e *Events) Get(job *engine.Job) engine.Status {
|
||||||
|
|
||||||
// Resend every event in the [since, until] time interval.
|
// Resend every event in the [since, until] time interval.
|
||||||
if since != 0 {
|
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)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +78,7 @@ func (e *Events) Get(job *engine.Job) engine.Status {
|
||||||
if !ok {
|
if !ok {
|
||||||
return engine.StatusOK
|
return engine.StatusOK
|
||||||
}
|
}
|
||||||
if err := writeEvent(job, event); err != nil {
|
if err := writeEvent(job, event, eventFilters); err != nil {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
case <-timeout.C:
|
case <-timeout.C:
|
||||||
|
@ -97,7 +103,23 @@ func (e *Events) SubscribersCount(job *engine.Job) engine.Status {
|
||||||
return engine.StatusOK
|
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
|
// When sending an event JSON serialization errors are ignored, but all
|
||||||
// other errors lead to the eviction of the listener.
|
// other errors lead to the eviction of the listener.
|
||||||
if b, err := json.Marshal(event); err == nil {
|
if b, err := json.Marshal(event); err == nil {
|
||||||
|
@ -108,11 +130,11 @@ func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
|
||||||
return nil
|
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()
|
e.mu.RLock()
|
||||||
for _, event := range e.events {
|
for _, event := range e.events {
|
||||||
if event.Time >= since && (event.Time <= until || until == 0) {
|
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()
|
e.mu.RUnlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestEventsPause(t *testing.T) {
|
||||||
t.Fatalf("event should be pause, not %#v", pauseEvent)
|
t.Fatalf("event should be pause, not %#v", pauseEvent)
|
||||||
}
|
}
|
||||||
if unpauseEvent[len(unpauseEvent)-1] != "unpause" {
|
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)
|
waitCmd := exec.Command(dockerBinary, "wait", name)
|
||||||
|
@ -138,13 +138,13 @@ func TestEventsContainerEvents(t *testing.T) {
|
||||||
t.Fatalf("event should be create, not %#v", createEvent)
|
t.Fatalf("event should be create, not %#v", createEvent)
|
||||||
}
|
}
|
||||||
if startEvent[len(startEvent)-1] != "start" {
|
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" {
|
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" {
|
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")
|
logDone("events - container create, start, die, destroy is logged")
|
||||||
|
@ -283,3 +283,58 @@ func TestEventsImageImport(t *testing.T) {
|
||||||
|
|
||||||
logDone("events - image import is logged")
|
logDone("events - image import is logged")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEventsFilters(t *testing.T) {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
cmd(t, "run", "--rm", "busybox", "true")
|
||||||
|
cmd(t, "run", "--rm", "busybox", "true")
|
||||||
|
eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", now), 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 {
|
||||||
|
fmt.Printf("%v\n", events)
|
||||||
|
t.Fatalf("Unexpected event")
|
||||||
|
}
|
||||||
|
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", "--since=0", 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("Unexpected event")
|
||||||
|
}
|
||||||
|
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…
Add table
Reference in a new issue