Skip to content

Commit 041a1ff

Browse files
committed
Merge pull request #2000 from mnowster/do-not-allow-booleans-in-environment
Disallow booleans in environment
2 parents e33ab0c + 4b2fd76 commit 041a1ff

File tree

4 files changed

+60
-9
lines changed

4 files changed

+60
-9
lines changed

compose/config/fields_schema.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,16 @@
3636

3737
"environment": {
3838
"oneOf": [
39-
{"type": "object"},
39+
{
40+
"type": "object",
41+
"patternProperties": {
42+
"^[^-]+$": {
43+
"type": ["string", "number", "boolean"],
44+
"format": "environment"
45+
}
46+
},
47+
"additionalProperties": false
48+
},
4049
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
4150
]
4251
},

compose/config/validation.py

+25-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import logging
23
import os
34
from functools import wraps
45

@@ -11,6 +12,9 @@
1112
from .errors import ConfigurationError
1213

1314

15+
log = logging.getLogger(__name__)
16+
17+
1418
DOCKER_CONFIG_HINTS = {
1519
'cpu_share': 'cpu_shares',
1620
'add_host': 'extra_hosts',
@@ -44,6 +48,21 @@ def format_ports(instance):
4448
return True
4549

4650

51+
@FormatChecker.cls_checks(format="environment")
52+
def format_boolean_in_environment(instance):
53+
"""
54+
Check if there is a boolean in the environment and display a warning.
55+
Always return True here so the validation won't raise an error.
56+
"""
57+
if isinstance(instance, bool):
58+
log.warn(
59+
"Warning: There is a boolean value, {0} in the 'environment' key.\n"
60+
"Environment variables can only be strings.\nPlease add quotes to any boolean values to make them string "
61+
"(eg, '{0}').\nThis warning will become an error in a future release. \r\n".format(instance)
62+
)
63+
return True
64+
65+
4766
def validate_service_names(func):
4867
@wraps(func)
4968
def func_wrapper(config):
@@ -259,23 +278,25 @@ def _parse_oneof_validator(error):
259278

260279
def validate_against_fields_schema(config):
261280
schema_filename = "fields_schema.json"
262-
return _validate_against_schema(config, schema_filename)
281+
format_checkers = ["ports", "environment"]
282+
return _validate_against_schema(config, schema_filename, format_checkers)
263283

264284

265285
def validate_against_service_schema(config, service_name):
266286
schema_filename = "service_schema.json"
267-
return _validate_against_schema(config, schema_filename, service_name)
287+
format_checkers = ["ports"]
288+
return _validate_against_schema(config, schema_filename, format_checkers, service_name)
268289

269290

270-
def _validate_against_schema(config, schema_filename, service_name=None):
291+
def _validate_against_schema(config, schema_filename, format_checker=[], service_name=None):
271292
config_source_dir = os.path.dirname(os.path.abspath(__file__))
272293
schema_file = os.path.join(config_source_dir, schema_filename)
273294

274295
with open(schema_file, "r") as schema_fh:
275296
schema = json.load(schema_fh)
276297

277298
resolver = RefResolver('file://' + config_source_dir + '/', schema)
278-
validation_output = Draft4Validator(schema, resolver=resolver, format_checker=FormatChecker(["ports"]))
299+
validation_output = Draft4Validator(schema, resolver=resolver, format_checker=FormatChecker(format_checker))
279300

280301
errors = [error for error in sorted(validation_output.iter_errors(config), key=str)]
281302
if errors:

docs/yml.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -184,17 +184,21 @@ Mount all of the volumes from another service or container.
184184

185185
### environment
186186

187-
Add environment variables. You can use either an array or a dictionary.
187+
Add environment variables. You can use either an array or a dictionary. Any
188+
boolean values; true, false, yes no, need to be enclosed in quotes to ensure
189+
they are not converted to True or False by the YML parser.
188190

189191
Environment variables with only a key are resolved to their values on the
190192
machine Compose is running on, which can be helpful for secret or host-specific values.
191193

192194
environment:
193195
RACK_ENV: development
196+
SHOW: 'true'
194197
SESSION_SECRET:
195198

196199
environment:
197200
- RACK_ENV=development
201+
- SHOW=true
198202
- SESSION_SECRET
199203

200204
### env_file

tests/unit/config_test.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,32 @@ def test_valid_config_oneof_string_or_list(self):
270270
)
271271
self.assertEqual(service[0]['entrypoint'], entrypoint)
272272

273-
def test_validation_message_for_invalid_type_when_multiple_types_allowed(self):
274-
expected_error_msg = "Service 'web' configuration key 'mem_limit' contains an invalid type, it should be a number or a string"
273+
@mock.patch('compose.config.validation.log')
274+
def test_logs_warning_for_boolean_in_environment(self, mock_logging):
275+
expected_warning_msg = "Warning: There is a boolean value, True in the 'environment' key."
276+
config.load(
277+
config.ConfigDetails(
278+
{'web': {
279+
'image': 'busybox',
280+
'environment': {'SHOW_STUFF': True}
281+
}},
282+
'working_dir',
283+
'filename.yml'
284+
)
285+
)
286+
287+
self.assertTrue(mock_logging.warn.called)
288+
self.assertTrue(expected_warning_msg in mock_logging.warn.call_args[0][0])
289+
290+
def test_config_invalid_environment_dict_key_raises_validation_error(self):
291+
expected_error_msg = "Service 'web' configuration key 'environment' contains an invalid type"
275292

276293
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
277294
config.load(
278295
config.ConfigDetails(
279296
{'web': {
280297
'image': 'busybox',
281-
'mem_limit': ['incorrect']
298+
'environment': {'---': 'nope'}
282299
}},
283300
'working_dir',
284301
'filename.yml'

0 commit comments

Comments
 (0)