Skip to content

Commit efec2aa

Browse files
committedNov 23, 2015
Fixes #2008 - re-use list_or_dict schema for all the types
At the same time, moves extra_hosts validation to the config module. Signed-off-by: Daniel Nephin <dnephin@docker.com>

File tree

8 files changed

+90
-88
lines changed

8 files changed

+90
-88
lines changed
 

‎compose/config/config.py

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .errors import ComposeFileNotFound
1515
from .errors import ConfigurationError
1616
from .interpolation import interpolate_environment_variables
17+
from .types import parse_extra_hosts
1718
from .types import parse_restart_spec
1819
from .types import VolumeFromSpec
1920
from .validation import validate_against_fields_schema
@@ -379,6 +380,9 @@ def process_service(service_config):
379380
if 'labels' in service_dict:
380381
service_dict['labels'] = parse_labels(service_dict['labels'])
381382

383+
if 'extra_hosts' in service_dict:
384+
service_dict['extra_hosts'] = parse_extra_hosts(service_dict['extra_hosts'])
385+
382386
# TODO: move to a validate_service()
383387
if 'ulimits' in service_dict:
384388
validate_ulimits(service_dict['ulimits'])

‎compose/config/fields_schema.json

+12-19
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,7 @@
3737
"domainname": {"type": "string"},
3838
"entrypoint": {"$ref": "#/definitions/string_or_list"},
3939
"env_file": {"$ref": "#/definitions/string_or_list"},
40-
41-
"environment": {
42-
"oneOf": [
43-
{
44-
"type": "object",
45-
"patternProperties": {
46-
".+": {
47-
"type": ["string", "number", "boolean", "null"],
48-
"format": "environment"
49-
}
50-
},
51-
"additionalProperties": false
52-
},
53-
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
54-
]
55-
},
40+
"environment": {"$ref": "#/definitions/list_or_dict"},
5641

5742
"expose": {
5843
"type": "array",
@@ -165,10 +150,18 @@
165150

166151
"list_or_dict": {
167152
"oneOf": [
168-
{"type": "array", "items": {"type": "string"}, "uniqueItems": true},
169-
{"type": "object"}
153+
{
154+
"type": "object",
155+
"patternProperties": {
156+
".+": {
157+
"type": ["string", "number", "boolean", "null"],
158+
"format": "bool-value-in-mapping"
159+
}
160+
},
161+
"additionalProperties": false
162+
},
163+
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
170164
]
171165
}
172-
173166
}
174167
}

‎compose/config/types.py

+16
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,19 @@ def parse_restart_spec(restart_config):
4343
max_retry_count = 0
4444

4545
return {'Name': name, 'MaximumRetryCount': int(max_retry_count)}
46+
47+
48+
def parse_extra_hosts(extra_hosts_config):
49+
if not extra_hosts_config:
50+
return {}
51+
52+
if isinstance(extra_hosts_config, dict):
53+
return dict(extra_hosts_config)
54+
55+
if isinstance(extra_hosts_config, list):
56+
extra_hosts_dict = {}
57+
for extra_hosts_line in extra_hosts_config:
58+
# TODO: validate string contains ':' ?
59+
host, ip = extra_hosts_line.split(':')
60+
extra_hosts_dict[host.strip()] = ip.strip()
61+
return extra_hosts_dict

‎compose/config/validation.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def format_ports(instance):
4949
return True
5050

5151

52-
@FormatChecker.cls_checks(format="environment")
52+
@FormatChecker.cls_checks(format="bool-value-in-mapping")
5353
def format_boolean_in_environment(instance):
5454
"""
5555
Check if there is a boolean in the environment and display a warning.
@@ -273,7 +273,7 @@ def validate_against_fields_schema(config, filename):
273273
_validate_against_schema(
274274
config,
275275
"fields_schema.json",
276-
format_checker=["ports", "environment"],
276+
format_checker=["ports", "bool-value-in-mapping"],
277277
filename=filename)
278278

279279

‎compose/service.py

+3-33
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ def _get_container_host_config(self, override_options, one_off=False):
640640
pid = options.get('pid', None)
641641
security_opt = options.get('security_opt', None)
642642

643+
# TODO: these options are already normalized by config
643644
dns = options.get('dns', None)
644645
if isinstance(dns, six.string_types):
645646
dns = [dns]
@@ -648,9 +649,6 @@ def _get_container_host_config(self, override_options, one_off=False):
648649
if isinstance(dns_search, six.string_types):
649650
dns_search = [dns_search]
650651

651-
extra_hosts = build_extra_hosts(options.get('extra_hosts', None))
652-
read_only = options.get('read_only', None)
653-
654652
devices = options.get('devices', None)
655653
cgroup_parent = options.get('cgroup_parent', None)
656654
ulimits = build_ulimits(options.get('ulimits', None))
@@ -672,8 +670,8 @@ def _get_container_host_config(self, override_options, one_off=False):
672670
memswap_limit=options.get('memswap_limit'),
673671
ulimits=ulimits,
674672
log_config=log_config,
675-
extra_hosts=extra_hosts,
676-
read_only=read_only,
673+
extra_hosts=options.get('extra_hosts'),
674+
read_only=options.get('read_only'),
677675
pid_mode=pid,
678676
security_opt=security_opt,
679677
ipc_mode=options.get('ipc'),
@@ -1057,31 +1055,3 @@ def build_ulimits(ulimit_config):
10571055
ulimits.append(ulimit_dict)
10581056

10591057
return ulimits
1060-
1061-
1062-
# Extra hosts
1063-
1064-
1065-
def build_extra_hosts(extra_hosts_config):
1066-
if not extra_hosts_config:
1067-
return {}
1068-
1069-
if isinstance(extra_hosts_config, list):
1070-
extra_hosts_dict = {}
1071-
for extra_hosts_line in extra_hosts_config:
1072-
if not isinstance(extra_hosts_line, six.string_types):
1073-
raise ConfigError(
1074-
"extra_hosts_config \"%s\" must be either a list of strings or a string->string mapping," %
1075-
extra_hosts_config
1076-
)
1077-
host, ip = extra_hosts_line.split(':')
1078-
extra_hosts_dict.update({host.strip(): ip.strip()})
1079-
extra_hosts_config = extra_hosts_dict
1080-
1081-
if isinstance(extra_hosts_config, dict):
1082-
return extra_hosts_config
1083-
1084-
raise ConfigError(
1085-
"extra_hosts_config \"%s\" must be either a list of strings or a string->string mapping," %
1086-
extra_hosts_config
1087-
)

‎tests/integration/service_test.py

-33
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
from compose.const import LABEL_SERVICE
2323
from compose.const import LABEL_VERSION
2424
from compose.container import Container
25-
from compose.service import build_extra_hosts
26-
from compose.service import ConfigError
2725
from compose.service import ConvergencePlan
2826
from compose.service import ConvergenceStrategy
2927
from compose.service import Net
@@ -139,37 +137,6 @@ def test_create_container_with_cpu_shares(self):
139137
container.start()
140138
self.assertEqual(container.get('HostConfig.CpuShares'), 73)
141139

142-
def test_build_extra_hosts(self):
143-
# string
144-
self.assertRaises(ConfigError, lambda: build_extra_hosts("www.example.com: 192.168.0.17"))
145-
146-
# list of strings
147-
self.assertEqual(build_extra_hosts(
148-
["www.example.com:192.168.0.17"]),
149-
{'www.example.com': '192.168.0.17'})
150-
self.assertEqual(build_extra_hosts(
151-
["www.example.com: 192.168.0.17"]),
152-
{'www.example.com': '192.168.0.17'})
153-
self.assertEqual(build_extra_hosts(
154-
["www.example.com: 192.168.0.17",
155-
"static.example.com:192.168.0.19",
156-
"api.example.com: 192.168.0.18"]),
157-
{'www.example.com': '192.168.0.17',
158-
'static.example.com': '192.168.0.19',
159-
'api.example.com': '192.168.0.18'})
160-
161-
# list of dictionaries
162-
self.assertRaises(ConfigError, lambda: build_extra_hosts(
163-
[{'www.example.com': '192.168.0.17'},
164-
{'api.example.com': '192.168.0.18'}]))
165-
166-
# dictionaries
167-
self.assertEqual(build_extra_hosts(
168-
{'www.example.com': '192.168.0.17',
169-
'api.example.com': '192.168.0.18'}),
170-
{'www.example.com': '192.168.0.17',
171-
'api.example.com': '192.168.0.18'})
172-
173140
def test_create_container_with_extra_hosts_list(self):
174141
extra_hosts = ['somehost:162.242.195.82', 'otherhost:50.31.209.229']
175142
service = self.create_service('db', extra_hosts=extra_hosts)

‎tests/unit/config/config_test.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def service_sort(services):
3232
return sorted(services, key=itemgetter('name'))
3333

3434

35-
def build_config_details(contents, working_dir, filename):
35+
def build_config_details(contents, working_dir='working_dir', filename='filename.yml'):
3636
return config.ConfigDetails(
3737
working_dir,
3838
[config.ConfigFile(filename, contents)])
@@ -512,6 +512,29 @@ def test_load_yaml_with_yaml_error(self):
512512

513513
assert 'line 3, column 32' in exc.exconly()
514514

515+
def test_validate_extra_hosts_invalid(self):
516+
with pytest.raises(ConfigurationError) as exc:
517+
config.load(build_config_details({
518+
'web': {
519+
'image': 'alpine',
520+
'extra_hosts': "www.example.com: 192.168.0.17",
521+
}
522+
}))
523+
assert "'extra_hosts' contains an invalid type" in exc.exconly()
524+
525+
def test_validate_extra_hosts_invalid_list(self):
526+
with pytest.raises(ConfigurationError) as exc:
527+
config.load(build_config_details({
528+
'web': {
529+
'image': 'alpine',
530+
'extra_hosts': [
531+
{'www.example.com': '192.168.0.17'},
532+
{'api.example.com': '192.168.0.18'}
533+
],
534+
}
535+
}))
536+
assert "which is an invalid type" in exc.exconly()
537+
515538

516539
class InterpolationTest(unittest.TestCase):
517540
@mock.patch.dict(os.environ)

‎tests/unit/config/types_test.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from compose.config.types import parse_extra_hosts
2+
3+
4+
def test_parse_extra_hosts_list():
5+
expected = {'www.example.com': '192.168.0.17'}
6+
assert parse_extra_hosts(["www.example.com:192.168.0.17"]) == expected
7+
8+
expected = {'www.example.com': '192.168.0.17'}
9+
assert parse_extra_hosts(["www.example.com: 192.168.0.17"]) == expected
10+
11+
assert parse_extra_hosts([
12+
"www.example.com: 192.168.0.17",
13+
"static.example.com:192.168.0.19",
14+
"api.example.com: 192.168.0.18"
15+
]) == {
16+
'www.example.com': '192.168.0.17',
17+
'static.example.com': '192.168.0.19',
18+
'api.example.com': '192.168.0.18'
19+
}
20+
21+
22+
def test_parse_extra_hosts_dict():
23+
assert parse_extra_hosts({
24+
'www.example.com': '192.168.0.17',
25+
'api.example.com': '192.168.0.18'
26+
}) == {
27+
'www.example.com': '192.168.0.17',
28+
'api.example.com': '192.168.0.18'
29+
}

0 commit comments

Comments
 (0)
Please sign in to comment.