Repository: docker/docker-py

Issue:
Add support for logging drivers.
Docker 1.6 has added a log driver option to the host config.

> Logging Drivers
> There have been a growing number of proposals for a logging driver API that would allow you to send container logs to other systems such as Syslog or a third-party. This new logging driver follows the exec driver and storage driver concepts already available in Engine today.
> There is a new option to docker run,  --log-driver, which has three options:json-file (the default, and same as the old functionality), syslog, and none.

https://blog.docker.com/2015/04/docker-release-1-6/
https://docs.docker.com/reference/api/docker_remote_api_v1.18/#create-a-container

This should be exposed in client.create_container



Discussion:

Resolving patch:
diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py
--- a/docker/utils/__init__.py
+++ b/docker/utils/__init__.py
@@ -5,4 +5,4 @@
     create_container_config, parse_bytes, ping_registry
 ) # flake8: noqa
 
-from .types import Ulimit # flake8: noqa
\ No newline at end of file
+from .types import Ulimit, LogConfig # flake8: noqa
\ No newline at end of file
diff --git a/docker/utils/types.py b/docker/utils/types.py
--- a/docker/utils/types.py
+++ b/docker/utils/types.py
@@ -1,12 +1,63 @@
 import six
 
 
+class LogConfigTypesEnum(object):
+    _values = (
+        'json-file',
+        'syslog',
+        'none'
+    )
+    JSON, SYSLOG, NONE = _values
+
+
 class DictType(dict):
     def __init__(self, init):
         for k, v in six.iteritems(init):
             self[k] = v
 
 
+class LogConfig(DictType):
+    types = LogConfigTypesEnum
+
+    def __init__(self, **kwargs):
+        type_ = kwargs.get('type', kwargs.get('Type'))
+        config = kwargs.get('config', kwargs.get('Config'))
+        if type_ not in self.types._values:
+            raise ValueError("LogConfig.type must be one of ({0})".format(
+                ', '.join(self.types._values)
+            ))
+        if config and not isinstance(config, dict):
+            raise ValueError("LogConfig.config must be a dictionary")
+
+        super(LogConfig, self).__init__({
+            'Type': type_,
+            'Config': config or {}
+        })
+
+    @property
+    def type(self):
+        return self['Type']
+
+    @type.setter
+    def type(self, value):
+        if value not in self.types._values:
+            raise ValueError("LogConfig.type must be one of {0}".format(
+                ', '.join(self.types._values)
+            ))
+        self['Type'] = value
+
+    @property
+    def config(self):
+        return self['Config']
+
+    def set_config_value(self, key, value):
+        self.config[key] = value
+
+    def unset_config(self, key):
+        if key in self.config:
+            del self.config[key]
+
+
 class Ulimit(DictType):
     def __init__(self, **kwargs):
         name = kwargs.get('name', kwargs.get('Name'))
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -28,7 +28,7 @@
 
 from .. import errors
 from .. import tls
-from .types import Ulimit
+from .types import Ulimit, LogConfig
 
 
 DEFAULT_HTTP_HOST = "127.0.0.1"
@@ -359,7 +359,7 @@ def create_host_config(
     dns=None, dns_search=None, volumes_from=None, network_mode=None,
     restart_policy=None, cap_add=None, cap_drop=None, devices=None,
     extra_hosts=None, read_only=None, pid_mode=None, ipc_mode=None,
-    security_opt=None, ulimits=None
+    security_opt=None, ulimits=None, log_config=None
 ):
     host_config = {}
 
@@ -456,13 +456,24 @@ def create_host_config(
         if not isinstance(ulimits, list):
             raise errors.DockerException(
                 'Invalid type for ulimits param: expected list but found'
-                ' {0}'.format(type(ulimits)))
+                ' {0}'.format(type(ulimits))
+            )
         host_config['Ulimits'] = []
         for l in ulimits:
             if not isinstance(l, Ulimit):
                 l = Ulimit(**l)
             host_config['Ulimits'].append(l)
 
+    if log_config is not None:
+        if not isinstance(log_config, LogConfig):
+            if not isinstance(log_config, dict):
+                raise errors.DockerException(
+                    'Invalid type for log_config param: expected LogConfig but'
+                    ' found {0}'.format(type(log_config))
+                )
+            log_config = LogConfig(**log_config)
+        host_config['LogConfig'] = log_config
+
     return host_config
 
 


Test patch:
diff --git a/tests/integration_test.py b/tests/integration_test.py
--- a/tests/integration_test.py
+++ b/tests/integration_test.py
@@ -348,6 +348,31 @@ def runTest(self):
             os.unlink(mount_origin)
 
 
+class TestCreateContainerWithLogConfig(BaseTestCase):
+    def runTest(self):
+        config = docker.utils.LogConfig(
+            type=docker.utils.LogConfig.types.SYSLOG,
+            config={'key1': 'val1'}
+        )
+        ctnr = self.client.create_container(
+            'busybox', ['true'],
+            host_config=create_host_config(log_config=config)
+        )
+        self.assertIn('Id', ctnr)
+        self.tmp_containers.append(ctnr['Id'])
+        self.client.start(ctnr)
+        info = self.client.inspect_container(ctnr)
+        self.assertIn('HostConfig', info)
+        host_config = info['HostConfig']
+        self.assertIn('LogConfig', host_config)
+        log_config = host_config['LogConfig']
+        self.assertIn('Type', log_config)
+        self.assertEqual(log_config['Type'], config.type)
+        self.assertIn('Config', log_config)
+        self.assertEqual(type(log_config['Config']), dict)
+        self.assertEqual(log_config['Config'], config.config)
+
+
 @unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native')
 class TestCreateContainerReadOnlyFs(BaseTestCase):
     def runTest(self):
@@ -958,7 +983,7 @@ def runTest(self):
 
 class TestStartContainerWithUlimits(BaseTestCase):
     def runTest(self):
-        ulimit = docker.utils.Ulimit('nofile', 4096, 4096)
+        ulimit = docker.utils.Ulimit(name='nofile', soft=4096, hard=4096)
 
         res0 = self.client.create_container('busybox', 'true')
         container1_id = res0['Id']
diff --git a/tests/utils_test.py b/tests/utils_test.py
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -6,7 +6,7 @@
 from docker.errors import DockerException
 from docker.utils import (
     parse_repository_tag, parse_host, convert_filters, kwargs_from_env,
-    create_host_config, Ulimit
+    create_host_config, Ulimit, LogConfig
 )
 from docker.utils.ports import build_port_bindings, split_port
 from docker.auth import resolve_authconfig
@@ -141,6 +141,26 @@ def test_ulimit_invalid_type(self):
         self.assertRaises(ValueError, lambda: Ulimit(name='hello', soft='123'))
         self.assertRaises(ValueError, lambda: Ulimit(name='hello', hard='456'))
 
+    def test_create_host_config_dict_logconfig(self):
+        dct = {'type': LogConfig.types.SYSLOG, 'config': {'key1': 'val1'}}
+        config = create_host_config(log_config=dct)
+        self.assertIn('LogConfig', config)
+        self.assertTrue(isinstance(config['LogConfig'], LogConfig))
+        self.assertEqual(dct['type'], config['LogConfig'].type)
+
+    def test_create_host_config_obj_logconfig(self):
+        obj = LogConfig(type=LogConfig.types.SYSLOG, config={'key1': 'val1'})
+        config = create_host_config(log_config=obj)
+        self.assertIn('LogConfig', config)
+        self.assertTrue(isinstance(config['LogConfig'], LogConfig))
+        self.assertEqual(obj, config['LogConfig'])
+
+    def test_logconfig_invalid_type(self):
+        self.assertRaises(ValueError, lambda: LogConfig(type='xxx', config={}))
+        self.assertRaises(ValueError, lambda: LogConfig(
+            type=LogConfig.types.JSON, config='helloworld'
+        ))
+
     def test_resolve_authconfig(self):
         auth_config = {
             'https://index.docker.io/v1/': {'auth': 'indexuser'},