Skip to content

Commit 22dd8d7

Browse files
committed
Merge pull request #480 from ggtools/events
Add missing options to the events command
2 parents 6a804ac + ea28ebb commit 22dd8d7

File tree

5 files changed

+115
-4
lines changed

5 files changed

+115
-4
lines changed

docker/client.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import shlex
1919
import struct
2020
import warnings
21+
from datetime import datetime
2122

2223
import requests
2324
import requests.exceptions
@@ -30,6 +31,7 @@
3031
from . import errors
3132
from .tls import TLSConfig
3233

34+
3335
if not six.PY3:
3436
import websocket
3537

@@ -290,7 +292,7 @@ def _get_raw_response_socket(self, response):
290292

291293
return sock
292294

293-
def _stream_helper(self, response):
295+
def _stream_helper(self, response, decode=False):
294296
"""Generator for data coming from a chunked-encoded HTTP response."""
295297
if response.raw._fp.chunked:
296298
reader = response.raw
@@ -301,6 +303,8 @@ def _stream_helper(self, response):
301303
break
302304
if reader._fp.chunk_left:
303305
data += reader.read(reader._fp.chunk_left)
306+
if decode:
307+
data = json.loads(data)
304308
yield data
305309
else:
306310
# Response isn't chunked, meaning we probably
@@ -565,8 +569,25 @@ def diff(self, container):
565569
return self._result(self._get(self._url("/containers/{0}/changes".
566570
format(container))), True)
567571

568-
def events(self):
569-
return self._stream_helper(self.get(self._url('/events'), stream=True))
572+
def events(self, since=None, until=None, filters=None, decode=None):
573+
if isinstance(since, datetime):
574+
since = utils.datetime_to_timestamp(since)
575+
576+
if isinstance(until, datetime):
577+
until = utils.datetime_to_timestamp(until)
578+
579+
if filters:
580+
filters = utils.convert_filters(filters)
581+
582+
params = {
583+
'since': since,
584+
'until': until,
585+
'filters': filters
586+
}
587+
588+
return self._stream_helper(self.get(self._url('/events'),
589+
params=params, stream=True),
590+
decode=decode)
570591

571592
def execute(self, container, cmd, detach=False, stdout=True, stderr=True,
572593
stream=False, tty=False):

docker/utils/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
import tempfile
2121
from distutils.version import StrictVersion
2222
from fnmatch import fnmatch
23+
from datetime import datetime
2324

2425
import requests
2526
import six
2627

2728
from .. import errors
2829
from .. import tls
2930

31+
3032
DEFAULT_HTTP_HOST = "127.0.0.1"
3133
DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock"
3234

@@ -296,6 +298,12 @@ def convert_filters(filters):
296298
return json.dumps(result)
297299

298300

301+
def datetime_to_timestamp(dt=datetime.now()):
302+
"""Convert a datetime in local timezone to a unix timestamp"""
303+
delta = dt - datetime.fromtimestamp(0)
304+
return delta.seconds + delta.days * 24 * 3600
305+
306+
299307
def create_host_config(
300308
binds=None, port_bindings=None, lxc_conf=None,
301309
publish_all_ports=False, links=None, privileged=False,

docs/api.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,28 @@ Inspect changes on a container's filesystem
229229

230230
**Returns** (str):
231231

232+
## events
233+
234+
Identical to the `docker events` command: get real time events from the server. The `events`
235+
function return a blocking generator you can iterate over to retrieve events as they happen.
236+
237+
**Params**:
238+
239+
* since (datetime or int): get events from this point
240+
241+
* until (datetime or int): get events until this point
242+
243+
* filters (dict): filter the events by event time, container or image
244+
245+
**Returns** (generator):
246+
247+
```python
248+
{u'status': u'start',
249+
u'from': u'image/with:tag',
250+
u'id': u'container-id',
251+
u'time': 1423339459}
252+
```
253+
232254
## execute
233255

234256
```python

tests/fake_api.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ def get_fake_diff():
221221
return status_code, response
222222

223223

224+
def get_fake_events():
225+
status_code = 200
226+
response = [{'status': 'stop', 'id': FAKE_CONTAINER_ID,
227+
'from': FAKE_IMAGE_ID, 'time': 1423247867}]
228+
return status_code, response
229+
230+
224231
def get_fake_export():
225232
status_code = 200
226233
response = 'Byte Stream....'
@@ -402,5 +409,7 @@ def post_fake_tag_image():
402409
'{1}/{0}/containers/create'.format(CURRENT_VERSION, prefix):
403410
post_fake_create_container,
404411
'{1}/{0}/build'.format(CURRENT_VERSION, prefix):
405-
post_fake_build_container
412+
post_fake_build_container,
413+
'{1}/{0}/events'.format(CURRENT_VERSION, prefix):
414+
get_fake_events
406415
}

tests/test.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,57 @@ def test_image_viz(self):
177177
except Exception:
178178
pass
179179

180+
def test_events(self):
181+
try:
182+
self.client.events()
183+
except Exception as e:
184+
self.fail('Command should not raise exception: {0}'.format(e))
185+
186+
fake_request.assert_called_with(
187+
url_prefix + 'events',
188+
params={'since': None, 'until': None, 'filters': None},
189+
stream=True
190+
)
191+
192+
def test_events_with_since_until(self):
193+
ts = 1356048000
194+
now = datetime.datetime.fromtimestamp(ts)
195+
since = now - datetime.timedelta(seconds=10)
196+
until = now + datetime.timedelta(seconds=10)
197+
try:
198+
self.client.events(since=since, until=until)
199+
except Exception as e:
200+
self.fail('Command should not raise exception: {0}'.format(e))
201+
202+
fake_request.assert_called_with(
203+
url_prefix + 'events',
204+
params={
205+
'since': ts - 10,
206+
'until': ts + 10,
207+
'filters': None
208+
},
209+
stream=True
210+
)
211+
212+
def test_events_with_filters(self):
213+
filters = {'event': ['die', 'stop'],
214+
'container': fake_api.FAKE_CONTAINER_ID}
215+
try:
216+
self.client.events(filters=filters)
217+
except Exception as e:
218+
self.fail('Command should not raise exception: {0}'.format(e))
219+
220+
expected_filters = docker.utils.convert_filters(filters)
221+
fake_request.assert_called_with(
222+
url_prefix + 'events',
223+
params={
224+
'since': None,
225+
'until': None,
226+
'filters': expected_filters
227+
},
228+
stream=True
229+
)
230+
180231
###################
181232
# LISTING TESTS #
182233
###################

0 commit comments

Comments
 (0)