diff --git a/changelogs/fragments/check_mode_markers.yml b/changelogs/fragments/check_mode_markers.yml new file mode 100644 index 00000000000..756bd9d35b1 --- /dev/null +++ b/changelogs/fragments/check_mode_markers.yml @@ -0,0 +1,2 @@ +minor_changes: + - Add new option to default standard out callback plugin, ``ANSIBLE_CHECK_MODE_MARKERS``, which adds check mode markers (``DRY RUN``, ``CHECK_MODE``) to the output when running in check mode. It is off by default. diff --git a/lib/ansible/plugins/callback/default.py b/lib/ansible/plugins/callback/default.py index a1648fcc9f1..98c66ab68a8 100644 --- a/lib/ansible/plugins/callback/default.py +++ b/lib/ansible/plugins/callback/default.py @@ -16,8 +16,27 @@ DOCUMENTATION = ''' - default_callback requirements: - set as stdout in configuration + options: + check_mode_markers: + name: Show markers when running in check mode + description: + - "Toggle to control displaying markers when running in check mode. The markers are C(DRY RUN) + at the beggining and ending of playbook execution (when calling C(ansible-playbook --check)) + and C(CHECK MODE) as a suffix at every play and task that is run in check mode." + type: bool + default: no + version_added: 2.9 + env: + - name: ANSIBLE_CHECK_MODE_MARKERS + ini: + - key: check_mode_markers + section: defaults ''' +# NOTE: check_mode_markers functionality is also implemented in the following derived plugins: +# debug.py, yaml.py, dense.py. Maybe their documentation needs updating, too. + + from ansible import constants as C from ansible import context from ansible.playbook.task_include import TaskInclude @@ -33,10 +52,12 @@ from ansible.utils.color import colorize, hostcolor # these are used to provide backwards compat with old plugins that subclass from default # but still don't use the new config system and/or fail to document the options +# TODO: Change the default of check_mode_markers to True in a future release (2.13) COMPAT_OPTIONS = (('display_skipped_hosts', C.DISPLAY_SKIPPED_HOSTS), ('display_ok_hosts', True), ('show_custom_stats', C.SHOW_CUSTOM_STATS), - ('display_failed_stderr', False),) + ('display_failed_stderr', False), + ('check_mode_markers', False),) class CallbackModule(CallbackBase): @@ -213,7 +234,11 @@ class CallbackModule(CallbackBase): if task_name is None: task_name = task.get_name().strip() - self._display.banner(u"%s [%s%s]" % (prefix, task_name, args)) + if task.check_mode and self.check_mode_markers: + checkmsg = " [CHECK MODE]" + else: + checkmsg = "" + self._display.banner(u"%s [%s%s]%s" % (prefix, task_name, args, checkmsg)) if self._display.verbosity >= 2: path = task.get_path() if path: @@ -233,10 +258,14 @@ class CallbackModule(CallbackBase): def v2_playbook_on_play_start(self, play): name = play.get_name().strip() - if not name: - msg = u"PLAY" + if play.check_mode and self.check_mode_markers: + checkmsg = " [CHECK MODE]" else: - msg = u"PLAY [%s]" % name + checkmsg = "" + if not name: + msg = u"PLAY%s" % checkmsg + else: + msg = u"PLAY [%s]%s" % (name, checkmsg) self._play = play @@ -378,6 +407,9 @@ class CallbackModule(CallbackBase): self._display.display('\tRUN: %s' % self._dump_results(stats.custom['_run'], indent=1).replace('\n', '')) self._display.display("", screen_only=True) + if context.CLIARGS['check'] and self.check_mode_markers: + self._display.banner("DRY RUN") + def v2_playbook_on_start(self, playbook): if self._display.verbosity > 1: from os.path import basename @@ -394,6 +426,9 @@ class CallbackModule(CallbackBase): if val: self._display.display('%s: %s' % (argument, val), color=C.COLOR_VERBOSE, screen_only=True) + if context.CLIARGS['check'] and self.check_mode_markers: + self._display.banner("DRY RUN") + def v2_runner_retry(self, result): task_name = result.task_name or result._task msg = "FAILED - RETRYING: %s (%d retries left)." % (task_name, result._result['retries'] - result._result['attempts']) diff --git a/test/integration/targets/callback_default/callback_default.out.check_markers_dry.stderr b/test/integration/targets/callback_default/callback_default.out.check_markers_dry.stderr new file mode 100644 index 00000000000..431a0200e64 --- /dev/null +++ b/test/integration/targets/callback_default/callback_default.out.check_markers_dry.stderr @@ -0,0 +1,2 @@ ++ ansible-playbook -i inventory --check test_dryrun.yml +++ set +x diff --git a/test/integration/targets/callback_default/callback_default.out.check_markers_dry.stdout b/test/integration/targets/callback_default/callback_default.out.check_markers_dry.stdout new file mode 100644 index 00000000000..8a3490977b1 --- /dev/null +++ b/test/integration/targets/callback_default/callback_default.out.check_markers_dry.stdout @@ -0,0 +1,78 @@ + +DRY RUN ************************************************************************ + +PLAY [A common play] [CHECK MODE] ********************************************** + +TASK [debug] [CHECK MODE] ****************************************************** +ok: [testhost] => { + "msg": "ansible_check_mode: True" +} + +TASK [Command] [CHECK MODE] **************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY [Play with check_mode: true (runs always in check_mode)] [CHECK MODE] ***** + +TASK [debug] [CHECK MODE] ****************************************************** +ok: [testhost] => { + "msg": "ansible_check_mode: True" +} + +TASK [Command] [CHECK MODE] **************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY [Play with check_mode: false (runs always in wet mode)] ******************* + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: True" +} + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY [Play with a block with check_mode: true] [CHECK MODE] ******************** + +TASK [Command] [CHECK MODE] **************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY [Play with a block with check_mode: false] [CHECK MODE] ******************* + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY RECAP ********************************************************************* +testhost : ok=10 changed=7 unreachable=0 failed=0 skipped=8 rescued=0 ignored=0 + + +DRY RUN ************************************************************************ diff --git a/test/integration/targets/callback_default/callback_default.out.check_markers_wet.stderr b/test/integration/targets/callback_default/callback_default.out.check_markers_wet.stderr new file mode 100644 index 00000000000..e4309428ac9 --- /dev/null +++ b/test/integration/targets/callback_default/callback_default.out.check_markers_wet.stderr @@ -0,0 +1,2 @@ ++ ansible-playbook -i inventory test_dryrun.yml +++ set +x diff --git a/test/integration/targets/callback_default/callback_default.out.check_markers_wet.stdout b/test/integration/targets/callback_default/callback_default.out.check_markers_wet.stdout new file mode 100644 index 00000000000..f5f45105afb --- /dev/null +++ b/test/integration/targets/callback_default/callback_default.out.check_markers_wet.stdout @@ -0,0 +1,74 @@ + +PLAY [A common play] *********************************************************** + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: False" +} + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY [Play with check_mode: true (runs always in check_mode)] [CHECK MODE] ***** + +TASK [debug] [CHECK MODE] ****************************************************** +ok: [testhost] => { + "msg": "ansible_check_mode: False" +} + +TASK [Command] [CHECK MODE] **************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY [Play with check_mode: false (runs always in wet mode)] ******************* + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: False" +} + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY [Play with a block with check_mode: true] ********************************* + +TASK [Command] [CHECK MODE] **************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY [Play with a block with check_mode: false] ******************************** + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] [CHECK MODE] ****************************** +skipping: [testhost] + +PLAY RECAP ********************************************************************* +testhost : ok=11 changed=8 unreachable=0 failed=0 skipped=7 rescued=0 ignored=0 + diff --git a/test/integration/targets/callback_default/callback_default.out.check_nomarkers_dry.stderr b/test/integration/targets/callback_default/callback_default.out.check_nomarkers_dry.stderr new file mode 100644 index 00000000000..431a0200e64 --- /dev/null +++ b/test/integration/targets/callback_default/callback_default.out.check_nomarkers_dry.stderr @@ -0,0 +1,2 @@ ++ ansible-playbook -i inventory --check test_dryrun.yml +++ set +x diff --git a/test/integration/targets/callback_default/callback_default.out.check_nomarkers_dry.stdout b/test/integration/targets/callback_default/callback_default.out.check_nomarkers_dry.stdout new file mode 100644 index 00000000000..e984d499dfa --- /dev/null +++ b/test/integration/targets/callback_default/callback_default.out.check_nomarkers_dry.stdout @@ -0,0 +1,74 @@ + +PLAY [A common play] *********************************************************** + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: True" +} + +TASK [Command] ***************************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY [Play with check_mode: true (runs always in check_mode)] ****************** + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: True" +} + +TASK [Command] ***************************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY [Play with check_mode: false (runs always in wet mode)] ******************* + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: True" +} + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY [Play with a block with check_mode: true] ********************************* + +TASK [Command] ***************************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY [Play with a block with check_mode: false] ******************************** + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY RECAP ********************************************************************* +testhost : ok=10 changed=7 unreachable=0 failed=0 skipped=8 rescued=0 ignored=0 + diff --git a/test/integration/targets/callback_default/callback_default.out.check_nomarkers_wet.stderr b/test/integration/targets/callback_default/callback_default.out.check_nomarkers_wet.stderr new file mode 100644 index 00000000000..e4309428ac9 --- /dev/null +++ b/test/integration/targets/callback_default/callback_default.out.check_nomarkers_wet.stderr @@ -0,0 +1,2 @@ ++ ansible-playbook -i inventory test_dryrun.yml +++ set +x diff --git a/test/integration/targets/callback_default/callback_default.out.check_nomarkers_wet.stdout b/test/integration/targets/callback_default/callback_default.out.check_nomarkers_wet.stdout new file mode 100644 index 00000000000..2b331bb8e53 --- /dev/null +++ b/test/integration/targets/callback_default/callback_default.out.check_nomarkers_wet.stdout @@ -0,0 +1,74 @@ + +PLAY [A common play] *********************************************************** + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: False" +} + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY [Play with check_mode: true (runs always in check_mode)] ****************** + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: False" +} + +TASK [Command] ***************************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY [Play with check_mode: false (runs always in wet mode)] ******************* + +TASK [debug] ******************************************************************* +ok: [testhost] => { + "msg": "ansible_check_mode: False" +} + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY [Play with a block with check_mode: true] ********************************* + +TASK [Command] ***************************************************************** +skipping: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY [Play with a block with check_mode: false] ******************************** + +TASK [Command] ***************************************************************** +changed: [testhost] + +TASK [Command with check_mode: false] ****************************************** +changed: [testhost] + +TASK [Command with check_mode: true] ******************************************* +skipping: [testhost] + +PLAY RECAP ********************************************************************* +testhost : ok=11 changed=8 unreachable=0 failed=0 skipped=7 rescued=0 ignored=0 + diff --git a/test/integration/targets/callback_default/runme.sh b/test/integration/targets/callback_default/runme.sh index f52b4367af4..d394c7c5613 100755 --- a/test/integration/targets/callback_default/runme.sh +++ b/test/integration/targets/callback_default/runme.sh @@ -28,6 +28,26 @@ run_test() { diff -u "${ORIGFILE}.${testname}.stderr" "${OUTFILE}.${testname}.stderr" || diff_failure } +run_test_dryrun() { + local testname=$1 + # optional, pass --check to run a dry run + local chk=${2:-} + + # This needed to satisfy shellcheck that can not accept unquoted variable + cmd="ansible-playbook -i inventory ${chk} test_dryrun.yml" + + # The shenanigans with redirection and 'tee' are to capture STDOUT and + # STDERR separately while still displaying both to the console + { $cmd \ + > >(set +x; tee "${OUTFILE}.${testname}.stdout"); } \ + 2> >(set +x; tee "${OUTFILE}.${testname}.stderr" >&2) + # Scrub deprication warning that shows up in Python 2.6 on CentOS 6 + sed -i -e '/RandomPool_DeprecationWarning/d' "${OUTFILE}.${testname}.stderr" + + diff -u "${ORIGFILE}.${testname}.stdout" "${OUTFILE}.${testname}.stdout" || diff_failure + diff -u "${ORIGFILE}.${testname}.stderr" "${OUTFILE}.${testname}.stderr" || diff_failure +} + diff_failure() { if [[ $INIT = 0 ]]; then echo "FAILURE...diff mismatch!" @@ -41,11 +61,11 @@ cleanup() { fi if [[ -f "${BASEFILE}.unreachable.stdout" ]]; then - rm -rf "${BASEFILE}.unreachable.stdout" + rm -rf "${BASEFILE}.unreachable.stdout" fi if [[ -f "${BASEFILE}.unreachable.stderr" ]]; then - rm -rf "${BASEFILE}.unreachable.stderr" + rm -rf "${BASEFILE}.unreachable.stderr" fi # Restore TTY cols @@ -91,6 +111,7 @@ export ANSIBLE_NOCOLOR=1 export ANSIBLE_DISPLAY_SKIPPED_HOSTS=1 export ANSIBLE_DISPLAY_OK_HOSTS=1 export ANSIBLE_DISPLAY_FAILED_STDERR=0 +export ANSIBLE_CHECK_MODE_MARKERS=0 run_test default @@ -128,6 +149,30 @@ set +e ansible-playbook -i inventory test_2.yml > >(set +x; tee "${BASEFILE}.unreachable.stdout";) 2> >(set +x; tee "${BASEFILE}.unreachable.stderr" >&2) || true set -e if test "$(grep -c 'UNREACHABLE' "${BASEFILE}.unreachable.stderr")" -ne 1; then - echo "Test failed" - exit 1 + echo "Test failed" + exit 1 fi + +## DRY RUN tests +# +# Default settings with dry run tasks +export ANSIBLE_DISPLAY_SKIPPED_HOSTS=1 +export ANSIBLE_DISPLAY_OK_HOSTS=1 +export ANSIBLE_DISPLAY_FAILED_STDERR=1 +# Enable Check mode markers +export ANSIBLE_CHECK_MODE_MARKERS=1 + +# Test the wet run with check markers +run_test_dryrun check_markers_wet + +# Test the dry run with check markers +run_test_dryrun check_markers_dry --check + +# Disable Check mode markers +export ANSIBLE_CHECK_MODE_MARKERS=0 + +# Test the wet run without check markers +run_test_dryrun check_nomarkers_wet + +# Test the dry run without check markers +run_test_dryrun check_nomarkers_dry --check diff --git a/test/integration/targets/callback_default/test_dryrun.yml b/test/integration/targets/callback_default/test_dryrun.yml new file mode 100644 index 00000000000..26cf08316a2 --- /dev/null +++ b/test/integration/targets/callback_default/test_dryrun.yml @@ -0,0 +1,93 @@ +--- +- name: A common play + hosts: testhost + gather_facts: no + tasks: + - debug: + msg: 'ansible_check_mode: {{ansible_check_mode}}' + + - name: Command + command: ls -l + + - name: "Command with check_mode: false" + command: ls -l + check_mode: false + + - name: "Command with check_mode: true" + command: ls -l + check_mode: true + + +- name: "Play with check_mode: true (runs always in check_mode)" + hosts: testhost + gather_facts: no + check_mode: true + tasks: + - debug: + msg: 'ansible_check_mode: {{ansible_check_mode}}' + + - name: Command + command: ls -l + + - name: "Command with check_mode: false" + command: ls -l + check_mode: false + + - name: "Command with check_mode: true" + command: ls -l + check_mode: true + + +- name: "Play with check_mode: false (runs always in wet mode)" + hosts: testhost + gather_facts: no + check_mode: false + tasks: + - debug: + msg: 'ansible_check_mode: {{ansible_check_mode}}' + + - name: Command + command: ls -l + + - name: "Command with check_mode: false" + command: ls -l + check_mode: false + + - name: "Command with check_mode: true" + command: ls -l + check_mode: true + + +- name: "Play with a block with check_mode: true" + hosts: testhost + gather_facts: no + tasks: + - block: + - name: Command + command: ls -l + + - name: "Command with check_mode: false" + command: ls -l + check_mode: false + + - name: "Command with check_mode: true" + command: ls -l + check_mode: true + check_mode: true + +- name: "Play with a block with check_mode: false" + hosts: testhost + gather_facts: no + tasks: + - block: + - name: Command + command: ls -l + + - name: "Command with check_mode: false" + command: ls -l + check_mode: false + + - name: "Command with check_mode: true" + command: ls -l + check_mode: true + check_mode: false