Skip to content

Commit ac3178d

Browse files
authored
Merge pull request #856 from stokito/events_filters
Events filters
2 parents f9ebdbc + d87c203 commit ac3178d

File tree

2 files changed

+59
-15
lines changed

2 files changed

+59
-15
lines changed

event.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,48 @@ package docker
77
import (
88
"encoding/json"
99
"errors"
10-
"fmt"
1110
"io"
1211
"math"
1312
"net"
1413
"net/http"
1514
"net/http/httputil"
15+
"strconv"
1616
"sync"
1717
"sync/atomic"
1818
"time"
1919
)
2020

21+
// EventsOptions to filter events
22+
// See https://docs.docker.com/engine/api/v1.41/#operation/SystemEvents for more details.
23+
type EventsOptions struct {
24+
// Show events created since this timestamp then stream new events.
25+
Since string
26+
27+
// Show events created until this timestamp then stop streaming.
28+
Until string
29+
30+
// Filter for events. For example:
31+
// map[string][]string{"type": {"container"}, "event": {"start", "die"}}
32+
// will return events when container was started and stopped or killed
33+
//
34+
// Available filters:
35+
// config=<string> config name or ID
36+
// container=<string> container name or ID
37+
// daemon=<string> daemon name or ID
38+
// event=<string> event type
39+
// image=<string> image name or ID
40+
// label=<string> image or container label
41+
// network=<string> network name or ID
42+
// node=<string> node ID
43+
// plugin= plugin name or ID
44+
// scope= local or swarm
45+
// secret=<string> secret name or ID
46+
// service=<string> service name or ID
47+
// type=<string> container, image, volume, network, daemon, plugin, node, service, secret or config
48+
// volume=<string> volume name
49+
Filters map[string][]string
50+
}
51+
2152
// APIEvents represents events coming from the Docker API
2253
// The fields in the Docker API changed in API version 1.22, and
2354
// events for more than images and containers are now fired off.
@@ -93,9 +124,17 @@ var (
93124
//
94125
// The parameter is a channel through which events will be sent.
95126
func (c *Client) AddEventListener(listener chan<- *APIEvents) error {
127+
return c.AddEventListenerWithOptions(EventsOptions{}, listener)
128+
}
129+
130+
// AddEventListener adds a new listener to container events in the Docker API.
131+
// See https://docs.docker.com/engine/api/v1.41/#operation/SystemEvents for more details.
132+
//
133+
// The listener parameter is a channel through which events will be sent.
134+
func (c *Client) AddEventListenerWithOptions(options EventsOptions, listener chan<- *APIEvents) error {
96135
var err error
97136
if !c.eventMonitor.isEnabled() {
98-
err = c.eventMonitor.enableEventMonitoring(c)
137+
err = c.eventMonitor.enableEventMonitoring(c, options)
99138
if err != nil {
100139
return err
101140
}
@@ -165,15 +204,15 @@ func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
165204
return false
166205
}
167206

168-
func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error {
207+
func (eventState *eventMonitoringState) enableEventMonitoring(c *Client, opts EventsOptions) error {
169208
eventState.Lock()
170209
defer eventState.Unlock()
171210
if !eventState.enabled {
172211
eventState.enabled = true
173212
atomic.StoreInt64(&eventState.lastSeen, 0)
174213
eventState.C = make(chan *APIEvents, 100)
175214
eventState.errC = make(chan error, 1)
176-
go eventState.monitorEvents(c)
215+
go eventState.monitorEvents(c, opts)
177216
}
178217
return nil
179218
}
@@ -193,7 +232,7 @@ func (eventState *eventMonitoringState) disableEventMonitoring() {
193232
}
194233
}
195234

196-
func (eventState *eventMonitoringState) monitorEvents(c *Client) {
235+
func (eventState *eventMonitoringState) monitorEvents(c *Client, opts EventsOptions) {
197236
const (
198237
noListenersTimeout = 5 * time.Second
199238
noListenersInterval = 10 * time.Millisecond
@@ -213,7 +252,7 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
213252
return
214253
}
215254

216-
if err = eventState.connectWithRetry(c); err != nil {
255+
if err = eventState.connectWithRetry(c, opts); err != nil {
217256
// terminate if connect failed
218257
eventState.disableEventMonitoring()
219258
return
@@ -236,7 +275,7 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
236275
eventState.disableEventMonitoring()
237276
return
238277
} else if err != nil {
239-
defer func() { go eventState.monitorEvents(c) }()
278+
defer func() { go eventState.monitorEvents(c, opts) }()
240279
return
241280
}
242281
case <-timeout:
@@ -245,21 +284,21 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
245284
}
246285
}
247286

248-
func (eventState *eventMonitoringState) connectWithRetry(c *Client) error {
287+
func (eventState *eventMonitoringState) connectWithRetry(c *Client, opts EventsOptions) error {
249288
var retries int
250289
eventState.RLock()
251290
eventChan := eventState.C
252291
errChan := eventState.errC
253292
eventState.RUnlock()
254-
err := c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
293+
err := c.eventHijack(opts, atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
255294
for ; err != nil && retries < maxMonitorConnRetries; retries++ {
256295
waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries)))
257296
time.Sleep(time.Duration(waitTime) * time.Millisecond)
258297
eventState.RLock()
259298
eventChan = eventState.C
260299
errChan = eventState.errC
261300
eventState.RUnlock()
262-
err = c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
301+
err = c.eventHijack(opts, atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
263302
}
264303
return err
265304
}
@@ -304,11 +343,12 @@ func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
304343
}
305344
}
306345

307-
func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error {
308-
uri := "/events"
346+
func (c *Client) eventHijack(opts EventsOptions, startTime int64, eventChan chan *APIEvents, errChan chan error) error {
347+
// on reconnect override initial Since with last event seen time
309348
if startTime != 0 {
310-
uri += fmt.Sprintf("?since=%d", startTime)
349+
opts.Since = strconv.FormatInt(startTime, 10)
311350
}
351+
uri := "/events?" + queryString(opts)
312352
protocol := c.endpointURL.Scheme
313353
address := c.endpointURL.Path
314354
if protocol != "unix" && protocol != "npipe" {

event_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,12 @@ func testEventListeners(testName string, t *testing.T, buildServer func(http.Han
239239
t.Error(err)
240240
}
241241
}()
242-
243-
err = client.AddEventListener(listener)
242+
filters := map[string][]string{
243+
"type": {"container"},
244+
"event": {"create", "destroy", "start", "stop", "pull", "attach"},
245+
}
246+
opts := EventsOptions{Since: "1374067970", Until: "1442421700", Filters: filters}
247+
err = client.AddEventListenerWithOptions(opts, listener)
244248
if err != nil {
245249
t.Errorf("Failed to add event listener: %s", err)
246250
}

0 commit comments

Comments
 (0)