mirror of
https://github.com/zebrajr/ansible.git
synced 2025-12-06 00:19:48 +01:00
Add missing warning methods and args (#85225)
This commit is contained in:
parent
cbcefc53a3
commit
eafe5fc739
5
changelogs/fragments/module_utils_warnings.yml
Normal file
5
changelogs/fragments/module_utils_warnings.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
minor_changes:
|
||||||
|
- module_utils - Add optional ``help_text`` argument to ``AnsibleModule.warn``.
|
||||||
|
- module_utils - Add ``AnsibleModule.error_as_warning``.
|
||||||
|
- module_utils - Add ``ansible.module_utils.common.warnings.error_as_warning``.
|
||||||
|
- display - Add ``help_text`` and ``obj`` to ``Display.error_as_warning``.
|
||||||
|
|
@ -12,7 +12,7 @@ from ansible.errors import AnsibleError
|
||||||
|
|
||||||
def unmask(value: object, type_names: str | list[str]) -> object:
|
def unmask(value: object, type_names: str | list[str]) -> object:
|
||||||
"""
|
"""
|
||||||
Internal filter to suppress automatic type transformation in Jinja (e.g., WarningMessageDetail, DeprecationMessageDetail, ErrorDetail).
|
Internal filter to suppress automatic type transformation in Jinja (e.g., WarningSummary, DeprecationSummary, ErrorSummary).
|
||||||
Lazy collection caching is in play - the first attempt to access a value in a given lazy container must be with unmasking in place, or the transformed value
|
Lazy collection caching is in play - the first attempt to access a value in a given lazy container must be with unmasking in place, or the transformed value
|
||||||
will already be cached.
|
will already be cached.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -806,7 +806,7 @@ class TaskExecutor:
|
||||||
if isinstance(deprecations, list):
|
if isinstance(deprecations, list):
|
||||||
for deprecation in deprecations:
|
for deprecation in deprecations:
|
||||||
if not isinstance(deprecation, _messages.DeprecationSummary):
|
if not isinstance(deprecation, _messages.DeprecationSummary):
|
||||||
# translate non-DeprecationMessageDetail message dicts
|
# translate non-DeprecationSummary message dicts
|
||||||
try:
|
try:
|
||||||
if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
|
if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
|
||||||
# deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
|
# deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ from . import _messages
|
||||||
MSG_REASON_DIRECT_CAUSE: _t.Final[str] = '<<< caused by >>>'
|
MSG_REASON_DIRECT_CAUSE: _t.Final[str] = '<<< caused by >>>'
|
||||||
MSG_REASON_HANDLING_CAUSE: _t.Final[str] = '<<< while handling >>>'
|
MSG_REASON_HANDLING_CAUSE: _t.Final[str] = '<<< while handling >>>'
|
||||||
|
|
||||||
|
TRACEBACK_REASON_EXCEPTION_DIRECT_WARNING: _t.Final[str] = 'The above exception was the direct cause of the following warning:'
|
||||||
|
|
||||||
|
|
||||||
class EventFactory:
|
class EventFactory:
|
||||||
"""Factory for creating `Event` instances from `BaseException` instances on targets."""
|
"""Factory for creating `Event` instances from `BaseException` instances on targets."""
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@ from ansible.module_utils.common._utils import get_all_subclasses as _get_all_su
|
||||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS, BOOLEANS_FALSE, BOOLEANS_TRUE, boolean
|
from ansible.module_utils.parsing.convert_bool import BOOLEANS, BOOLEANS_FALSE, BOOLEANS_TRUE, boolean
|
||||||
from ansible.module_utils.common.warnings import (
|
from ansible.module_utils.common.warnings import (
|
||||||
deprecate,
|
deprecate,
|
||||||
|
error_as_warning,
|
||||||
get_deprecations,
|
get_deprecations,
|
||||||
get_warnings,
|
get_warnings,
|
||||||
warn,
|
warn,
|
||||||
|
|
@ -504,9 +505,34 @@ class AnsibleModule(object):
|
||||||
|
|
||||||
return self._tmpdir
|
return self._tmpdir
|
||||||
|
|
||||||
def warn(self, warning):
|
def warn(
|
||||||
warn(warning)
|
self,
|
||||||
self.log('[WARNING] %s' % warning)
|
warning: str,
|
||||||
|
*,
|
||||||
|
help_text: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
_skip_stackwalk = True
|
||||||
|
|
||||||
|
warn(
|
||||||
|
warning=warning,
|
||||||
|
help_text=help_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
def error_as_warning(
|
||||||
|
self,
|
||||||
|
msg: str | None,
|
||||||
|
exception: BaseException,
|
||||||
|
*,
|
||||||
|
help_text: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Display an exception as a warning."""
|
||||||
|
_skip_stackwalk = True
|
||||||
|
|
||||||
|
error_as_warning(
|
||||||
|
msg=msg,
|
||||||
|
exception=exception,
|
||||||
|
help_text=help_text,
|
||||||
|
)
|
||||||
|
|
||||||
def deprecate(
|
def deprecate(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from __future__ import annotations as _annotations
|
||||||
|
|
||||||
import typing as _t
|
import typing as _t
|
||||||
|
|
||||||
from ansible.module_utils._internal import _traceback, _deprecator, _event_utils, _messages
|
from ansible.module_utils._internal import _traceback, _deprecator, _event_utils, _messages, _errors
|
||||||
from ansible.module_utils import _internal
|
from ansible.module_utils import _internal
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -40,6 +40,45 @@ def warn(
|
||||||
_global_warnings[warning] = None
|
_global_warnings[warning] = None
|
||||||
|
|
||||||
|
|
||||||
|
def error_as_warning(
|
||||||
|
msg: str | None,
|
||||||
|
exception: BaseException,
|
||||||
|
*,
|
||||||
|
help_text: str | None = None,
|
||||||
|
obj: object = None,
|
||||||
|
) -> None:
|
||||||
|
"""Display an exception as a warning."""
|
||||||
|
_skip_stackwalk = True
|
||||||
|
|
||||||
|
if _internal.is_controller:
|
||||||
|
_display = _internal.import_controller_module('ansible.utils.display').Display()
|
||||||
|
_display.error_as_warning(
|
||||||
|
msg=msg,
|
||||||
|
exception=exception,
|
||||||
|
help_text=help_text,
|
||||||
|
obj=obj,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
event = _errors.EventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING))
|
||||||
|
|
||||||
|
warning = _messages.WarningSummary(
|
||||||
|
event=_messages.Event(
|
||||||
|
msg=msg,
|
||||||
|
help_text=help_text,
|
||||||
|
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
|
||||||
|
chain=_messages.EventChain(
|
||||||
|
msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
|
||||||
|
traceback_reason=_errors.TRACEBACK_REASON_EXCEPTION_DIRECT_WARNING,
|
||||||
|
event=event,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
_global_warnings[warning] = None
|
||||||
|
|
||||||
|
|
||||||
def deprecate(
|
def deprecate(
|
||||||
msg: str,
|
msg: str,
|
||||||
version: str | None = None,
|
version: str | None = None,
|
||||||
|
|
|
||||||
|
|
@ -903,7 +903,7 @@ class Display(metaclass=Singleton):
|
||||||
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
|
formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
|
||||||
chain=_messages.EventChain(
|
chain=_messages.EventChain(
|
||||||
msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
|
msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
|
||||||
traceback_reason="The above exception was the direct cause of the following warning:",
|
traceback_reason=_errors.TRACEBACK_REASON_EXCEPTION_DIRECT_WARNING,
|
||||||
event=event,
|
event=event,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ else:
|
||||||
from .controller_only_conftest import * # pylint: disable=wildcard-import,unused-wildcard-import
|
from .controller_only_conftest import * # pylint: disable=wildcard-import,unused-wildcard-import
|
||||||
|
|
||||||
from ansible.module_utils import _internal as _module_utils_internal
|
from ansible.module_utils import _internal as _module_utils_internal
|
||||||
|
from ansible.module_utils._internal import _traceback as _module_utils_internal_traceback
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config: pytest.Config):
|
def pytest_configure(config: pytest.Config):
|
||||||
|
|
@ -83,3 +84,4 @@ def pytest_collection_finish(session: pytest.Session):
|
||||||
def as_target(mocker: pytest_mock.MockerFixture) -> None:
|
def as_target(mocker: pytest_mock.MockerFixture) -> None:
|
||||||
"""Force execution in the context of a target host instead of the controller."""
|
"""Force execution in the context of a target host instead of the controller."""
|
||||||
mocker.patch.object(_module_utils_internal, 'is_controller', False)
|
mocker.patch.object(_module_utils_internal, 'is_controller', False)
|
||||||
|
mocker.patch.object(_module_utils_internal_traceback, '_is_traceback_enabled', _module_utils_internal_traceback._is_module_traceback_enabled)
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,43 @@ import typing as t
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ansible.module_utils._internal import _traceback
|
from ansible.module_utils._internal import _traceback, _messages, _deprecator
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.common import warnings
|
from ansible.module_utils.common import warnings
|
||||||
from ansible.module_utils.common.warnings import deprecate
|
from ansible.module_utils.common.warnings import deprecate
|
||||||
|
from ansible.module_utils.testing import patch_module_args
|
||||||
from units.mock.module import ModuleEnvMocker
|
from units.mock.module import ModuleEnvMocker
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("module_env_mocker")
|
pytestmark = pytest.mark.usefixtures("as_target", "module_env_mocker")
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecate() -> None:
|
||||||
|
deprecate('Deprecation message')
|
||||||
|
assert warnings.get_deprecation_messages() == (dict(msg='Deprecation message', collection_name=None, version=None),)
|
||||||
|
assert warnings.get_deprecations() == [_messages.DeprecationSummary(
|
||||||
|
event=_messages.Event(msg='Deprecation message'),
|
||||||
|
deprecator=_deprecator.INDETERMINATE_DEPRECATOR,
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecate_via_module() -> None:
|
||||||
|
with patch_module_args():
|
||||||
|
am = AnsibleModule(argument_spec={})
|
||||||
|
|
||||||
|
am.deprecate('Deprecation message')
|
||||||
|
|
||||||
|
assert warnings.get_deprecation_messages() == (dict(msg='Deprecation message', collection_name=None, version=None),)
|
||||||
|
assert warnings.get_deprecations() == [_messages.DeprecationSummary(
|
||||||
|
event=_messages.Event(msg='Deprecation message'),
|
||||||
|
deprecator=_deprecator.INDETERMINATE_DEPRECATOR,
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
def test_dedupe_with_traceback(module_env_mocker: ModuleEnvMocker) -> None:
|
def test_dedupe_with_traceback(module_env_mocker: ModuleEnvMocker) -> None:
|
||||||
module_env_mocker.set_traceback_config([_traceback.TracebackEvent.DEPRECATED])
|
module_env_mocker.set_traceback_config([_traceback.TracebackEvent.DEPRECATED])
|
||||||
deprecate_args: dict[str, t.Any] = dict(msg="same", version="1.2.3", collection_name="blar.blar")
|
deprecate_args: dict[str, t.Any] = dict(msg="same", version="1.2.3", collection_name="blar.blar")
|
||||||
|
|
||||||
# DeprecationMessageDetail dataclass object hash is the dedupe key; presence of differing tracebacks or SourceContexts affects de-dupe
|
# DeprecationSummary dataclass object hash is the dedupe key; presence of differing tracebacks or SourceContexts affects de-dupe
|
||||||
|
|
||||||
for _i in range(0, 10):
|
for _i in range(0, 10):
|
||||||
# same location, same traceback- should be collapsed to one message
|
# same location, same traceback- should be collapsed to one message
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.common import warnings
|
||||||
|
|
||||||
|
from ansible.module_utils.common.warnings import error_as_warning
|
||||||
|
from ansible.module_utils.testing import patch_module_args
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures("as_target", "module_env_mocker")
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_as_warning() -> None:
|
||||||
|
try:
|
||||||
|
raise Exception('hello')
|
||||||
|
except Exception as ex:
|
||||||
|
error_as_warning('Warning message', ex)
|
||||||
|
|
||||||
|
assert warnings.get_warning_messages() == ('Warning message: hello',)
|
||||||
|
assert len(warnings.get_warnings()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_as_warning_via_module() -> None:
|
||||||
|
with patch_module_args():
|
||||||
|
am = AnsibleModule(argument_spec={})
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise Exception('hello')
|
||||||
|
except Exception as ex:
|
||||||
|
am.error_as_warning('Warning message', ex)
|
||||||
|
|
||||||
|
assert warnings.get_warning_messages() == ('Warning message: hello',)
|
||||||
|
assert len(warnings.get_warnings()) == 1
|
||||||
|
|
@ -8,12 +8,14 @@ import pytest
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from ansible.module_utils._internal import _traceback, _messages
|
from ansible.module_utils._internal import _traceback, _messages
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.common import warnings
|
from ansible.module_utils.common import warnings
|
||||||
|
|
||||||
from ansible.module_utils.common.warnings import warn
|
from ansible.module_utils.common.warnings import warn
|
||||||
|
from ansible.module_utils.testing import patch_module_args
|
||||||
from units.mock.module import ModuleEnvMocker
|
from units.mock.module import ModuleEnvMocker
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("module_env_mocker")
|
pytestmark = pytest.mark.usefixtures("as_target", "module_env_mocker")
|
||||||
|
|
||||||
|
|
||||||
def test_warn():
|
def test_warn():
|
||||||
|
|
@ -22,6 +24,16 @@ def test_warn():
|
||||||
assert warnings.get_warnings() == [_messages.WarningSummary(event=_messages.Event(msg='Warning message'))]
|
assert warnings.get_warnings() == [_messages.WarningSummary(event=_messages.Event(msg='Warning message'))]
|
||||||
|
|
||||||
|
|
||||||
|
def test_warn_via_module() -> None:
|
||||||
|
with patch_module_args():
|
||||||
|
am = AnsibleModule(argument_spec={})
|
||||||
|
|
||||||
|
am.warn('Warning message')
|
||||||
|
|
||||||
|
assert warnings.get_warning_messages() == ('Warning message',)
|
||||||
|
assert warnings.get_warnings() == [_messages.WarningSummary(event=_messages.Event(msg='Warning message'))]
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_warnings():
|
def test_multiple_warnings():
|
||||||
messages = [
|
messages = [
|
||||||
'First warning',
|
'First warning',
|
||||||
|
|
@ -40,7 +52,7 @@ def test_dedupe_with_traceback(module_env_mocker: ModuleEnvMocker) -> None:
|
||||||
module_env_mocker.set_traceback_config([_traceback.TracebackEvent.WARNING])
|
module_env_mocker.set_traceback_config([_traceback.TracebackEvent.WARNING])
|
||||||
msg = "a warning message"
|
msg = "a warning message"
|
||||||
|
|
||||||
# WarningMessageDetail dataclass object hash is the dedupe key; presence of differing tracebacks or SourceContexts affects de-dupe
|
# WarningSummary dataclass object hash is the dedupe key; presence of differing tracebacks or SourceContexts affects de-dupe
|
||||||
|
|
||||||
for _i in range(0, 10):
|
for _i in range(0, 10):
|
||||||
warn(msg) # same location, same traceback- should be collapsed to one message
|
warn(msg) # same location, same traceback- should be collapsed to one message
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,29 @@ from __future__ import annotations
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ansible.utils.display import Display
|
from ansible.utils.display import Display
|
||||||
|
from ansible.module_utils import basic
|
||||||
|
from units.test_utils.controller.display import emits_warnings
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_utils_warn() -> None:
|
||||||
|
"""Verify that `module_utils.basic.warn` on the controller is routed to `Display.warning`."""
|
||||||
|
with emits_warnings(warning_pattern="hello"):
|
||||||
|
basic.warn("hello")
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_utils_error_as_warning() -> None:
|
||||||
|
"""Verify that `module_utils.basic.error_as_warning` on the controller is routed to `Display.error_as_warning`."""
|
||||||
|
with emits_warnings(warning_pattern="hello.*world"):
|
||||||
|
try:
|
||||||
|
raise Exception("world")
|
||||||
|
except Exception as ex:
|
||||||
|
basic.error_as_warning("hello", ex)
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_utils_deprecate() -> None:
|
||||||
|
"""Verify that `module_utils.basic.deprecate` on the controller is routed to `Display.deprecated`."""
|
||||||
|
with emits_warnings(deprecation_pattern="hello"):
|
||||||
|
basic.deprecate("hello", version='9999.9')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user