From 892e230514090dc9221ee01d289c3532aa6ef260 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Fri, 8 May 2015 10:12:36 -0400 Subject: [PATCH] Don't convert nulls to strings. This change is similar to https://github.com/ansible/ansible/pull/10465 It extends the logic there to also support none types. Right now if you have a '!!null' in yaml, and that var gets passed around, it will get converted to a string. eg. defaults/main.yml ``` ENABLE_AWESOME_FEATURE: !!null # Yaml Null OTHER_CONFIG: secret1: "so_secret" secret2: "even_more_secret" CONFIG: hostname: "some_hostname" features: awesame_feature: "{{ ENABLE_AWESOME_FEATURE}}" secrets: "{{ OTHER_CONFIG }}" ``` If you output `CONFIG` to json or yaml, the feature flag would get represented in the output as a string instead of as a null, but secrets would get represented as a dictionary. This is a mis-match in behaviour where some "types" are retained and others are not. This change should fix the issue. I also updated the template test to test for this and made the changes to v2. Added a changelog entry specifically for the change from empty string to null as the default. Made the null representation configurable. It still defaults to the python NoneType but can be overriden to be an emptystring by updating the DEFAULT_NULL_REPRESENTATION config. --- CHANGELOG.md | 7 +++++++ lib/ansible/constants.py | 6 +++++- lib/ansible/template/__init__.py | 3 +++ test/integration/roles/test_template/files/foo.txt | 1 + test/integration/roles/test_template/vars/main.yml | 2 ++ v1/ansible/utils/template.py | 3 ++- 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 295def12bb3..7c7a911dc89 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 290274c13e8..8cb3bcb2fb8 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 c309e113adc..1a1465139ac 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 edd704da048..58af3be81b9 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 b79f95e6cf1..16776cb7e83 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 fb35924ce14..368b2067c35 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):