From eb45f07ae3a64a8ff675bd0846d156522c1e14fc Mon Sep 17 00:00:00 2001 From: Stoned Elipot Date: Sun, 14 Jul 2013 21:07:45 +0200 Subject: [PATCH] Introduce 'changed_when' keyword to override a task's changed status with the evaluation of a Jinja2 expression --- lib/ansible/playbook/__init__.py | 4 ++-- lib/ansible/playbook/task.py | 14 ++++++++++---- lib/ansible/runner/__init__.py | 10 ++++++++++ test/TestPlayBook.py | 28 ++++++++++++++++++++++++++++ test/playbook-changed_when.yml | 25 +++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 test/playbook-changed_when.yml diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index f554bd3e319..cfaaa7cb4ec 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -351,7 +351,7 @@ class PlayBook(object): # extra vars need to always trump - so update again following the facts self.SETUP_CACHE[host].update(self.extra_vars) if task.register: - if 'stdout' in result: + if 'stdout' in result and 'stdout_lines' not in result: result['stdout_lines'] = result['stdout'].splitlines() self.SETUP_CACHE[host][task.register] = result @@ -359,7 +359,7 @@ class PlayBook(object): if task.ignore_errors and task.register: failed = results.get('failed', {}) for host, result in failed.iteritems(): - if 'stdout' in result: + if 'stdout' in result and 'stdout_lines' not in result: result['stdout_lines'] = result['stdout'].splitlines() self.SETUP_CACHE[host][task.register] = result diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index 7a34601c7e6..1566e77e907 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -29,7 +29,7 @@ class Task(object): 'delegate_to', 'first_available_file', 'ignore_errors', 'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass', 'items_lookup_plugin', 'items_lookup_terms', 'environment', 'args', - 'any_errors_fatal' + 'any_errors_fatal', 'changed_when' ] # to prevent typos and such @@ -38,7 +38,7 @@ class Task(object): 'first_available_file', 'include', 'tags', 'register', 'ignore_errors', 'delegate_to', 'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass', 'when', 'connection', 'environment', 'args', - 'any_errors_fatal' + 'any_errors_fatal', 'changed_when' ] def __init__(self, play, ds, module_vars=None, additional_conditions=None): @@ -88,8 +88,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 in [ 'changed_when', 'when']: + ds[x] = "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']))) @@ -161,6 +161,10 @@ class Task(object): # load various attributes self.only_if = ds.get('only_if', 'True') self.when = ds.get('when', None) + self.changed_when = ds.get('changed_when', None) + + if self.changed_when is not None: + self.changed_when = utils.compile_when_to_only_if(self.changed_when) self.async_seconds = int(ds.get('async', 0)) # not async by default self.async_poll_interval = int(ds.get('poll', 10)) # default poll = 10 seconds @@ -214,6 +218,8 @@ class Task(object): # make ignore_errors accessable to Runner code self.module_vars['ignore_errors'] = self.ignore_errors + self.module_vars['register'] = self.register + self.module_vars['changed_when'] = self.changed_when # tags allow certain parts of a playbook to be run without running the whole playbook apply_tags = ds.get('tags', None) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 914e87e21c8..a2bc7373894 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -630,6 +630,16 @@ class Runner(object): module_name=module_name ) + changed_when = self.module_vars.get('changed_when') + if changed_when is not None: + register = self.module_vars.get('register') + if register is not None: + if 'stdout' in data: + data['stdout_lines'] = data['stdout'].splitlines() + inject[register] = data + changed = template.template(self.basedir, changed_when, inject, fail_on_undefined=self.error_on_undefined_vars) + data['changed'] = utils.check_conditional(changed) + if is_chained: # no callbacks return result diff --git a/test/TestPlayBook.py b/test/TestPlayBook.py index 5b44302af09..6f005520760 100644 --- a/test/TestPlayBook.py +++ b/test/TestPlayBook.py @@ -382,6 +382,34 @@ class TestPlaybook(unittest.TestCase): assert utils.jsonify(expected, format=True) == utils.jsonify(actual,format=True) + def test_playbook_changed_when(self): + test_callbacks = TestCallbacks() + playbook = ansible.playbook.PlayBook( + playbook=os.path.join(self.test_dir, 'playbook-changed_when.yml'), + host_list='test/ansible_hosts', + stats=ans_callbacks.AggregateStats(), + callbacks=test_callbacks, + runner_callbacks=test_callbacks + ) + actual = playbook.run() + + # if different, this will output to screen + print "**ACTUAL**" + print utils.jsonify(actual, format=True) + expected = { + "localhost": { + "changed": 3, + "failures": 0, + "ok": 6, + "skipped": 0, + "unreachable": 0 + } + } + print "**EXPECTED**" + print utils.jsonify(expected, format=True) + + assert utils.jsonify(expected, format=True) == utils.jsonify(actual,format=True) + def _compare_file_output(self, filename, expected_lines): actual_lines = [] with open(filename) as f: diff --git a/test/playbook-changed_when.yml b/test/playbook-changed_when.yml new file mode 100644 index 00000000000..9625d5d6551 --- /dev/null +++ b/test/playbook-changed_when.yml @@ -0,0 +1,25 @@ +--- +- hosts: all + connection: local + gather_facts: False + + tasks: + - action: command echo first action + - action: command echo second action + register: var + changed_when: "'X' in var.stdout" + - action: shell exit 2 + register: exit + ignore_errors: yes + changed_when: "exit.rc < 1" + - action: command echo third action + changed_when: false + - action: file path=/ state=directory + changed_when: true + - action: command echo {{item}} + register: out + changed_when: "'e' in out.stdout" + with_items: + - hello + - foo + - bye