Introduce 'changed_when' keyword to override a task's changed status with the evaluation of a Jinja2 expression

This commit is contained in:
Stoned Elipot 2013-07-14 21:07:45 +02:00
parent 81940c8b11
commit eb45f07ae3
5 changed files with 75 additions and 6 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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