diff --git a/compose/config/serialize.py b/compose/config/serialize.py index 3fdd4d392..86fdac38f 100644 --- a/compose/config/serialize.py +++ b/compose/config/serialize.py @@ -21,8 +21,11 @@ def serialize_dict_type(dumper, data): def serialize_string(dumper, data): - """ Ensure boolean-like strings are quoted in the output """ + """ Ensure boolean-like strings are quoted in the output and escape $ characters """ representer = dumper.represent_str if six.PY3 else dumper.represent_unicode + + data = data.replace('$', '$$') + if data.lower() in ('y', 'n', 'yes', 'no', 'on', 'off', 'true', 'false'): # Empirically only y/n appears to be an issue, but this might change # depending on which PyYaml version is being used. Err on safe side. diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 9d42f2b59..63cb7eaef 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -4185,3 +4185,25 @@ class SerializeTest(unittest.TestCase): assert 'command: "true"\n' in serialized_config assert 'FOO: "Y"\n' in serialized_config assert 'BAR: "on"\n' in serialized_config + + def test_serialize_escape_dollar_sign(self): + cfg = { + 'version': '2.2', + 'services': { + 'web': { + 'image': 'busybox', + 'command': 'echo $$FOO', + 'environment': { + 'CURRENCY': '$$' + }, + 'entrypoint': ['$$SHELL', '-c'], + } + } + } + config_dict = config.load(build_config_details(cfg)) + + serialized_config = yaml.load(serialize_config(config_dict)) + serialized_service = serialized_config['services']['web'] + assert serialized_service['environment']['CURRENCY'] == '$$' + assert serialized_service['command'] == 'echo $$FOO' + assert serialized_service['entrypoint'][0] == '$$SHELL'