Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions docker/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import shlex
import struct
import warnings
from datetime import datetime

import requests
import requests.exceptions
Expand All @@ -30,6 +31,7 @@
from . import errors
from .tls import TLSConfig


if not six.PY3:
import websocket

Expand Down Expand Up @@ -290,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
Expand All @@ -301,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
Expand Down Expand Up @@ -565,8 +569,25 @@ 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, decode=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),
decode=decode)

def execute(self, container, cmd, detach=False, stdout=True, stderr=True,
stream=False, tty=False):
Expand Down
8 changes: 8 additions & 0 deletions docker/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
import tempfile
from distutils.version import StrictVersion
from fnmatch import fnmatch
from datetime import datetime

import requests
import six

from .. import errors
from .. import tls


DEFAULT_HTTP_HOST = "127.0.0.1"
DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock"

Expand Down Expand Up @@ -296,6 +298,12 @@ 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"""
delta = dt - datetime.fromtimestamp(0)
return delta.seconds + delta.days * 24 * 3600


def create_host_config(
binds=None, port_bindings=None, lxc_conf=None,
publish_all_ports=False, links=None, privileged=False,
Expand Down
22 changes: 22 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{u'status': u'start',
u'from': u'image/with:tag',
u'id': u'container-id',
u'time': 1423339459}
```

## execute

```python
Expand Down
11 changes: 10 additions & 1 deletion tests/fake_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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....'
Expand Down Expand Up @@ -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
}
51 changes: 51 additions & 0 deletions tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,57 @@ 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):
ts = 1356048000
now = datetime.datetime.fromtimestamp(ts)
since = now - datetime.timedelta(seconds=10)
until = now + datetime.timedelta(seconds=10)
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 #
###################
Expand Down