Browse code

events filtering

Signed-off-by: Victor Vieux <vieux@docker.com>

Victor Vieux authored on 2014/11/21 04:46:48
Showing 6 changed files
... ...
@@ -1760,6 +1760,10 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
1760 1760
 	cmd := cli.Subcmd("events", "", "Get real time events from the server")
1761 1761
 	since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
1762 1762
 	until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
1763
+
1764
+	flFilter := opts.NewListOpts(nil)
1765
+	cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'event=stop')")
1766
+
1763 1767
 	if err := cmd.Parse(args); err != nil {
1764 1768
 		return nil
1765 1769
 	}
... ...
@@ -1769,9 +1773,20 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
1769 1769
 		return nil
1770 1770
 	}
1771 1771
 	var (
1772
-		v   = url.Values{}
1773
-		loc = time.FixedZone(time.Now().Zone())
1772
+		v               = url.Values{}
1773
+		loc             = time.FixedZone(time.Now().Zone())
1774
+		eventFilterArgs = filters.Args{}
1774 1775
 	)
1776
+
1777
+	// Consolidate all filter flags, and sanity check them early.
1778
+	// They'll get process in the daemon/server.
1779
+	for _, f := range flFilter.GetAll() {
1780
+		var err error
1781
+		eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
1782
+		if err != nil {
1783
+			return err
1784
+		}
1785
+	}
1775 1786
 	var setTime = func(key, value string) {
1776 1787
 		format := timeutils.RFC3339NanoFixed
1777 1788
 		if len(value) < len(format) {
... ...
@@ -1789,6 +1804,13 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
1789 1789
 	if *until != "" {
1790 1790
 		setTime("until", *until)
1791 1791
 	}
1792
+	if len(eventFilterArgs) > 0 {
1793
+		filterJson, err := filters.ToParam(eventFilterArgs)
1794
+		if err != nil {
1795
+			return err
1796
+		}
1797
+		v.Set("filters", filterJson)
1798
+	}
1792 1799
 	if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
1793 1800
 		return err
1794 1801
 	}
... ...
@@ -315,6 +315,7 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite
315 315
 	streamJSON(job, w, true)
316 316
 	job.Setenv("since", r.Form.Get("since"))
317 317
 	job.Setenv("until", r.Form.Get("until"))
318
+	job.Setenv("filters", r.Form.Get("filters"))
318 319
 	return job.Run()
319 320
 }
320 321
 
... ...
@@ -1386,6 +1386,7 @@ Query Parameters:
1386 1386
 
1387 1387
 -   **since** – timestamp used for polling
1388 1388
 -   **until** – timestamp used for polling
1389
+-   **filters** – a json encoded value of the filters (a map[string][]string) to process on the event list.
1389 1390
 
1390 1391
 Status Codes:
1391 1392
 
... ...
@@ -610,7 +610,10 @@ For example:
610 610
     Usage: docker events [OPTIONS]
611 611
 
612 612
     Get real time events from the server
613
-
613
+      -f, --filter=[]    Provide filter values. Valid filters:
614
+                           event=<string> - event to filter
615
+                           image=<string> - image to filter
616
+                           container=<string> - container to filter
614 617
       --since=""         Show all events created since timestamp
615 618
       --until=""         Stream events until this timestamp
616 619
 
... ...
@@ -622,6 +625,24 @@ and Docker images will report:
622 622
 
623 623
     untag, delete
624 624
 
625
+#### Filtering
626
+
627
+The filtering flag (`-f` or `--filter`) format is of "key=value". If you would like to use
628
+multiple filters, pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
629
+
630
+Using the same filter multiple times will be handled as a `OR`; for example
631
+`--filter container=588a23dac085 --filter container=a8f7720b8c22` will display events for
632
+container 588a23dac085 `OR` container a8f7720b8c22
633
+
634
+Using multiple filters will be handled as a `AND`; for example
635
+`--filter container=588a23dac085 --filter event=start` will display events for container
636
+container 588a23dac085 `AND` only when the event type is `start`
637
+
638
+Current filters:
639
+ * event
640
+ * image
641
+ * container
642
+
625 643
 #### Examples
626 644
 
627 645
 You'll need two shells for this example.
... ...
@@ -630,31 +651,64 @@ You'll need two shells for this example.
630 630
 
631 631
     $ sudo docker events
632 632
 
633
-**Shell 2: Start and Stop a Container:**
633
+**Shell 2: Start and Stop containers:**
634 634
 
635 635
     $ sudo docker start 4386fb97867d
636 636
     $ sudo docker stop 4386fb97867d
637
+    $ sudo docker stop 7805c1d35632
637 638
 
638 639
 **Shell 1: (Again .. now showing events):**
639 640
 
640
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) start
641
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
642
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
641
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
642
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
643
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
644
+    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
645
+    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
643 646
 
644 647
 **Show events in the past from a specified time:**
645 648
 
646 649
     $ sudo docker events --since 1378216169
647
-    2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
648
-    2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
650
+    2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
651
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
652
+    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
653
+    2014-03-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
649 654
 
650 655
     $ sudo docker events --since '2013-09-03'
651
-    2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) start
652
-    2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
653
-    2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
656
+    2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
657
+    2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
658
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
659
+    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
660
+    2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
654 661
 
655 662
     $ sudo docker events --since '2013-09-03 15:49:29 +0200 CEST'
656
-    2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
657
-    2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
663
+    2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
664
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
665
+    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
666
+    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
667
+
668
+**Filter events:**
669
+
670
+    $ sudo docker events --filter 'event=stop'
671
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
672
+    2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
673
+
674
+    $ sudo docker events --filter 'image=ubuntu-1:14.04'
675
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
676
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
677
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
678
+
679
+    $ sudo docker events --filter 'container=7805c1d35632'
680
+    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
681
+    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
682
+
683
+    $ sudo docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d'
684
+    2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
685
+    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
686
+    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
687
+    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
688
+
689
+    $ sudo docker events --filter 'container=7805c1d35632' --filter 'event=stop'
690
+    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
658 691
 
659 692
 ## exec
660 693
 
... ...
@@ -768,7 +822,7 @@ by default.
768 768
 
769 769
 #### Filtering
770 770
 
771
-The filtering flag (`-f` or `--filter`) format is of "key=value". If there are more
771
+The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
772 772
 than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
773 773
 
774 774
 Current filters:
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"time"
7 7
 
8 8
 	"github.com/docker/docker/engine"
9
+	"github.com/docker/docker/pkg/parsers/filters"
9 10
 	"github.com/docker/docker/utils"
10 11
 )
11 12
 
... ...
@@ -48,6 +49,11 @@ func (e *Events) Get(job *engine.Job) engine.Status {
48 48
 		timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now()))
49 49
 	)
50 50
 
51
+	eventFilters, err := filters.FromParam(job.Getenv("filters"))
52
+	if err != nil {
53
+		return job.Error(err)
54
+	}
55
+
51 56
 	// If no until, disable timeout
52 57
 	if until == 0 {
53 58
 		timeout.Stop()
... ...
@@ -61,7 +67,7 @@ func (e *Events) Get(job *engine.Job) engine.Status {
61 61
 
62 62
 	// Resend every event in the [since, until] time interval.
63 63
 	if since != 0 {
64
-		if err := e.writeCurrent(job, since, until); err != nil {
64
+		if err := e.writeCurrent(job, since, until, eventFilters); err != nil {
65 65
 			return job.Error(err)
66 66
 		}
67 67
 	}
... ...
@@ -72,7 +78,7 @@ func (e *Events) Get(job *engine.Job) engine.Status {
72 72
 			if !ok {
73 73
 				return engine.StatusOK
74 74
 			}
75
-			if err := writeEvent(job, event); err != nil {
75
+			if err := writeEvent(job, event, eventFilters); err != nil {
76 76
 				return job.Error(err)
77 77
 			}
78 78
 		case <-timeout.C:
... ...
@@ -97,7 +103,23 @@ func (e *Events) SubscribersCount(job *engine.Job) engine.Status {
97 97
 	return engine.StatusOK
98 98
 }
99 99
 
100
-func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
100
+func writeEvent(job *engine.Job, event *utils.JSONMessage, eventFilters filters.Args) error {
101
+	isFiltered := func(field string, filter []string) bool {
102
+		if len(filter) == 0 {
103
+			return false
104
+		}
105
+		for _, v := range filter {
106
+			if v == field {
107
+				return false
108
+			}
109
+		}
110
+		return true
111
+	}
112
+
113
+	if isFiltered(event.Status, eventFilters["event"]) || isFiltered(event.From, eventFilters["image"]) || isFiltered(event.ID, eventFilters["container"]) {
114
+		return nil
115
+	}
116
+
101 117
 	// When sending an event JSON serialization errors are ignored, but all
102 118
 	// other errors lead to the eviction of the listener.
103 119
 	if b, err := json.Marshal(event); err == nil {
... ...
@@ -108,11 +130,11 @@ func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
108 108
 	return nil
109 109
 }
110 110
 
111
-func (e *Events) writeCurrent(job *engine.Job, since, until int64) error {
111
+func (e *Events) writeCurrent(job *engine.Job, since, until int64, eventFilters filters.Args) error {
112 112
 	e.mu.RLock()
113 113
 	for _, event := range e.events {
114 114
 		if event.Time >= since && (event.Time <= until || until == 0) {
115
-			if err := writeEvent(job, event); err != nil {
115
+			if err := writeEvent(job, event, eventFilters); err != nil {
116 116
 				e.mu.RUnlock()
117 117
 				return err
118 118
 			}
... ...
@@ -61,7 +61,7 @@ func TestEventsPause(t *testing.T) {
61 61
 		t.Fatalf("event should be pause, not %#v", pauseEvent)
62 62
 	}
63 63
 	if unpauseEvent[len(unpauseEvent)-1] != "unpause" {
64
-		t.Fatalf("event should be pause, not %#v", unpauseEvent)
64
+		t.Fatalf("event should be unpause, not %#v", unpauseEvent)
65 65
 	}
66 66
 
67 67
 	waitCmd := exec.Command(dockerBinary, "wait", name)
... ...
@@ -138,13 +138,13 @@ func TestEventsContainerEvents(t *testing.T) {
138 138
 		t.Fatalf("event should be create, not %#v", createEvent)
139 139
 	}
140 140
 	if startEvent[len(startEvent)-1] != "start" {
141
-		t.Fatalf("event should be pause, not %#v", startEvent)
141
+		t.Fatalf("event should be start, not %#v", startEvent)
142 142
 	}
143 143
 	if dieEvent[len(dieEvent)-1] != "die" {
144
-		t.Fatalf("event should be pause, not %#v", dieEvent)
144
+		t.Fatalf("event should be die, not %#v", dieEvent)
145 145
 	}
146 146
 	if destroyEvent[len(destroyEvent)-1] != "destroy" {
147
-		t.Fatalf("event should be pause, not %#v", destroyEvent)
147
+		t.Fatalf("event should be destroy, not %#v", destroyEvent)
148 148
 	}
149 149
 
150 150
 	logDone("events - container create, start, die, destroy is logged")
... ...
@@ -283,3 +283,58 @@ func TestEventsImageImport(t *testing.T) {
283 283
 
284 284
 	logDone("events - image import is logged")
285 285
 }
286
+
287
+func TestEventsFilters(t *testing.T) {
288
+	now := time.Now().Unix()
289
+	cmd(t, "run", "--rm", "busybox", "true")
290
+	cmd(t, "run", "--rm", "busybox", "true")
291
+	eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", now), fmt.Sprintf("--until=%d", time.Now().Unix()), "--filter", "event=die")
292
+	out, exitCode, err := runCommandWithOutput(eventsCmd)
293
+	if exitCode != 0 || err != nil {
294
+		t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
295
+	}
296
+	events := strings.Split(out, "\n")
297
+	events = events[:len(events)-1]
298
+	if len(events) != 2 {
299
+		fmt.Printf("%v\n", events)
300
+		t.Fatalf("Unexpected event")
301
+	}
302
+	dieEvent := strings.Fields(events[len(events)-1])
303
+	if dieEvent[len(dieEvent)-1] != "die" {
304
+		t.Fatalf("event should be die, not %#v", dieEvent)
305
+	}
306
+
307
+	dieEvent = strings.Fields(events[len(events)-2])
308
+	if dieEvent[len(dieEvent)-1] != "die" {
309
+		t.Fatalf("event should be die, not %#v", dieEvent)
310
+	}
311
+
312
+	eventsCmd = exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", time.Now().Unix()), "--filter", "event=die", "--filter", "event=start")
313
+	out, exitCode, err = runCommandWithOutput(eventsCmd)
314
+	if exitCode != 0 || err != nil {
315
+		t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
316
+	}
317
+	events = strings.Split(out, "\n")
318
+	events = events[:len(events)-1]
319
+	if len(events) != 4 {
320
+		t.Fatalf("Unexpected event")
321
+	}
322
+	startEvent := strings.Fields(events[len(events)-4])
323
+	if startEvent[len(startEvent)-1] != "start" {
324
+		t.Fatalf("event should be start, not %#v", startEvent)
325
+	}
326
+	dieEvent = strings.Fields(events[len(events)-3])
327
+	if dieEvent[len(dieEvent)-1] != "die" {
328
+		t.Fatalf("event should be die, not %#v", dieEvent)
329
+	}
330
+	startEvent = strings.Fields(events[len(events)-2])
331
+	if startEvent[len(startEvent)-1] != "start" {
332
+		t.Fatalf("event should be start, not %#v", startEvent)
333
+	}
334
+	dieEvent = strings.Fields(events[len(events)-1])
335
+	if dieEvent[len(dieEvent)-1] != "die" {
336
+		t.Fatalf("event should be die, not %#v", dieEvent)
337
+	}
338
+
339
+	logDone("events - filters")
340
+}