normalize error handler choices (#84998)

use existing to avoid deprecation cycle
normalize test too
pull/85019/head
Brian Coca 8 months ago committed by GitHub
parent e6dc17cda4
commit 2cbb721f6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

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

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

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

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

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

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

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

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

Loading…
Cancel
Save