From 21d80b16ddb67b1d95b62b56dad1b91a7986333f Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Fri, 24 Apr 2015 14:56:06 -0700 Subject: [PATCH 1/2] Added log_config support in host config --- docker/utils/__init__.py | 2 +- docker/utils/types.py | 51 ++++++++++++++++++++++++++++++++++++++++ docker/utils/utils.py | 17 +++++++++++--- docs/hostconfig.md | 7 ++++-- 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py index 411d278c..3594b9cd 100644 --- a/docker/utils/__init__.py +++ b/docker/utils/__init__.py @@ -5,4 +5,4 @@ from .utils import ( 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 index f960afb0..d742fd0a 100644 --- 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 index fb32904b..975bb4d8 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -28,7 +28,7 @@ import six 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 diff --git a/docs/hostconfig.md b/docs/hostconfig.md index 1d82c830..99ffe1c6 100644 --- a/docs/hostconfig.md +++ b/docs/hostconfig.md @@ -85,8 +85,11 @@ for example: * read_only (bool): mount the container's root filesystem as read only * pid_mode (str): if set to "host", use the host PID namespace inside the container -* security_opt (list): A list of string values to customize labels for MLS systems, such as SELinux. -* ulimits (list): A list of dicts or `docker.utils.Ulimit` objects. +* security_opt (list): A list of string values to customize labels for MLS + systems, such as SELinux. +* ulimits (list): A list of dicts or `docker.utils.Ulimit` objects. A list + of ulimits to be set in the container. +* log_config (`docker.utils.LogConfig` or dict): Logging configuration to container **Returns** (dict) HostConfig dictionary From c7948436e54dd7e12cc062aebcfd46328427715f Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Fri, 24 Apr 2015 15:10:51 -0700 Subject: [PATCH 2/2] Added tests for log_config param --- tests/integration_test.py | 27 ++++++++++++++++++++++++++- tests/utils_test.py | 22 +++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/tests/integration_test.py b/tests/integration_test.py index eb7a0eb6..21679d7e 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -348,6 +348,31 @@ class TestStartContainerWithFileBind(BaseTestCase): 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 @@ class TestStartContainerWithVolumesFrom(BaseTestCase): 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 index 1f18512b..454a14e8 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -6,7 +6,7 @@ from docker.client import Client 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 @@ class UtilsTest(base.BaseTestCase): 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'},