diff --git a/lib/ansible/_internal/_errors/_handler.py b/lib/ansible/_internal/_errors/_handler.py index 94a391c3786..360b0981cf1 100644 --- a/lib/ansible/_internal/_errors/_handler.py +++ b/lib/ansible/_internal/_errors/_handler.py @@ -16,8 +16,8 @@ class ErrorAction(enum.Enum): """Action to take when an error is encountered.""" IGNORE = enum.auto() - WARN = enum.auto() - FAIL = enum.auto() + WARNING = enum.auto() + ERROR = enum.auto() @classmethod def from_config(cls, setting: str, variables: dict[str, t.Any] | None = None) -> t.Self: @@ -75,9 +75,9 @@ class ErrorHandler: yield except args as ex: match self.action: - case ErrorAction.WARN: + case ErrorAction.WARNING: display.error_as_warning(msg=None, exception=ex) - case ErrorAction.FAIL: + case ErrorAction.ERROR: raise case _: # ErrorAction.IGNORE pass diff --git a/lib/ansible/_internal/_templating/_jinja_bits.py b/lib/ansible/_internal/_templating/_jinja_bits.py index 4b05c8870ee..6b1a4fa2c82 100644 --- a/lib/ansible/_internal/_templating/_jinja_bits.py +++ b/lib/ansible/_internal/_templating/_jinja_bits.py @@ -985,12 +985,12 @@ def _maybe_finalize_scalar(o: t.Any) -> t.Any: match _TemplateConfig.unknown_type_conversion_handler.action: # we don't want to show the object value, and it can't be Origin-tagged; send the current template value for best effort - case ErrorAction.WARN: + case ErrorAction.WARNING: display.warning( msg=f'Type {native_type_name(o)!r} is unsupported in variable storage, converting to {native_type_name(target_type)!r}.', obj=TemplateContext.current(optional=True).template_value, ) - case ErrorAction.FAIL: + case ErrorAction.ERROR: raise AnsibleVariableTypeError.from_value(obj=TemplateContext.current(optional=True).template_value) return target_type(o) @@ -1006,12 +1006,12 @@ def _finalize_fallback_collection( ) -> t.Collection[t.Any]: match _TemplateConfig.unknown_type_conversion_handler.action: # we don't want to show the object value, and it can't be Origin-tagged; send the current template value for best effort - case ErrorAction.WARN: + case ErrorAction.WARNING: display.warning( msg=f'Type {native_type_name(o)!r} is unsupported in variable storage, converting to {native_type_name(target_type)!r}.', obj=TemplateContext.current(optional=True).template_value, ) - case ErrorAction.FAIL: + case ErrorAction.ERROR: raise AnsibleVariableTypeError.from_value(obj=TemplateContext.current(optional=True).template_value) return _finalize_collection(o, mode, finalizer, target_type) diff --git a/lib/ansible/config/base.yml b/lib/ansible/config/base.yml index b3cd67607fa..d63eac8ac34 100644 --- a/lib/ansible/config/base.yml +++ b/lib/ansible/config/base.yml @@ -9,6 +9,18 @@ _ANSIBLE_CONNECTION_PATH: - For internal use only. type: path version_added: "2.18" +_CALLBACK_DISPATCH_ERROR_BEHAVIOR: + name: Callback dispatch error behavior + default: warning + description: + - Action to take when a callback dispatch results in an error. + type: choices + choices: &basic_error + error: issue a 'fatal' error and stop the play + warning: issue a warning but continue + ignore: just continue silently + env: [ { name: _ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR } ] + version_added: '2.19' ALLOW_BROKEN_CONDITIONALS: # This config option will be deprecated once it no longer has any effect (2.23). name: Allow broken conditionals @@ -224,18 +236,6 @@ CACHE_PLUGIN_TIMEOUT: - {key: fact_caching_timeout, section: defaults} type: integer yaml: {key: facts.cache.timeout} -_CALLBACK_DISPATCH_ERROR_BEHAVIOR: - name: Callback dispatch error behavior - default: warn - description: - - Action to take when a callback dispatch results in an error. - type: choices - choices: &choices_ignore_warn_fail - - ignore - - warn - - fail - env: [ { name: _ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR } ] - version_added: '2.19' COLLECTIONS_SCAN_SYS_PATH: name: Scan PYTHONPATH for installed collections description: A boolean to enable or disable scanning the sys.path for installed collections. @@ -268,10 +268,7 @@ COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH: - When a collection is loaded that does not support the running Ansible version (with the collection metadata key `requires_ansible`). env: [{name: ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH}] ini: [{key: collections_on_ansible_version_mismatch, section: defaults}] - choices: &basic_error - error: issue a 'fatal' error and stop the play - warning: issue a warning but continue - ignore: just continue silently + choices: *basic_error default: warning COLOR_CHANGED: name: Color for 'changed' task status @@ -2058,13 +2055,13 @@ TASK_TIMEOUT: version_added: '2.10' _TEMPLAR_UNKNOWN_TYPE_CONVERSION: name: Templar unknown type conversion behavior - default: warn + default: warning description: - Action to take when an unknown type is converted for variable storage during template finalization. - This setting has no effect on the inability to store unsupported variable types as the result of templating. - Experimental diagnostic feature, subject to change. type: choices - choices: *choices_ignore_warn_fail + choices: *basic_error env: [{name: _ANSIBLE_TEMPLAR_UNKNOWN_TYPE_CONVERSION}] version_added: '2.19' _TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED: @@ -2074,7 +2071,7 @@ _TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED: - Action to take when an unknown type is encountered inside a template pipeline. - Experimental diagnostic feature, subject to change. type: choices - choices: *choices_ignore_warn_fail + choices: *basic_error env: [{name: _ANSIBLE_TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED}] version_added: '2.19' _TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR: @@ -2086,7 +2083,7 @@ _TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR: - This setting has no effect on expressions. - Experimental diagnostic feature, subject to change. type: choices - choices: *choices_ignore_warn_fail + choices: *basic_error env: [{name: _ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR}] version_added: '2.19' WORKER_SHUTDOWN_POLL_COUNT: diff --git a/test/integration/targets/adhoc/runme.sh b/test/integration/targets/adhoc/runme.sh index 5172020aa23..dd4b953f72e 100755 --- a/test/integration/targets/adhoc/runme.sh +++ b/test/integration/targets/adhoc/runme.sh @@ -10,7 +10,7 @@ ansible --task-timeout 5 localhost -m command -a '{"cmd": "whoami"}' | grep 'rc= # ensure that legacy deserializer behaves as expected on JSON CLI args (https://github.com/ansible/ansible/issues/82600) # also ensure that various templated args function (non-exhaustive) -_ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR=warn ansible '{{"localhost"}}' -m '{{"debug"}}' -a var=fromcli -e '{"fromcli":{"no_trust":{"__ansible_unsafe":"{{\"hello\"}}"},"trust":"{{ 1 }}"}}' > "${OUTPUT_DIR}/output.txt" 2>&1 +_ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR=warning ansible '{{"localhost"}}' -m '{{"debug"}}' -a var=fromcli -e '{"fromcli":{"no_trust":{"__ansible_unsafe":"{{\"hello\"}}"},"trust":"{{ 1 }}"}}' > "${OUTPUT_DIR}/output.txt" 2>&1 grep '"no_trust": "{{."hello."}}"' "${OUTPUT_DIR}/output.txt" # ensure that the template was not rendered grep '"trust": 1' "${OUTPUT_DIR}/output.txt" # ensure that the trusted template was rendered grep "Encountered untrusted template" "${OUTPUT_DIR}/output.txt" # look for the untrusted template warning text diff --git a/test/integration/targets/data_tagging_controller/runme.sh b/test/integration/targets/data_tagging_controller/runme.sh index 2f7ffd41b98..ec700458996 100755 --- a/test/integration/targets/data_tagging_controller/runme.sh +++ b/test/integration/targets/data_tagging_controller/runme.sh @@ -6,7 +6,7 @@ unset ANSIBLE_DEPRECATION_WARNINGS ansible-playbook untrusted_propagation.yml "$@" -e output_dir="${OUTPUT_DIR}" -ANSIBLE_CALLBACK_FORMAT_PRETTY=0 ANSIBLE_WRAP_STDERR=0 _ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR=warn ansible-playbook -i hosts output_tests.yml -vvv 2>&1 | tee output.txt +ANSIBLE_CALLBACK_FORMAT_PRETTY=0 ANSIBLE_WRAP_STDERR=0 _ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR=warning ansible-playbook -i hosts output_tests.yml -vvv 2>&1 | tee output.txt ../playbook_output_validator/filter.py actual_stdout.txt actual_stderr.txt < output.txt diff --git a/test/units/_internal/templating/test_templar.py b/test/units/_internal/templating/test_templar.py index 3fa756c1409..c14ef02a45e 100644 --- a/test/units/_internal/templating/test_templar.py +++ b/test/units/_internal/templating/test_templar.py @@ -98,18 +98,18 @@ class TestTemplarTemplate(BaseTemplar, unittest.TestCase): """Ensure template trust check failures default to fatal for unit tests (set in units/conftest.py)""" from ansible._internal._templating._engine import TemplateTrustCheckFailedError - assert _TemplateConfig.untrusted_template_handler.action is ErrorAction.FAIL + assert _TemplateConfig.untrusted_template_handler.action is ErrorAction.ERROR with pytest.raises(TemplateTrustCheckFailedError): self.templar.template("{{ i_am_not_trusted }}") def test_trust_fail_warning_behavior(self): - """Validate that trust checks are non-fatal when TemplateConfig.untrusted_template_handler is set to `ErrorAction.WARN`.""" + """Validate that trust checks are non-fatal when TemplateConfig.untrusted_template_handler is set to `ErrorAction.WARNING`.""" untrusted_template = "{{ i_am_not_trusted }}" assert hasattr(_TemplateConfig, 'untrusted_template_handler') - with (unittest.mock.patch.object(_TemplateConfig, 'untrusted_template_handler', ErrorHandler(ErrorAction.WARN)), + with (unittest.mock.patch.object(_TemplateConfig, 'untrusted_template_handler', ErrorHandler(ErrorAction.WARNING)), unittest.mock.patch.object(Display, 'error_as_warning', return_value=None) as mock_warning): assert self.templar.template(untrusted_template) is untrusted_template diff --git a/test/units/conftest.py b/test/units/conftest.py index 53059cf1e97..36b5a5e92d9 100644 --- a/test/units/conftest.py +++ b/test/units/conftest.py @@ -20,7 +20,7 @@ else: # Ensure unit tests fail when encountering untrusted templates to reduce mistakes in tests. # Tests that need to ignore or warn on untrusted templates will need to override this setting. - _TemplateConfig.untrusted_template_handler = ErrorHandler(ErrorAction.FAIL) + _TemplateConfig.untrusted_template_handler = ErrorHandler(ErrorAction.ERROR) from .controller_only_conftest import * # pylint: disable=wildcard-import,unused-wildcard-import diff --git a/test/units/errors/test_handler.py b/test/units/errors/test_handler.py index 3a5c5acbe9b..fdd55470328 100644 --- a/test/units/errors/test_handler.py +++ b/test/units/errors/test_handler.py @@ -49,7 +49,7 @@ def test_skippable_non_skip_exception() -> None: assert err.value is ex_to_raise -@pytest.mark.parametrize("error_action", (ErrorAction.IGNORE, ErrorAction.WARN, ErrorAction.FAIL)) +@pytest.mark.parametrize("error_action", (ErrorAction.IGNORE, ErrorAction.WARNING, ErrorAction.ERROR)) def test_skip_on_ignore_missing_skippable(error_action: ErrorAction) -> None: """Verify that a `_SkipException` is raised when `skip_on_ignore=True` and no `Skippable` context was used to suppress it.""" body_ran = False @@ -87,20 +87,20 @@ def test_ignore_passes_other_exceptions() -> None: @pytest.mark.parametrize("exception_type", (RuntimeError, NotImplementedError)) def test_warn_success(exception_type: type[Exception], mocker: pytest_mock.MockerFixture) -> None: - """Verify that `ErrorAction.WARN` eats the specified error type and calls `error_as_warning` with the exception instance raised.""" + """Verify that `ErrorAction.WARNING` eats the specified error type and calls `error_as_warning` with the exception instance raised.""" eaw = mocker.patch.object(Display(), 'error_as_warning') - with ErrorHandler(ErrorAction.WARN).handle(RuntimeError, NotImplementedError): + with ErrorHandler(ErrorAction.WARNING).handle(RuntimeError, NotImplementedError): raise exception_type() assert isinstance(eaw.call_args.kwargs['exception'], exception_type) def test_warn_passes_other_exceptions(mocker: pytest_mock.MockerFixture) -> None: - """Verify that `ErrorAction.WARN` does not suppress exception types not passed to `handle`, and that `error_as_warning` is not called for them.""" + """Verify that `ErrorAction.WARNING` does not suppress exception types not passed to `handle`, and that `error_as_warning` is not called for them.""" eaw = mocker.patch.object(Display(), 'error_as_warning') with pytest.raises(NotImplementedError): - with ErrorHandler(ErrorAction.WARN).handle(TypeError, ValueError): + with ErrorHandler(ErrorAction.WARNING).handle(TypeError, ValueError): raise NotImplementedError() assert not eaw.called @@ -108,9 +108,9 @@ def test_warn_passes_other_exceptions(mocker: pytest_mock.MockerFixture) -> None @pytest.mark.parametrize("exception_type", (AttributeError, NotImplementedError, ValueError)) def test_fail(exception_type: type[Exception]) -> None: - """Verify that `ErrorAction.FAIL` passes through all exception types, regardless of what was passed to `handle`.""" + """Verify that `ErrorAction.ERROR` passes through all exception types, regardless of what was passed to `handle`.""" with pytest.raises(exception_type): - with ErrorHandler(ErrorAction.FAIL).handle(AttributeError, NotImplementedError): + with ErrorHandler(ErrorAction.ERROR).handle(AttributeError, NotImplementedError): raise exception_type() @@ -121,7 +121,7 @@ def test_no_exceptions_to_handle(): pass -@pytest.mark.parametrize("value", ('ignore', 'warn', 'fail')) +@pytest.mark.parametrize("value", ('ignore', 'warning', 'error')) def test_from_config_env_success(value: str, mocker: pytest_mock.MockerFixture) -> None: """Verify that `from_config` correctly creates handlers with the requested error action config string.""" mocker.patch.dict(os.environ, dict(_ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR=value))