diff --git a/CHANGELOG.md b/CHANGELOG.md index 06fe0504fc..69d7c3fd56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,10 @@ Ansible Changes By Release ## 2.0 "TBD" - ACTIVE DEVELOPMENT Major Changes: - big_ip modules now support turning off ssl certificate validation (use only for self signed) + - big_ip modules now support turning off ssl certificate validation (use only for self signed) + + - 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 New Modules: cloudtrail diff --git a/lib/ansible/utils/template.py b/lib/ansible/utils/template.py index 9426e254eb..5f712b2675 100644 --- a/lib/ansible/utils/template.py +++ b/lib/ansible/utils/template.py @@ -31,6 +31,7 @@ import datetime import pwd import ast import traceback +from numbers import Number from ansible.utils.string_functions import count_newlines_from_end from ansible.utils import to_bytes, to_unicode @@ -81,6 +82,11 @@ class Flags: FILTER_PLUGINS = None _LISTRE = re.compile(r"(\w+)\[(\d+)\]") + +# A regex for checking to see if a variable we're trying to +# expand is just a single variable name. +SINGLE_VAR = re.compile(r"^{{\s*(\w*)\s*}}$") + JINJA2_OVERRIDE = '#jinja2:' JINJA2_ALLOWED_OVERRIDES = ['trim_blocks', 'lstrip_blocks', 'newline_sequence', 'keep_trailing_newline'] @@ -109,7 +115,6 @@ def lookup(name, *args, **kwargs): def template(basedir, varname, templatevars, lookup_fatal=True, depth=0, expand_lists=True, convert_bare=False, fail_on_undefined=False, filter_fatal=True): ''' templates a data structure by traversing it and substituting for other data structures ''' from ansible import utils - try: if convert_bare and isinstance(varname, basestring): first_part = varname.split(".")[0].split("[")[0] @@ -123,10 +128,13 @@ def template(basedir, varname, templatevars, lookup_fatal=True, depth=0, expand_ except errors.AnsibleError, e: raise errors.AnsibleError("Failed to template %s: %s" % (varname, str(e))) - if (varname.startswith("{") and not varname.startswith("{{")) or varname.startswith("["): - eval_results = utils.safe_eval(varname, locals=templatevars, include_exceptions=True) - if eval_results[1] is None: - varname = eval_results[0] + # template_from_string may return non strings for the case where the var is just + # a reference to a single variable, so we should re_check before we do further evals + if isinstance(varname, basestring): + if (varname.startswith("{") and not varname.startswith("{{")) or varname.startswith("["): + eval_results = utils.safe_eval(varname, locals=templatevars, include_exceptions=True) + if eval_results[1] is None: + varname = eval_results[0] return varname @@ -323,10 +331,20 @@ def template_from_file(basedir, path, vars, vault_password=None): def template_from_string(basedir, data, vars, fail_on_undefined=False): ''' run a string through the (Jinja2) templating engine ''' - try: if type(data) == str: data = unicode(data, 'utf-8') + + # Check to see if the string we are trying to render is just referencing a single + # var. In this case we don't wont to accidentally change the type of the variable + # to a string by using the jinja template renderer. We just want to pass it. + only_one = SINGLE_VAR.match(data) + if only_one: + var_name = only_one.group(1) + if var_name in vars: + resolved_val = vars[var_name] + if isinstance(resolved_val, (bool, Number)): + return resolved_val def my_finalize(thing): return thing if thing is not None else '' diff --git a/test/integration/roles/test_template/files/foo.txt b/test/integration/roles/test_template/files/foo.txt index 3e96db9b3e..edd704da04 100644 --- a/test/integration/roles/test_template/files/foo.txt +++ b/test/integration/roles/test_template/files/foo.txt @@ -1 +1,8 @@ templated_var_loaded + +{ + "bool": true, + "multi_part": "1Foo", + "number": 5, + "string_num": "5" +} diff --git a/test/integration/roles/test_template/templates/foo.j2 b/test/integration/roles/test_template/templates/foo.j2 index 55aab8f1ea..22187f9130 100644 --- a/test/integration/roles/test_template/templates/foo.j2 +++ b/test/integration/roles/test_template/templates/foo.j2 @@ -1 +1,3 @@ {{ templated_var }} + +{{ templated_dict | to_nice_json }} diff --git a/test/integration/roles/test_template/vars/main.yml b/test/integration/roles/test_template/vars/main.yml index 1e8f64ccf4..b79f95e6cf 100644 --- a/test/integration/roles/test_template/vars/main.yml +++ b/test/integration/roles/test_template/vars/main.yml @@ -1 +1,14 @@ templated_var: templated_var_loaded + +number_var: 5 +string_num: "5" +bool_var: true +part_1: 1 +part_2: "Foo" + +templated_dict: + number: "{{ number_var }}" + string_num: "{{ string_num }}" + bool: "{{ bool_var }}" + multi_part: "{{ part_1 }}{{ part_2 }}" + diff --git a/v2/ansible/template/__init__.py b/v2/ansible/template/__init__.py index 46bbc06a07..0345a75008 100644 --- a/v2/ansible/template/__init__.py +++ b/v2/ansible/template/__init__.py @@ -32,8 +32,17 @@ from ansible.template.template import AnsibleJ2Template from ansible.template.vars import AnsibleJ2Vars from ansible.utils.debug import debug +from numbers import Number + __all__ = ['Templar'] +# A regex for checking to see if a variable we're trying to +# expand is just a single variable name. +SINGLE_VAR = re.compile(r"^{{\s*(\w*)\s*}}$") + +# Primitive Types which we don't want Jinja to convert to strings. +NON_TEMPLATED_TYPES = ( bool, Number ) + JINJA2_OVERRIDE = '#jinja2:' JINJA2_ALLOWED_OVERRIDES = ['trim_blocks', 'lstrip_blocks', 'newline_sequence', 'keep_trailing_newline'] @@ -125,6 +134,18 @@ class Templar: if isinstance(variable, basestring): result = variable if self._contains_vars(variable): + + # Check to see if the string we are trying to render is just referencing a single + # var. In this case we don't wont to accidentally change the type of the variable + # to a string by using the jinja template renderer. We just want to pass it. + only_one = SINGLE_VAR.match(variable) + if only_one: + var_name = only_one.group(1) + if var_name in self._available_vars: + resolved_val = self._available_vars[var_name] + if isinstance(resolved_val, NON_TEMPLATED_TYPES): + return resolved_val + result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines) # if this looks like a dictionary or list, convert it to such using the safe_eval method