瀏覽代碼

Merge pull request #2000 from mnowster/do-not-allow-booleans-in-environment

Disallow booleans in environment
Aanand Prasad 10 年之前
父節點
當前提交
041a1ff08c
共有 4 個文件被更改,包括 60 次插入9 次删除
  1. 10 1
      compose/config/fields_schema.json
  2. 25 4
      compose/config/validation.py
  3. 5 1
      docs/yml.md
  4. 20 3
      tests/unit/config_test.py

+ 10 - 1
compose/config/fields_schema.json

@@ -36,7 +36,16 @@
 
         "environment": {
           "oneOf": [
-            {"type": "object"},
+            {
+              "type": "object",
+              "patternProperties": {
+                "^[^-]+$": {
+                  "type": ["string", "number", "boolean"],
+                  "format": "environment"
+                }
+              },
+              "additionalProperties": false
+            },
             {"type": "array", "items": {"type": "string"}, "uniqueItems": true}
           ]
         },

+ 25 - 4
compose/config/validation.py

@@ -1,4 +1,5 @@
 import json
+import logging
 import os
 from functools import wraps
 
@@ -11,6 +12,9 @@ from jsonschema import ValidationError
 from .errors import ConfigurationError
 
 
+log = logging.getLogger(__name__)
+
+
 DOCKER_CONFIG_HINTS = {
     'cpu_share': 'cpu_shares',
     'add_host': 'extra_hosts',
@@ -44,6 +48,21 @@ def format_ports(instance):
     return True
 
 
[email protected]_checks(format="environment")
+def format_boolean_in_environment(instance):
+    """
+    Check if there is a boolean in the environment and display a warning.
+    Always return True here so the validation won't raise an error.
+    """
+    if isinstance(instance, bool):
+        log.warn(
+            "Warning: There is a boolean value, {0} in the 'environment' key.\n"
+            "Environment variables can only be strings.\nPlease add quotes to any boolean values to make them string "
+            "(eg, '{0}').\nThis warning will become an error in a future release. \r\n".format(instance)
+        )
+    return True
+
+
 def validate_service_names(func):
     @wraps(func)
     def func_wrapper(config):
@@ -259,15 +278,17 @@ def process_errors(errors, service_name=None):
 
 def validate_against_fields_schema(config):
     schema_filename = "fields_schema.json"
-    return _validate_against_schema(config, schema_filename)
+    format_checkers = ["ports", "environment"]
+    return _validate_against_schema(config, schema_filename, format_checkers)
 
 
 def validate_against_service_schema(config, service_name):
     schema_filename = "service_schema.json"
-    return _validate_against_schema(config, schema_filename, service_name)
+    format_checkers = ["ports"]
+    return _validate_against_schema(config, schema_filename, format_checkers, service_name)
 
 
-def _validate_against_schema(config, schema_filename, service_name=None):
+def _validate_against_schema(config, schema_filename, format_checker=[], service_name=None):
     config_source_dir = os.path.dirname(os.path.abspath(__file__))
     schema_file = os.path.join(config_source_dir, schema_filename)
 
@@ -275,7 +296,7 @@ def _validate_against_schema(config, schema_filename, service_name=None):
         schema = json.load(schema_fh)
 
     resolver = RefResolver('file://' + config_source_dir + '/', schema)
-    validation_output = Draft4Validator(schema, resolver=resolver, format_checker=FormatChecker(["ports"]))
+    validation_output = Draft4Validator(schema, resolver=resolver, format_checker=FormatChecker(format_checker))
 
     errors = [error for error in sorted(validation_output.iter_errors(config), key=str)]
     if errors:

+ 5 - 1
docs/yml.md

@@ -184,17 +184,21 @@ Mount all of the volumes from another service or container.
 
 ### environment
 
-Add environment variables. You can use either an array or a dictionary.
+Add environment variables. You can use either an array or a dictionary. Any
+boolean values; true, false, yes no, need to be enclosed in quotes to ensure
+they are not converted to True or False by the YML parser.
 
 Environment variables with only a key are resolved to their values on the
 machine Compose is running on, which can be helpful for secret or host-specific values.
 
     environment:
       RACK_ENV: development
+      SHOW: 'true'
       SESSION_SECRET:
 
     environment:
       - RACK_ENV=development
+      - SHOW=true
       - SESSION_SECRET
 
 ### env_file

+ 20 - 3
tests/unit/config_test.py

@@ -270,15 +270,32 @@ class ConfigTest(unittest.TestCase):
             )
             self.assertEqual(service[0]['entrypoint'], entrypoint)
 
-    def test_validation_message_for_invalid_type_when_multiple_types_allowed(self):
-        expected_error_msg = "Service 'web' configuration key 'mem_limit' contains an invalid type, it should be a number or a string"
+    @mock.patch('compose.config.validation.log')
+    def test_logs_warning_for_boolean_in_environment(self, mock_logging):
+        expected_warning_msg = "Warning: There is a boolean value, True in the 'environment' key."
+        config.load(
+            config.ConfigDetails(
+                {'web': {
+                    'image': 'busybox',
+                    'environment': {'SHOW_STUFF': True}
+                }},
+                'working_dir',
+                'filename.yml'
+            )
+        )
+
+        self.assertTrue(mock_logging.warn.called)
+        self.assertTrue(expected_warning_msg in mock_logging.warn.call_args[0][0])
+
+    def test_config_invalid_environment_dict_key_raises_validation_error(self):
+        expected_error_msg = "Service 'web' configuration key 'environment' contains an invalid type"
 
         with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
             config.load(
                 config.ConfigDetails(
                     {'web': {
                         'image': 'busybox',
-                        'mem_limit': ['incorrect']
+                        'environment': {'---': 'nope'}
                     }},
                     'working_dir',
                     'filename.yml'