From a07bd28077ce8cd13bfe66c2b74b3b0f76f14d8e Mon Sep 17 00:00:00 2001 From: Christophe Labouisse Date: Fri, 6 Feb 2015 20:00:04 +0100 Subject: [PATCH 1/5] Add missing options to the events command - Add since, until and filters parameters to `Client.events` - Add missing `events`command in the documentation Signed-off-by: Christophe Labouisse --- docker/client.py | 22 +++++++++++++++++-- docker/utils/utils.py | 6 ++++++ docs/api.md | 22 +++++++++++++++++++ tests/fake_api.py | 11 +++++++++- tests/test.py | 50 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/docker/client.py b/docker/client.py index 563fe00abf..80e53b6c73 100644 --- a/docker/client.py +++ b/docker/client.py @@ -23,6 +23,8 @@ import requests.exceptions import six +from datetime import datetime + from .auth import auth from .unixconn import unixconn from .ssladapter import ssladapter @@ -565,8 +567,24 @@ def diff(self, container): return self._result(self._get(self._url("/containers/{0}/changes". format(container))), True) - def events(self): - return self._stream_helper(self.get(self._url('/events'), stream=True)) + def events(self, since=None, until=None, filters=None): + if isinstance(since, datetime): + since = utils.datetime_to_timestamp(since) + + if isinstance(until, datetime): + until = utils.datetime_to_timestamp(until) + + if filters: + filters = utils.convert_filters(filters) + + params = { + 'since': since, + 'until': until, + 'filters': filters + } + + return self._stream_helper(self.get(self._url('/events'), + params=params, stream=True)) def execute(self, container, cmd, detach=False, stdout=True, stderr=True, stream=False, tty=False): diff --git a/docker/utils/utils.py b/docker/utils/utils.py index fdaf667934..ccb8320008 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -20,6 +20,7 @@ import tempfile from distutils.version import StrictVersion from fnmatch import fnmatch +from datetime import datetime import requests import six @@ -296,6 +297,11 @@ def convert_filters(filters): return json.dumps(result) +def datetime_to_timestamp(dt=datetime.now()): + """Convert a datetime in local timezone to a unix timestamp""" + return int((dt - datetime.fromtimestamp(0)).total_seconds()) + + def create_host_config( binds=None, port_bindings=None, lxc_conf=None, publish_all_ports=False, links=None, privileged=False, diff --git a/docs/api.md b/docs/api.md index 1d162e9f6a..421e5daf5d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -229,6 +229,28 @@ Inspect changes on a container's filesystem **Returns** (str): +## events + +Identical to the `docker events` command: get real time events from the server. The `events` +function return a blocking generator you can iterate over to retrieve events as they happen. + +**Params**: + +* since (datetime or int): get events from this point + +* until (datetime or int): get events until this point + +* filters (dict): filter the events by event time, container or image + +**Returns** (generator): + +```python +{"status":"die", +"id":"container-id", +"from":"image/with:tag", +"time":unix-timestamp} +``` + ## execute ```python diff --git a/tests/fake_api.py b/tests/fake_api.py index d311fc1104..ab2e4fd1aa 100644 --- a/tests/fake_api.py +++ b/tests/fake_api.py @@ -221,6 +221,13 @@ def get_fake_diff(): return status_code, response +def get_fake_events(): + status_code = 200 + response = [{'status': 'stop', 'id': FAKE_CONTAINER_ID, + 'from': FAKE_IMAGE_ID, 'time': 1423247867}] + return status_code, response + + def get_fake_export(): status_code = 200 response = 'Byte Stream....' @@ -402,5 +409,7 @@ def post_fake_tag_image(): '{1}/{0}/containers/create'.format(CURRENT_VERSION, prefix): post_fake_create_container, '{1}/{0}/build'.format(CURRENT_VERSION, prefix): - post_fake_build_container + post_fake_build_container, + '{1}/{0}/events'.format(CURRENT_VERSION, prefix): + get_fake_events } diff --git a/tests/test.py b/tests/test.py index 5ad3ba9362..bd219a8d64 100644 --- a/tests/test.py +++ b/tests/test.py @@ -177,6 +177,56 @@ def test_image_viz(self): except Exception: pass + def test_events(self): + try: + self.client.events() + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + url_prefix + 'events', + params={'since': None, 'until': None, 'filters': None}, + stream=True + ) + + def test_events_with_since_until(self): + now = datetime.datetime.now() + since = now - datetime.timedelta(seconds=10) + until = now + datetime.timedelta(seconds=10) + ts = int((now - datetime.datetime.fromtimestamp(0)).total_seconds()) + try: + self.client.events(since=since, until=until) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + url_prefix + 'events', + params={ + 'since': ts - 10, + 'until': ts + 10, + 'filters': None + }, + stream=True + ) + + def test_events_with_filters(self): + filters = {'event': ['die', 'stop'], 'container': fake_api.FAKE_CONTAINER_ID} + try: + self.client.events(filters=filters) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + expected_filters = docker.utils.convert_filters(filters) + fake_request.assert_called_with( + url_prefix + 'events', + params={ + 'since': None, + 'until': None, + 'filters': expected_filters + }, + stream=True + ) + ################### # LISTING TESTS # ################### From 53b1bb41acff5207d6a40f57e0b0bb425f88e23f Mon Sep 17 00:00:00 2001 From: Christophe Labouisse Date: Sat, 7 Feb 2015 19:36:45 +0100 Subject: [PATCH 2/5] Fix datetime issue with Python 2.6 Signed-off-by: Christophe Labouisse --- docker/utils/utils.py | 4 +++- tests/test.py | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docker/utils/utils.py b/docker/utils/utils.py index ccb8320008..dee98ea740 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -28,6 +28,7 @@ from .. import errors from .. import tls + DEFAULT_HTTP_HOST = "127.0.0.1" DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock" @@ -299,7 +300,8 @@ def convert_filters(filters): def datetime_to_timestamp(dt=datetime.now()): """Convert a datetime in local timezone to a unix timestamp""" - return int((dt - datetime.fromtimestamp(0)).total_seconds()) + delta = dt - datetime.fromtimestamp(0) + return delta.seconds + delta.days * 24 * 3600 def create_host_config( diff --git a/tests/test.py b/tests/test.py index bd219a8d64..bf7b3fbfef 100644 --- a/tests/test.py +++ b/tests/test.py @@ -190,10 +190,10 @@ def test_events(self): ) def test_events_with_since_until(self): - now = datetime.datetime.now() + ts = 1356048000 + now = datetime.datetime.fromtimestamp(ts) since = now - datetime.timedelta(seconds=10) until = now + datetime.timedelta(seconds=10) - ts = int((now - datetime.datetime.fromtimestamp(0)).total_seconds()) try: self.client.events(since=since, until=until) except Exception as e: @@ -210,7 +210,8 @@ def test_events_with_since_until(self): ) def test_events_with_filters(self): - filters = {'event': ['die', 'stop'], 'container': fake_api.FAKE_CONTAINER_ID} + filters = {'event': ['die', 'stop'], + 'container': fake_api.FAKE_CONTAINER_ID} try: self.client.events(filters=filters) except Exception as e: From 2bd21ad066606f7d1109f72fc4c5547e8dda5a2d Mon Sep 17 00:00:00 2001 From: Christophe Labouisse Date: Sat, 7 Feb 2015 20:07:26 +0100 Subject: [PATCH 3/5] Fix flake8 issues Signed-off-by: Christophe Labouisse --- docker/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/client.py b/docker/client.py index 80e53b6c73..9ac43af4cc 100644 --- a/docker/client.py +++ b/docker/client.py @@ -18,13 +18,12 @@ import shlex import struct import warnings +from datetime import datetime import requests import requests.exceptions import six -from datetime import datetime - from .auth import auth from .unixconn import unixconn from .ssladapter import ssladapter @@ -32,6 +31,7 @@ from . import errors from .tls import TLSConfig + if not six.PY3: import websocket From 9bd71b2d4573a4431aa13ed1d764022263d2639c Mon Sep 17 00:00:00 2001 From: Christophe Labouisse Date: Sat, 7 Feb 2015 22:30:30 +0100 Subject: [PATCH 4/5] Add decoding to the events Signed-off-by: Christophe Labouisse --- docker/client.py | 7 +++++-- docs/api.md | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docker/client.py b/docker/client.py index 9ac43af4cc..5ce83babf7 100644 --- a/docker/client.py +++ b/docker/client.py @@ -292,7 +292,7 @@ def _get_raw_response_socket(self, response): return sock - def _stream_helper(self, response): + def _stream_helper(self, response, decode=False): """Generator for data coming from a chunked-encoded HTTP response.""" if response.raw._fp.chunked: reader = response.raw @@ -303,6 +303,8 @@ def _stream_helper(self, response): break if reader._fp.chunk_left: data += reader.read(reader._fp.chunk_left) + if decode: + data = json.loads(data) yield data else: # Response isn't chunked, meaning we probably @@ -584,7 +586,8 @@ def events(self, since=None, until=None, filters=None): } return self._stream_helper(self.get(self._url('/events'), - params=params, stream=True)) + params=params, stream=True), + decode=True) def execute(self, container, cmd, detach=False, stdout=True, stderr=True, stream=False, tty=False): diff --git a/docs/api.md b/docs/api.md index 421e5daf5d..d9eebc0c0a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -245,10 +245,10 @@ function return a blocking generator you can iterate over to retrieve events as **Returns** (generator): ```python -{"status":"die", -"id":"container-id", -"from":"image/with:tag", -"time":unix-timestamp} +{u'status': u'start', + u'from': u'image/with:tag', + u'id': u'container-id', + u'time': 1423339459} ``` ## execute From ea28ebb5f14b694b913557abe474895562353e2f Mon Sep 17 00:00:00 2001 From: Christophe Labouisse Date: Wed, 11 Feb 2015 07:19:27 +0100 Subject: [PATCH 5/5] Add a `decode` parameter to the events command This will ensure compatibility with the previous versions of docker-py. Signed-off-by: Christophe Labouisse --- docker/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/client.py b/docker/client.py index 5ce83babf7..cba8d39a8d 100644 --- a/docker/client.py +++ b/docker/client.py @@ -569,7 +569,7 @@ def diff(self, container): return self._result(self._get(self._url("/containers/{0}/changes". format(container))), True) - def events(self, since=None, until=None, filters=None): + def events(self, since=None, until=None, filters=None, decode=None): if isinstance(since, datetime): since = utils.datetime_to_timestamp(since) @@ -587,7 +587,7 @@ def events(self, since=None, until=None, filters=None): return self._stream_helper(self.get(self._url('/events'), params=params, stream=True), - decode=True) + decode=decode) def execute(self, container, cmd, detach=False, stdout=True, stderr=True, stream=False, tty=False):