diff --git a/docker/client.py b/docker/client.py index 563fe00a..80e53b6c 100644 --- a/docker/client.py +++ b/docker/client.py @@ -23,6 +23,8 @@ import requests 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 @@ class Client(requests.Session): 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 fdaf6679..ccb83200 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -20,6 +20,7 @@ import tarfile 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 1d162e9f..421e5daf 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 d311fc11..ab2e4fd1 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 @@ fake_responses = { '{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 5ad3ba93..bd219a8d 100644 --- a/tests/test.py +++ b/tests/test.py @@ -177,6 +177,56 @@ class DockerClientTest(Cleanup, unittest.TestCase): 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 # ###################