From a1ac289943639754106d5b6bdb1efbc5e503f7f3 Mon Sep 17 00:00:00 2001 From: Drew Romanyk Date: Thu, 30 Nov 2017 11:57:42 -0600 Subject: [PATCH 1/2] Add label config validation, fixes #4904 Signed-off-by: Drew Romanyk --- compose/config/config_schema_v1.json | 17 ++++++++++++- compose/config/config_schema_v2.0.json | 17 ++++++++++++- compose/config/config_schema_v2.1.json | 23 ++++++++++++++--- compose/config/config_schema_v2.2.json | 23 ++++++++++++++--- compose/config/config_schema_v2.3.json | 23 ++++++++++++++--- compose/config/config_schema_v3.0.json | 23 ++++++++++++++--- compose/config/config_schema_v3.1.json | 25 +++++++++++++++---- compose/config/config_schema_v3.2.json | 25 +++++++++++++++---- compose/config/config_schema_v3.3.json | 29 ++++++++++++++++------ compose/config/config_schema_v3.4.json | 29 ++++++++++++++++------ compose/config/config_schema_v3.5.json | 29 ++++++++++++++++------ tests/unit/config/config_test.py | 34 ++++++++++++++++++++++++++ 12 files changed, 248 insertions(+), 49 deletions(-) diff --git a/compose/config/config_schema_v1.json b/compose/config/config_schema_v1.json index 94354cda7..2771f9958 100644 --- a/compose/config/config_schema_v1.json +++ b/compose/config/config_schema_v1.json @@ -78,7 +78,7 @@ "hostname": {"type": "string"}, "image": {"type": "string"}, "ipc": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "log_driver": {"type": "string"}, "log_opt": {"type": "object"}, @@ -166,6 +166,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "constraints": { "service": { "id": "#/definitions/constraints/service", diff --git a/compose/config/config_schema_v2.0.json b/compose/config/config_schema_v2.0.json index 2ad62ac52..5a659eaff 100644 --- a/compose/config/config_schema_v2.0.json +++ b/compose/config/config_schema_v2.0.json @@ -158,7 +158,7 @@ "hostname": {"type": "string"}, "image": {"type": "string"}, "ipc": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -354,6 +354,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "blkio_limit": { "type": "object", "properties": { diff --git a/compose/config/config_schema_v2.1.json b/compose/config/config_schema_v2.1.json index 6b74f0ed6..3f3d87654 100644 --- a/compose/config/config_schema_v2.1.json +++ b/compose/config/config_schema_v2.1.json @@ -88,7 +88,7 @@ "context": {"type": "string"}, "dockerfile": {"type": "string"}, "args": {"$ref": "#/definitions/list_or_dict"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false } @@ -183,7 +183,7 @@ "image": {"type": "string"}, "ipc": {"type": "string"}, "isolation": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -350,7 +350,7 @@ }, "internal": {"type": "boolean"}, "enable_ipv6": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -373,7 +373,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "name": {"type": "string"} }, "additionalProperties": false @@ -407,6 +407,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "blkio_limit": { "type": "object", "properties": { diff --git a/compose/config/config_schema_v2.2.json b/compose/config/config_schema_v2.2.json index 21343b893..294820c74 100644 --- a/compose/config/config_schema_v2.2.json +++ b/compose/config/config_schema_v2.2.json @@ -88,7 +88,7 @@ "context": {"type": "string"}, "dockerfile": {"type": "string"}, "args": {"$ref": "#/definitions/list_or_dict"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "cache_from": {"$ref": "#/definitions/list_of_strings"}, "network": {"type": "string"} }, @@ -189,7 +189,7 @@ "init": {"type": ["boolean", "string"]}, "ipc": {"type": "string"}, "isolation": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -357,7 +357,7 @@ }, "internal": {"type": "boolean"}, "enable_ipv6": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -380,7 +380,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "name": {"type": "string"} }, "additionalProperties": false @@ -414,6 +414,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "blkio_limit": { "type": "object", "properties": { diff --git a/compose/config/config_schema_v2.3.json b/compose/config/config_schema_v2.3.json index 6f923871b..5c580afbf 100644 --- a/compose/config/config_schema_v2.3.json +++ b/compose/config/config_schema_v2.3.json @@ -88,7 +88,7 @@ "context": {"type": "string"}, "dockerfile": {"type": "string"}, "args": {"$ref": "#/definitions/list_or_dict"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "cache_from": {"$ref": "#/definitions/list_of_strings"}, "network": {"type": "string"}, "target": {"type": "string"}, @@ -192,7 +192,7 @@ "init": {"type": ["boolean", "string"]}, "ipc": {"type": "string"}, "isolation": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -361,7 +361,7 @@ }, "internal": {"type": "boolean"}, "enable_ipv6": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -384,7 +384,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "name": {"type": "string"} }, "additionalProperties": false @@ -418,6 +418,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "blkio_limit": { "type": "object", "properties": { diff --git a/compose/config/config_schema_v3.0.json b/compose/config/config_schema_v3.0.json index f39344cfb..afdb23ae2 100644 --- a/compose/config/config_schema_v3.0.json +++ b/compose/config/config_schema_v3.0.json @@ -105,7 +105,7 @@ "hostname": {"type": "string"}, "image": {"type": "string"}, "ipc": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -223,7 +223,7 @@ "properties": { "mode": {"type": "string"}, "replicas": {"type": "integer"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "update_config": { "type": "object", "properties": { @@ -310,7 +310,7 @@ "additionalProperties": false }, "internal": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -333,7 +333,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -366,6 +366,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "constraints": { "service": { "id": "#/definitions/constraints/service", diff --git a/compose/config/config_schema_v3.1.json b/compose/config/config_schema_v3.1.json index 719c0fa7a..4689bed71 100644 --- a/compose/config/config_schema_v3.1.json +++ b/compose/config/config_schema_v3.1.json @@ -116,7 +116,7 @@ "hostname": {"type": "string"}, "image": {"type": "string"}, "ipc": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -252,7 +252,7 @@ "properties": { "mode": {"type": "string"}, "replicas": {"type": "integer"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "update_config": { "type": "object", "properties": { @@ -339,7 +339,7 @@ "additionalProperties": false }, "internal": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -362,7 +362,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -378,7 +378,7 @@ "name": {"type": "string"} } }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -411,6 +411,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "constraints": { "service": { "id": "#/definitions/constraints/service", diff --git a/compose/config/config_schema_v3.2.json b/compose/config/config_schema_v3.2.json index 8d850d5d2..784a0233e 100644 --- a/compose/config/config_schema_v3.2.json +++ b/compose/config/config_schema_v3.2.json @@ -117,7 +117,7 @@ "hostname": {"type": "string"}, "image": {"type": "string"}, "ipc": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -297,7 +297,7 @@ "mode": {"type": "string"}, "endpoint_mode": {"type": "string"}, "replicas": {"type": "integer"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "update_config": { "type": "object", "properties": { @@ -385,7 +385,7 @@ }, "internal": {"type": "boolean"}, "attachable": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -408,7 +408,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -424,7 +424,7 @@ "name": {"type": "string"} } }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -457,6 +457,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "constraints": { "service": { "id": "#/definitions/constraints/service", diff --git a/compose/config/config_schema_v3.3.json b/compose/config/config_schema_v3.3.json index f1eb9a661..d8b509e89 100644 --- a/compose/config/config_schema_v3.3.json +++ b/compose/config/config_schema_v3.3.json @@ -83,7 +83,7 @@ "context": {"type": "string"}, "dockerfile": {"type": "string"}, "args": {"$ref": "#/definitions/list_or_dict"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "cache_from": {"$ref": "#/definitions/list_of_strings"} }, "additionalProperties": false @@ -151,7 +151,7 @@ "hostname": {"type": "string"}, "image": {"type": "string"}, "ipc": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -331,7 +331,7 @@ "mode": {"type": "string"}, "endpoint_mode": {"type": "string"}, "replicas": {"type": "integer"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "update_config": { "type": "object", "properties": { @@ -429,7 +429,7 @@ }, "internal": {"type": "boolean"}, "attachable": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -452,7 +452,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -468,7 +468,7 @@ "name": {"type": "string"} } }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -484,7 +484,7 @@ "name": {"type": "string"} } }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -517,6 +517,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "constraints": { "service": { "id": "#/definitions/constraints/service", diff --git a/compose/config/config_schema_v3.4.json b/compose/config/config_schema_v3.4.json index dae7d7d23..bf1f413d9 100644 --- a/compose/config/config_schema_v3.4.json +++ b/compose/config/config_schema_v3.4.json @@ -85,7 +85,7 @@ "context": {"type": "string"}, "dockerfile": {"type": "string"}, "args": {"$ref": "#/definitions/list_or_dict"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "cache_from": {"$ref": "#/definitions/list_of_strings"}, "network": {"type": "string"}, "target": {"type": "string"} @@ -155,7 +155,7 @@ "hostname": {"type": "string"}, "image": {"type": "string"}, "ipc": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -336,7 +336,7 @@ "mode": {"type": "string"}, "endpoint_mode": {"type": "string"}, "replicas": {"type": "integer"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "update_config": { "type": "object", "properties": { @@ -437,7 +437,7 @@ }, "internal": {"type": "boolean"}, "attachable": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -461,7 +461,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -477,7 +477,7 @@ "name": {"type": "string"} } }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -493,7 +493,7 @@ "name": {"type": "string"} } }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -526,6 +526,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "constraints": { "service": { "id": "#/definitions/constraints/service", diff --git a/compose/config/config_schema_v3.5.json b/compose/config/config_schema_v3.5.json index fa95d6a24..75af7724e 100644 --- a/compose/config/config_schema_v3.5.json +++ b/compose/config/config_schema_v3.5.json @@ -83,7 +83,7 @@ "context": {"type": "string"}, "dockerfile": {"type": "string"}, "args": {"$ref": "#/definitions/list_or_dict"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "cache_from": {"$ref": "#/definitions/list_of_strings"}, "network": {"type": "string"}, "target": {"type": "string"}, @@ -154,7 +154,7 @@ "hostname": {"type": "string"}, "image": {"type": "string"}, "ipc": {"type": "string"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { @@ -334,7 +334,7 @@ "mode": {"type": "string"}, "endpoint_mode": {"type": "string"}, "replicas": {"type": "integer"}, - "labels": {"$ref": "#/definitions/list_or_dict"}, + "labels": {"$ref": "#/definitions/labels"}, "update_config": { "type": "object", "properties": { @@ -435,7 +435,7 @@ }, "internal": {"type": "boolean"}, "attachable": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -459,7 +459,7 @@ }, "additionalProperties": false }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -475,7 +475,7 @@ "name": {"type": "string"} } }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -491,7 +491,7 @@ "name": {"type": "string"} } }, - "labels": {"$ref": "#/definitions/list_or_dict"} + "labels": {"$ref": "#/definitions/labels"} }, "additionalProperties": false }, @@ -524,6 +524,21 @@ ] }, + "labels": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "constraints": { "service": { "id": "#/definitions/constraints/service", diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index a758154c0..a61e0afcf 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -2631,6 +2631,40 @@ class ConfigTest(unittest.TestCase): ] assert service_sort(service_dicts) == service_sort(expected) + def test_config_invalid_service_label_validation(self): + config_details = build_config_details( + { + 'version': '3.5', + 'services': { + 'web': { + 'image': 'busybox', + 'labels': { + "key": 12345 + } + }, + }, + } + ) + with pytest.raises(ConfigurationError) as exc: + config.load(config_details) + assert "which is an invalid type, it should be a string" in exc.exconly() + + def test_config_valid_service_label_validation(self): + config_details = build_config_details( + { + 'version': '3.5', + 'services': { + 'web': { + 'image': 'busybox', + 'labels': { + "key": "string" + } + }, + }, + } + ) + config.load(config_details) + class NetworkModeTest(unittest.TestCase): From 7f30a88bd6c88ef40ba8ef6ff974b5c124e3ac0d Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 17 Jan 2018 18:44:26 -0800 Subject: [PATCH 2/2] Add type conversion (number, bool) -> float for label values Signed-off-by: Joffrey F --- compose/config/interpolation.py | 26 ++++++++++++- tests/unit/config/config_test.py | 51 +++++++++++++++++++++---- tests/unit/config/interpolation_test.py | 2 +- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/compose/config/interpolation.py b/compose/config/interpolation.py index 5a5f349db..9d52d2edb 100644 --- a/compose/config/interpolation.py +++ b/compose/config/interpolation.py @@ -84,7 +84,7 @@ def recursive_interpolate(obj, interpolator, config_path): ) if isinstance(obj, list): return [recursive_interpolate(val, interpolator, config_path) for val in obj] - return obj + return converter.convert(config_path, obj) class TemplateWithDefaults(Template): @@ -160,10 +160,11 @@ class UnsetRequiredSubstitution(Exception): PATH_JOKER = '[^.]+' +FULL_JOKER = '.+' def re_path(*args): - return re.compile('^{}$'.format('.'.join(args))) + return re.compile('^{}$'.format('\.'.join(args))) def re_path_basic(section, name): @@ -175,6 +176,8 @@ def service_path(*args): def to_boolean(s): + if not isinstance(s, six.string_types): + return s s = s.lower() if s in ['y', 'yes', 'true', 'on']: return True @@ -184,6 +187,9 @@ def to_boolean(s): def to_int(s): + if not isinstance(s, six.string_types): + return s + # We must be able to handle octal representation for `mode` values notably if six.PY3 and re.match('^0[0-9]+$', s.strip()): s = '0o' + s[1:] @@ -194,27 +200,39 @@ def to_int(s): def to_float(s): + if not isinstance(s, six.string_types): + return s + try: return float(s) except ValueError: raise ValueError('"{}" is not a valid float'.format(s)) +def to_str(o): + if isinstance(o, (bool, float, int)): + return '{}'.format(o) + return o + + class ConversionMap(object): map = { service_path('blkio_config', 'weight'): to_int, service_path('blkio_config', 'weight_device', 'weight'): to_int, + service_path('build', 'labels', FULL_JOKER): to_str, service_path('cpus'): to_float, service_path('cpu_count'): to_int, service_path('configs', 'mode'): to_int, service_path('secrets', 'mode'): to_int, service_path('healthcheck', 'retries'): to_int, service_path('healthcheck', 'disable'): to_boolean, + service_path('deploy', 'labels', PATH_JOKER): to_str, service_path('deploy', 'replicas'): to_int, service_path('deploy', 'update_config', 'parallelism'): to_int, service_path('deploy', 'update_config', 'max_failure_ratio'): to_float, service_path('deploy', 'restart_policy', 'max_attempts'): to_int, service_path('mem_swappiness'): to_int, + service_path('labels', FULL_JOKER): to_str, service_path('oom_kill_disable'): to_boolean, service_path('oom_score_adj'): to_int, service_path('ports', 'target'): to_int, @@ -232,9 +250,13 @@ class ConversionMap(object): re_path_basic('network', 'attachable'): to_boolean, re_path_basic('network', 'external'): to_boolean, re_path_basic('network', 'internal'): to_boolean, + re_path('network', PATH_JOKER, 'labels', FULL_JOKER): to_str, re_path_basic('volume', 'external'): to_boolean, + re_path('volume', PATH_JOKER, 'labels', FULL_JOKER): to_str, re_path_basic('secret', 'external'): to_boolean, + re_path('secret', PATH_JOKER, 'labels', FULL_JOKER): to_str, re_path_basic('config', 'external'): to_boolean, + re_path('config', PATH_JOKER, 'labels', FULL_JOKER): to_str, } def convert(self, path, value): diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 04e495f96..7cb74c00a 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -563,7 +563,7 @@ class ConfigTest(unittest.TestCase): 'services': { 'web': { 'build': { - 'context': '.', + 'context': os.getcwd(), 'args': None, }, }, @@ -959,7 +959,7 @@ class ConfigTest(unittest.TestCase): ).services[0] assert 'labels' in service['build'] assert 'label1' in service['build']['labels'] - assert service['build']['labels']['label1'] == 42 + assert service['build']['labels']['label1'] == '42' assert service['build']['labels']['label2'] == 'foobar' def test_load_build_labels_list(self): @@ -2747,24 +2747,61 @@ class ConfigTest(unittest.TestCase): ] assert service_sort(service_dicts) == service_sort(expected) - def test_config_invalid_service_label_validation(self): + def test_config_convertible_label_types(self): config_details = build_config_details( { 'version': '3.5', 'services': { 'web': { - 'image': 'busybox', + 'build': { + 'labels': {'testbuild': True}, + 'context': os.getcwd() + }, 'labels': { "key": 12345 } }, }, + 'networks': { + 'foo': { + 'labels': {'network.ips.max': 1023} + } + }, + 'volumes': { + 'foo': { + 'labels': {'volume.is_readonly': False} + } + }, + 'secrets': { + 'foo': { + 'labels': {'secret.data.expires': 1546282120} + } + }, + 'configs': { + 'foo': { + 'labels': {'config.data.correction.value': -0.1412} + } + } } ) - with pytest.raises(ConfigurationError) as exc: - config.load(config_details) + loaded_config = config.load(config_details) - assert "which is an invalid type, it should be a string" in exc.exconly() + assert loaded_config.services[0]['build']['labels'] == {'testbuild': 'True'} + assert loaded_config.services[0]['labels'] == {'key': '12345'} + assert loaded_config.networks['foo']['labels']['network.ips.max'] == '1023' + assert loaded_config.volumes['foo']['labels']['volume.is_readonly'] == 'False' + assert loaded_config.secrets['foo']['labels']['secret.data.expires'] == '1546282120' + assert loaded_config.configs['foo']['labels']['config.data.correction.value'] == '-0.1412' + + def test_config_invalid_label_types(self): + config_details = build_config_details({ + 'version': '2.3', + 'volumes': { + 'foo': {'labels': [1, 2, 3]} + } + }) + with pytest.raises(ConfigurationError): + config.load(config_details) def test_service_volume_invalid_config(self): config_details = build_config_details( diff --git a/tests/unit/config/interpolation_test.py b/tests/unit/config/interpolation_test.py index d68e4e559..fe5ef2490 100644 --- a/tests/unit/config/interpolation_test.py +++ b/tests/unit/config/interpolation_test.py @@ -109,7 +109,7 @@ def test_interpolate_environment_variables_in_secrets(mock_env): 'secretservice': { 'file': 'bar', 'labels': { - 'max': 2, + 'max': '2', 'user': 'jenny' } },