Add condition that causes a when to skip a task to output msg (#78918)

* Add condition that causes a when to skip a task

* Fix up tests

* Use false_condition instead of failed_condition

* Remove formatting accidentially added

* Fix sanity
pull/80011/head
Jordan Borean 2 years ago committed by GitHub
parent bd329dc543
commit 1e6b8249e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
minor_changes:
- Added the conditional that was False if ``when`` caused a task to skip under ``false_condition``.

@ -450,9 +450,11 @@ class TaskExecutor:
# the fact that the conditional may specify that the task be skipped due to a # the fact that the conditional may specify that the task be skipped due to a
# variable not being present which would otherwise cause validation to fail # variable not being present which would otherwise cause validation to fail
try: try:
if not self._task.evaluate_conditional(templar, tempvars): conditional_result, false_condition = self._task.evaluate_conditional_with_result(templar, tempvars)
if not conditional_result:
display.debug("when evaluation is False, skipping this task") display.debug("when evaluation is False, skipping this task")
return dict(changed=False, skipped=True, skip_reason='Conditional result was False', _ansible_no_log=no_log) return dict(changed=False, skipped=True, skip_reason='Conditional result was False',
false_condition=false_condition, _ansible_no_log=no_log)
except AnsibleError as e: except AnsibleError as e:
# loop error takes precedence # loop error takes precedence
if self._loop_eval_error is not None: if self._loop_eval_error is not None:

@ -20,6 +20,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import ast import ast
import typing as t
from jinja2.compiler import generate from jinja2.compiler import generate
from jinja2.exceptions import UndefinedError from jinja2.exceptions import UndefinedError
@ -28,6 +29,7 @@ from ansible.errors import AnsibleError, AnsibleUndefinedVariable
from ansible.module_utils.six import text_type from ansible.module_utils.six import text_type
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
from ansible.playbook.attribute import FieldAttribute from ansible.playbook.attribute import FieldAttribute
from ansible.template import Templar
from ansible.utils.display import Display from ansible.utils.display import Display
display = Display() display = Display()
@ -57,11 +59,19 @@ class Conditional:
if not isinstance(value, list): if not isinstance(value, list):
setattr(self, name, [value]) setattr(self, name, [value])
def evaluate_conditional(self, templar, all_vars): def evaluate_conditional(self, templar: Templar, all_vars: dict[str, t.Any]) -> bool:
''' '''
Loops through the conditionals set on this object, returning Loops through the conditionals set on this object, returning
False if any of them evaluate as such. False if any of them evaluate as such.
''' '''
return self.evaluate_conditional_with_result(templar, all_vars)[0]
def evaluate_conditional_with_result(self, templar: Templar, all_vars: dict[str, t.Any]) -> tuple[bool, t.Optional[str]]:
"""
Loops through the conditionals set on this object, returning
False if any of them evaluate as such as well as the condition
that was false.
"""
# since this is a mix-in, it may not have an underlying datastructure # since this is a mix-in, it may not have an underlying datastructure
# associated with it, so we pull it out now in case we need it for # associated with it, so we pull it out now in case we need it for
@ -71,6 +81,7 @@ class Conditional:
ds = getattr(self, '_ds') ds = getattr(self, '_ds')
result = True result = True
false_condition: t.Optional[str] = None
try: try:
for conditional in self.when: for conditional in self.when:
@ -88,12 +99,13 @@ class Conditional:
display.debug("Evaluated conditional (%s): %s" % (conditional, res)) display.debug("Evaluated conditional (%s): %s" % (conditional, res))
if not result: if not result:
false_condition = conditional
break break
except Exception as e: except Exception as e:
raise AnsibleError("The conditional check '%s' failed. The error was: %s" % (to_native(conditional), to_native(e)), obj=ds) raise AnsibleError("The conditional check '%s' failed. The error was: %s" % (to_native(conditional), to_native(e)), obj=ds)
return result return result, false_condition
def _check_conditional(self, conditional, templar, all_vars): def _check_conditional(self, conditional, templar, all_vars):
''' '''

@ -43,6 +43,7 @@ fatal: [testhost]: FAILED! =>
TASK [Skipped task] ************************************************************ TASK [Skipped task] ************************************************************
skipping: [testhost] => skipping: [testhost] =>
changed: false changed: false
false_condition: false
skip_reason: Conditional result was False skip_reason: Conditional result was False
TASK [Task with var in name (foo bar)] ***************************************** TASK [Task with var in name (foo bar)] *****************************************
@ -120,6 +121,7 @@ ok: [testhost] => (item=debug-3) =>
msg: debug-3 msg: debug-3
skipping: [testhost] => (item=debug-4) => skipping: [testhost] => (item=debug-4) =>
ansible_loop_var: item ansible_loop_var: item
false_condition: item != 4
item: 4 item: 4
fatal: [testhost]: FAILED! => fatal: [testhost]: FAILED! =>
msg: One or more items failed msg: One or more items failed
@ -199,9 +201,11 @@ skipping: [testhost] =>
TASK [debug] ******************************************************************* TASK [debug] *******************************************************************
skipping: [testhost] => (item=1) => skipping: [testhost] => (item=1) =>
ansible_loop_var: item ansible_loop_var: item
false_condition: false
item: 1 item: 1
skipping: [testhost] => (item=2) => skipping: [testhost] => (item=2) =>
ansible_loop_var: item ansible_loop_var: item
false_condition: false
item: 2 item: 2
skipping: [testhost] => skipping: [testhost] =>
msg: All items skipped msg: All items skipped

@ -45,6 +45,7 @@ fatal: [testhost]: FAILED! =>
TASK [Skipped task] ************************************************************ TASK [Skipped task] ************************************************************
skipping: [testhost] => skipping: [testhost] =>
changed: false changed: false
false_condition: false
skip_reason: Conditional result was False skip_reason: Conditional result was False
TASK [Task with var in name (foo bar)] ***************************************** TASK [Task with var in name (foo bar)] *****************************************
@ -126,6 +127,7 @@ ok: [testhost] => (item=debug-3) =>
msg: debug-3 msg: debug-3
skipping: [testhost] => (item=debug-4) => skipping: [testhost] => (item=debug-4) =>
ansible_loop_var: item ansible_loop_var: item
false_condition: item != 4
item: 4 item: 4
fatal: [testhost]: FAILED! => fatal: [testhost]: FAILED! =>
msg: One or more items failed msg: One or more items failed
@ -206,9 +208,11 @@ skipping: [testhost] =>
TASK [debug] ******************************************************************* TASK [debug] *******************************************************************
skipping: [testhost] => (item=1) => skipping: [testhost] => (item=1) =>
ansible_loop_var: item ansible_loop_var: item
false_condition: false
item: 1 item: 1
skipping: [testhost] => (item=2) => skipping: [testhost] => (item=2) =>
ansible_loop_var: item ansible_loop_var: item
false_condition: false
item: 2 item: 2
skipping: [testhost] => skipping: [testhost] =>
msg: All items skipped msg: All items skipped

@ -5,7 +5,7 @@ set -eux
# This test expects 7 loggable vars and 0 non-loggable ones. # This test expects 7 loggable vars and 0 non-loggable ones.
# If either mismatches it fails, run the ansible-playbook command to debug. # If either mismatches it fails, run the ansible-playbook command to debug.
[ "$(ansible-playbook no_log_local.yml -i ../../inventory -vvvvv "$@" | awk \ [ "$(ansible-playbook no_log_local.yml -i ../../inventory -vvvvv "$@" | awk \
'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "26/0" ] 'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "27/0" ]
# deal with corner cases with no log and loops # deal with corner cases with no log and loops
# no log enabled, should produce 6 censored messages # no log enabled, should produce 6 censored messages

@ -126,6 +126,16 @@
hello: world hello: world
register: executed_task register: executed_task
- name: Skip me with multiple conditions
set_fact:
hello: world
when:
- True == True
- foo == 'bar'
vars:
foo: foo
register: skipped_task_multi_condition
- name: Try skipped test on non-dictionary - name: Try skipped test on non-dictionary
set_fact: set_fact:
hello: "{{ 'nope' is skipped }}" hello: "{{ 'nope' is skipped }}"
@ -136,8 +146,11 @@
assert: assert:
that: that:
- skipped_task is skipped - skipped_task is skipped
- skipped_task.false_condition == False
- executed_task is not skipped - executed_task is not skipped
- misuse_of_skipped is failure - misuse_of_skipped is failure
- skipped_task_multi_condition is skipped
- skipped_task_multi_condition.false_condition == "foo == 'bar'"
- name: Not an async task - name: Not an async task
set_fact: set_fact:

@ -329,6 +329,7 @@ class TestTaskExecutor(unittest.TestCase):
# other reason is that if I specify 0 here, the test fails. ;) # other reason is that if I specify 0 here, the test fails. ;)
mock_task.async_val = 1 mock_task.async_val = 1
mock_task.poll = 0 mock_task.poll = 0
mock_task.evaluate_conditional_with_result.return_value = (True, None)
mock_play_context = MagicMock() mock_play_context = MagicMock()
mock_play_context.post_validate.return_value = None mock_play_context.post_validate.return_value = None

Loading…
Cancel
Save