diff --git a/compose/config/config.py b/compose/config/config.py index 2a0df9453..46ee2e28e 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -16,6 +16,7 @@ from cached_property import cached_property from ..const import COMPOSEFILE_V1 as V1 from ..const import COMPOSEFILE_V2_0 as V2_0 +from ..utils import build_string_dict from .errors import CircularReference from .errors import ComposeFileNotFound from .errors import ConfigurationError @@ -291,7 +292,7 @@ def load(config_details): config_details = config_details._replace(config_files=processed_files) main_file = config_details.config_files[0] - volumes = load_mapping(config_details.config_files, 'get_volumes', 'Volume') + volumes = load_volumes(config_details.config_files) networks = load_mapping(config_details.config_files, 'get_networks', 'Network') service_dicts = load_services( config_details.working_dir, @@ -335,6 +336,14 @@ def load_mapping(config_files, get_func, entity_type): return mapping +def load_volumes(config_files): + volumes = load_mapping(config_files, 'get_volumes', 'Volume') + for volume_name, volume in volumes.items(): + if 'driver_opts' in volume: + volume['driver_opts'] = build_string_dict(volume['driver_opts']) + return volumes + + def load_services(working_dir, config_file, service_configs): def build_service(service_name, service_dict, service_names): service_config = ServiceConfig.with_abs_paths( @@ -854,7 +863,7 @@ def normalize_build(service_dict, working_dir): else: build.update(service_dict['build']) if 'args' in build: - build['args'] = resolve_build_args(build) + build['args'] = build_string_dict(resolve_build_args(build)) service_dict['build'] = build diff --git a/compose/config/fields_schema_v2.0.json b/compose/config/fields_schema_v2.0.json index 876065e51..7703adcd0 100644 --- a/compose/config/fields_schema_v2.0.json +++ b/compose/config/fields_schema_v2.0.json @@ -78,7 +78,7 @@ "driver_opts": { "type": "object", "patternProperties": { - "^.+$": {"type": "string"} + "^.+$": {"type": ["string", "number"]} } }, "external": { diff --git a/compose/config/service_schema_v2.0.json b/compose/config/service_schema_v2.0.json index 98ae90a27..4c5c40fbc 100644 --- a/compose/config/service_schema_v2.0.json +++ b/compose/config/service_schema_v2.0.json @@ -23,7 +23,20 @@ "properties": { "context": {"type": "string"}, "dockerfile": {"type": "string"}, - "args": {"$ref": "#/definitions/list_or_dict"} + "args": { + "oneOf": [ + {"$ref": "#/definitions/list_of_strings"}, + { + "type": "object", + "patternProperties": { + "^.+$": { + "type": ["string", "number"] + } + }, + "additionalProperties": false + } + ] + } }, "additionalProperties": false } diff --git a/compose/utils.py b/compose/utils.py index 29d8a695d..669df1d20 100644 --- a/compose/utils.py +++ b/compose/utils.py @@ -92,3 +92,7 @@ def json_hash(obj): def microseconds_from_time_nano(time_nano): return int(time_nano % 1000000000 / 1000) + + +def build_string_dict(source_dict): + return dict([(k, str(v)) for k, v in source_dict.items()]) diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 59dd73648..204003bce 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -231,7 +231,7 @@ class ConfigTest(unittest.TestCase): assert volumes['simple'] == {} assert volumes['other'] == {} - def test_volume_invalid_driver_opt(self): + def test_volume_numeric_driver_opt(self): config_details = build_config_details({ 'version': '2', 'services': { @@ -241,6 +241,19 @@ class ConfigTest(unittest.TestCase): 'simple': {'driver_opts': {'size': 42}}, } }) + cfg = config.load(config_details) + assert cfg.volumes['simple']['driver_opts']['size'] == '42' + + def test_volume_invalid_driver_opt(self): + config_details = build_config_details({ + 'version': '2', + 'services': { + 'simple': {'image': 'busybox'} + }, + 'volumes': { + 'simple': {'driver_opts': {'size': True}}, + } + }) with pytest.raises(ConfigurationError) as exc: config.load(config_details) assert 'driver_opts.size contains an invalid type' in exc.exconly()