diff --git a/CHANGELOG.md b/CHANGELOG.md index 295def12bb..7c7a911dc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,13 @@ Major Changes: If you need the old behaviour, quote the value and it will get passed around as a string * added meta: refresh_inventory to force rereading the inventory in a play * vars are now settable at play, block, role and task level + * template code now retains types for bools, and Numbers instead of turning them into strings + If you need the old behaviour, quote the value and it will get passed around as a string. In the + case of nulls, the output used to be an empty string. + * Empty variables and variables set to null in yaml will no longer be converted to empty strings. + They will retain the value of `None`. To go back to the old behaviour, you can override + the `null_representation` setting to an empty string in your config file or by setting the + `ANSIBLE_NULL_REPRESENTATION` environment variable. Deprecated Modules (new ones in parens): * ec2_ami_search (ec2_ami_find) diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index 290274c13e..8cb3bcb2fb 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -40,7 +40,7 @@ def mk_boolean(value): else: return False -def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False, islist=False): +def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False, islist=False, isnone=False): ''' return a configuration variable with casting ''' value = _get_config(p, section, key, env_var, default) if boolean: @@ -53,6 +53,9 @@ def get_config(p, section, key, env_var, default, boolean=False, integer=False, elif islist: if isinstance(value, string_types): value = [x.strip() for x in value.split(',')] + elif isnone: + if value == "None": + value = None elif isinstance(value, string_types): value = unquote(value) return value @@ -205,6 +208,7 @@ DEFAULT_LOAD_CALLBACK_PLUGINS = get_config(p, DEFAULTS, 'bin_ansible_callbacks' DEFAULT_CALLBACK_WHITELIST = get_config(p, DEFAULTS, 'callback_whitelist', 'ANSIBLE_CALLBACK_WHITELIST', [], islist=True) RETRY_FILES_ENABLED = get_config(p, DEFAULTS, 'retry_files_enabled', 'ANSIBLE_RETRY_FILES_ENABLED', True, boolean=True) RETRY_FILES_SAVE_PATH = get_config(p, DEFAULTS, 'retry_files_save_path', 'ANSIBLE_RETRY_FILES_SAVE_PATH', '~/') +DEFAULT_NULL_REPRESENTATION = get_config(p, DEFAULTS, 'null_representation', 'ANSIBLE_NULL_REPRESENTATION', None, isnone=True) # CONNECTION RELATED ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index c309e113ad..1a1465139a 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -37,6 +37,7 @@ from ansible.template.vars import AnsibleJ2Vars from ansible.utils.debug import debug from numbers import Number +from types import NoneType __all__ = ['Templar'] @@ -187,6 +188,8 @@ class Templar: resolved_val = self._available_variables[var_name] if isinstance(resolved_val, NON_TEMPLATED_TYPES): return resolved_val + elif isinstance(resolved_val, NoneType): + return C.DEFAULT_NULL_REPRESENTATION result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines, fail_on_undefined=fail_on_undefined, overrides=overrides) diff --git a/test/integration/roles/test_template/files/foo.txt b/test/integration/roles/test_template/files/foo.txt index edd704da04..58af3be81b 100644 --- a/test/integration/roles/test_template/files/foo.txt +++ b/test/integration/roles/test_template/files/foo.txt @@ -3,6 +3,7 @@ templated_var_loaded { "bool": true, "multi_part": "1Foo", + "null_type": null, "number": 5, "string_num": "5" } diff --git a/test/integration/roles/test_template/vars/main.yml b/test/integration/roles/test_template/vars/main.yml index b79f95e6cf..16776cb7e8 100644 --- a/test/integration/roles/test_template/vars/main.yml +++ b/test/integration/roles/test_template/vars/main.yml @@ -5,10 +5,12 @@ string_num: "5" bool_var: true part_1: 1 part_2: "Foo" +null_type: !!null templated_dict: number: "{{ number_var }}" string_num: "{{ string_num }}" + null_type: "{{ null_type }}" bool: "{{ bool_var }}" multi_part: "{{ part_1 }}{{ part_2 }}" diff --git a/v1/ansible/utils/template.py b/v1/ansible/utils/template.py index fb35924ce1..368b2067c3 100644 --- a/v1/ansible/utils/template.py +++ b/v1/ansible/utils/template.py @@ -32,6 +32,7 @@ import pwd import ast import traceback from numbers import Number +from types import NoneType from ansible.utils.string_functions import count_newlines_from_end from ansible.utils import to_bytes, to_unicode @@ -343,7 +344,7 @@ def template_from_string(basedir, data, vars, fail_on_undefined=False): var_name = only_one.group(1) if var_name in vars: resolved_val = vars[var_name] - if isinstance(resolved_val, (bool, Number)): + if isinstance(resolved_val, (bool, Number, NoneType)): return resolved_val def my_finalize(thing):