Skip to content

Commit 67995ab

Browse files
committed
Pre-process validation steps
In order to validate a service name that has been specified as an integer we need to run that as a pre-process validation step *before* we pass the config to be validated against the schema. It is not possible to validate it *in* the schema, it causes a type error. Even though a number is a valid service name, it must be a cast as a string within the yaml to avoid type error. Taken this opportunity to move the code design in a direction towards: 1. pre-process 2. validate 3. construct Signed-off-by: Mazz Mosley <mazz@houseofmnowster.com>
1 parent c443e95 commit 67995ab

File tree

3 files changed

+54
-8
lines changed

3 files changed

+54
-8
lines changed

compose/config/config.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
CircularReference,
1414
ComposeFileNotFound,
1515
)
16-
from .validation import validate_against_schema
16+
from .validation import (
17+
validate_against_schema,
18+
validate_service_names,
19+
validate_top_level_object
20+
)
1721

1822

1923
DOCKER_CONFIG_KEYS = [
@@ -122,19 +126,26 @@ def get_config_path(base_dir):
122126
return os.path.join(path, winner)
123127

124128

129+
@validate_top_level_object
130+
@validate_service_names
131+
def pre_process_config(config):
132+
"""
133+
Pre validation checks and processing of the config file to interpolate env
134+
vars returning a config dict ready to be tested against the schema.
135+
"""
136+
config = interpolate_environment_variables(config)
137+
return config
138+
139+
125140
def load(config_details):
126141
config, working_dir, filename = config_details
127-
if not isinstance(config, dict):
128-
raise ConfigurationError(
129-
"Top level object needs to be a dictionary. Check your .yml file that you have defined a service at the top level."
130-
)
131142

132-
config = interpolate_environment_variables(config)
133-
validate_against_schema(config)
143+
processed_config = pre_process_config(config)
144+
validate_against_schema(processed_config)
134145

135146
service_dicts = []
136147

137-
for service_name, service_dict in list(config.items()):
148+
for service_name, service_dict in list(processed_config.items()):
138149
loader = ServiceLoader(working_dir=working_dir, filename=filename)
139150
service_dict = loader.make_service_dict(service_name, service_dict)
140151
validate_paths(service_dict)

compose/config/validation.py

+24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from functools import wraps
12
import os
23

34
from docker.utils.ports import split_port
@@ -36,6 +37,29 @@ def format_ports(instance):
3637
return True
3738

3839

40+
def validate_service_names(func):
41+
@wraps(func)
42+
def func_wrapper(config):
43+
for service_name in config.keys():
44+
if type(service_name) is int:
45+
raise ConfigurationError(
46+
"Service name: {} needs to be a string, eg '{}'".format(service_name, service_name)
47+
)
48+
return func(config)
49+
return func_wrapper
50+
51+
52+
def validate_top_level_object(func):
53+
@wraps(func)
54+
def func_wrapper(config):
55+
if not isinstance(config, dict):
56+
raise ConfigurationError(
57+
"Top level object needs to be a dictionary. Check your .yml file that you have defined a service at the top level."
58+
)
59+
return func(config)
60+
return func_wrapper
61+
62+
3963
def get_unsupported_config_msg(service_name, error_key):
4064
msg = "Unsupported config option for '{}' service: '{}'".format(service_name, error_key)
4165
if error_key in DOCKER_CONFIG_HINTS:

tests/unit/config_test.py

+11
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ def test_config_invalid_service_names(self):
6464
)
6565
)
6666

67+
def test_config_integer_service_name_raise_validation_error(self):
68+
expected_error_msg = "Service name: 1 needs to be a string, eg '1'"
69+
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
70+
config.load(
71+
config.ConfigDetails(
72+
{1: {'image': 'busybox'}},
73+
'working_dir',
74+
'filename.yml'
75+
)
76+
)
77+
6778
def test_config_valid_service_names(self):
6879
for valid_name in ['_', '-', '.__.', '_what-up.', 'what_.up----', 'whatup']:
6980
config.load(

0 commit comments

Comments
 (0)