Преглед изворни кода

Merge pull request #8893 from vieux/filter_events

Events filtering (daemon side)
Jessie Frazelle пре 10 година
родитељ
комит
9160e01cef

+ 24 - 2
api/client/commands.go

@@ -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
 	}

+ 1 - 0
api/server/server.go

@@ -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()
 }
 

+ 1 - 0
docs/sources/reference/api/docker_remote_api_v1.16.md

@@ -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:
 

+ 67 - 13
docs/sources/reference/commandline/cli.md

@@ -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:

+ 27 - 5
events/events.go

@@ -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
 			}

+ 58 - 4
integration-cli/docker_cli_events_test.go

@@ -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")
+}