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>
This commit is contained in:
Mazz Mosley 2015-08-12 17:09:48 +01:00
parent c443e95f07
commit 67995ab9e3
3 changed files with 54 additions and 8 deletions

View File

@ -13,7 +13,11 @@ from .errors import (
CircularReference,
ComposeFileNotFound,
)
from .validation import validate_against_schema
from .validation import (
validate_against_schema,
validate_service_names,
validate_top_level_object
)
DOCKER_CONFIG_KEYS = [
@ -122,19 +126,26 @@ def get_config_path(base_dir):
return os.path.join(path, winner)
@validate_top_level_object
@validate_service_names
def pre_process_config(config):
"""
Pre validation checks and processing of the config file to interpolate env
vars returning a config dict ready to be tested against the schema.
"""
config = interpolate_environment_variables(config)
return config
def load(config_details):
config, working_dir, filename = config_details
if not isinstance(config, dict):
raise ConfigurationError(
"Top level object needs to be a dictionary. Check your .yml file that you have defined a service at the top level."
)
config = interpolate_environment_variables(config)
validate_against_schema(config)
processed_config = pre_process_config(config)
validate_against_schema(processed_config)
service_dicts = []
for service_name, service_dict in list(config.items()):
for service_name, service_dict in list(processed_config.items()):
loader = ServiceLoader(working_dir=working_dir, filename=filename)
service_dict = loader.make_service_dict(service_name, service_dict)
validate_paths(service_dict)

View File

@ -1,3 +1,4 @@
from functools import wraps
import os
from docker.utils.ports import split_port
@ -36,6 +37,29 @@ def format_ports(instance):
return True
def validate_service_names(func):
@wraps(func)
def func_wrapper(config):
for service_name in config.keys():
if type(service_name) is int:
raise ConfigurationError(
"Service name: {} needs to be a string, eg '{}'".format(service_name, service_name)
)
return func(config)
return func_wrapper
def validate_top_level_object(func):
@wraps(func)
def func_wrapper(config):
if not isinstance(config, dict):
raise ConfigurationError(
"Top level object needs to be a dictionary. Check your .yml file that you have defined a service at the top level."
)
return func(config)
return func_wrapper
def get_unsupported_config_msg(service_name, error_key):
msg = "Unsupported config option for '{}' service: '{}'".format(service_name, error_key)
if error_key in DOCKER_CONFIG_HINTS:

View File

@ -64,6 +64,17 @@ class ConfigTest(unittest.TestCase):
)
)
def test_config_integer_service_name_raise_validation_error(self):
expected_error_msg = "Service name: 1 needs to be a string, eg '1'"
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
config.load(
config.ConfigDetails(
{1: {'image': 'busybox'}},
'working_dir',
'filename.yml'
)
)
def test_config_valid_service_names(self):
for valid_name in ['_', '-', '.__.', '_what-up.', 'what_.up----', 'whatup']:
config.load(