From f585c4cde7805b5d2f36345d91f2fd5846ea6694 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 5 Apr 2013 19:10:32 -0400 Subject: [PATCH] Upgraded variable support met with upgraded conditional support, see examples/playbooks/upgraded_vars.yml for details! --- CHANGELOG.md | 3 ++- examples/playbooks/upgraded_vars.yml | 24 +++++++++++++++++++++ lib/ansible/playbook/task.py | 2 ++ lib/ansible/runner/__init__.py | 1 + lib/ansible/utils/__init__.py | 21 ++++++++++++------ lib/ansible/utils/template.py | 32 +++++++++++++--------------- 6 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 examples/playbooks/upgraded_vars.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a217d9ca4e..17559267fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ Core Features: * capability to set 'all_errors_fatal: True' in a playbook to force any error to stop execution versus a whole group or serial block needing to fail usable, without breaking the ability to override in ansible -* ability to use variables from {{ }} syntax in mainline playbooks (work in progress) +* ability to use variables from {{ }} syntax in mainline playbooks, new 'when' conditional, + see examples/playbooks/upgraded_vars.yml * can set ansible_private_key_file as an inventory variable (similar to ansible_ssh_host, etc) Modules added diff --git a/examples/playbooks/upgraded_vars.yml b/examples/playbooks/upgraded_vars.yml new file mode 100644 index 00000000000..575777fdaf1 --- /dev/null +++ b/examples/playbooks/upgraded_vars.yml @@ -0,0 +1,24 @@ +# in Ansible 1.2 and later, the $foo variable syntax, which is friendly enough for simple things +# has been upgraded to allow Jinja2 substitiutions as well, which is now the preferred Syntax. +# here is an example. Note that Jinja2 conditionals belong only in templates. Use ansible conditionals +# in playbooks. + +--- + + - hosts: all + + tasks: + - shell: echo 'hello {{ ansible_hostname.upper() }}' + + - shell: echo 'match' + when: 2 == 2 + + - shell: echo 'no match' + when: 2 == 2 + 1 + + - shell: echo '{{ ansible_os_family }}' + + - shell: echo 'Centos' + when: ansible_os_family == 'RedHat' + + diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index c8cb07afead..1a2290e4b28 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -71,6 +71,8 @@ class Task(object): else: raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name)) + elif x == 'when': + ds['when'] = "jinja2_compare %s" % (ds[x]) elif x.startswith("when_"): if 'when' in ds: raise errors.AnsibleError("multiple when_* statements specified in task %s" % (ds.get('name', ds['action']))) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 08eb6742ba1..25b8982da98 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -424,6 +424,7 @@ class Runner(object): handler = utils.plugins.action_loader.get('async', self) conditional = utils.template(self.basedir, self.conditional, inject, expand_lists=False) + if not utils.check_conditional(conditional): result = utils.jsonify(dict(skipped=True)) self.callbacks.on_skipped(host, inject.get('item',None)) diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index 639680cf1a6..9f66b3f5383 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -155,13 +155,18 @@ def check_conditional(conditional): return conditional def is_set(var): - return not var.startswith("$") + return not var.startswith("$") and not '{{' in var def is_unset(var): - return var.startswith("$") + return var.startswith("$") or '{{' in var try: - return eval(conditional.replace("\n", "\\n")) + conditional = conditional.replace("\n", "\\n") + result = eval(conditional) + if result not in [ True, False ]: + raise errors.AnsibleError("Conditional expression must evaluate to True or False: %s" % conditional) + return result + except (NameError, SyntaxError): raise errors.AnsibleError("Could not evaluate the expression: " + conditional) @@ -545,6 +550,7 @@ def compile_when_to_only_if(expression): # when: int $x in $alist # when: float $x > 2 and $y <= $z # when: str $x != $y + # when: jinja2_compare asdf # implies {{ asdf }} if type(expression) not in [ str, unicode ]: raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression) @@ -562,8 +568,6 @@ def compile_when_to_only_if(expression): tcopy[i] = t return " ".join(tcopy) - - # when_failed / when_changed elif tokens[0] in [ 'failed', 'changed' ]: tcopy = tokens[1:] @@ -574,8 +578,6 @@ def compile_when_to_only_if(expression): tcopy[i] = t return " ".join(tcopy) - - # when_integer / when_float / when_string elif tokens[0] in [ 'integer', 'float', 'string' ]: cast = None @@ -602,6 +604,11 @@ def compile_when_to_only_if(expression): tcopy[i] = "(is_set('''%s''') and '''%s'''.lower() not in ('false', 'no', 'n', 'none', '0', ''))" % (t, t) return " ".join(tcopy) + # the stock 'when' without qualification (new in 1.2), assumes Jinja2 terms + elif tokens[0] == 'jinja2_compare': + # a Jinja2 evaluation that results in something Python can eval! + presented = "{% if " + " ".join(tokens[1:]).strip() + " %} True {% else %} False {% endif %}" + return presented else: raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression) diff --git a/lib/ansible/utils/template.py b/lib/ansible/utils/template.py index 86548abd9e3..b99734eaa32 100644 --- a/lib/ansible/utils/template.py +++ b/lib/ansible/utils/template.py @@ -232,7 +232,7 @@ def template(basedir, varname, vars, lookup_fatal=True, depth=0, expand_lists=Tr ''' templates a data structure by traversing it and substituting for other data structures ''' if isinstance(varname, basestring): - if '{{' in varname: + if '{{' in varname or '{%' in varname: return template_from_string(basedir, varname, vars) m = _varFind(basedir, varname, vars, lookup_fatal, depth, expand_lists) if not m: @@ -403,22 +403,20 @@ def _get_filter_plugins(): FILTER_PLUGINS.update(filters) return FILTER_PLUGINS -class J2Undefined(jinja2.runtime.Undefined): - def __str__(self): - return "{{ %s }}" % self._undefined_name - def __unicode__(self): - return "{{ %s }}" % self._undefined_name - def template_from_string(basedir, data, vars): ''' run a file through the (Jinja2) templating engine ''' - if type(data) == str: - data = unicode(data, 'utf-8') - environment = jinja2.Environment(trim_blocks=True, undefined=J2Undefined) - environment.filters.update(_get_filter_plugins()) - # TODO: may need some way of using lookup plugins here seeing we aren't calling - # the legacy engine, lookup() as a function, perhaps? - environment.template_class = J2Template - t = environment.from_string(data) - res = jinja2.utils.concat(t.root_render_func(t.new_context(_jinja2_vars(basedir, vars, t.globals), shared=True))) - return res + + try: + if type(data) == str: + data = unicode(data, 'utf-8') + environment = jinja2.Environment(trim_blocks=True) # undefined=J2Undefined) + environment.filters.update(_get_filter_plugins()) + # TODO: may need some way of using lookup plugins here seeing we aren't calling + # the legacy engine, lookup() as a function, perhaps? + environment.template_class = J2Template + t = environment.from_string(data) + res = jinja2.utils.concat(t.root_render_func(t.new_context(_jinja2_vars(basedir, vars, t.globals), shared=True))) + return res + except jinja2.exceptions.UndefinedError: + return data