diff --git a/changelogs/fragments/212-py26-deprecation.yml b/changelogs/fragments/212-py26-deprecation.yml new file mode 100644 index 00000000000..181efc974b1 --- /dev/null +++ b/changelogs/fragments/212-py26-deprecation.yml @@ -0,0 +1,4 @@ +minor_changes: +- Python 2.6 Target Support - Deprecate Python 2.6 for targets, requiring Python 2.7 or newer. + ``ansible-core==2.13`` will drop support for Python 2.6. + (https://github.com/ansible/ansible/pull/74165) diff --git a/docs/docsite/rst/porting_guides/porting_guide_core_2.12.rst b/docs/docsite/rst/porting_guides/porting_guide_core_2.12.rst index c3da04fbe2a..189b30e3108 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_core_2.12.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_core_2.12.rst @@ -31,6 +31,7 @@ No notable changes Deprecated ========== +* Python 2.6 on the target node is deprecated in this release. ``ansible-core`` 2.13 will remove support for Python 2.6. * Bare variables in conditionals: ``when`` conditionals no longer automatically parse string booleans such as ``"true"`` and ``"false"`` into actual booleans. Any variable containing a non-empty string is considered true. This was previously configurable with the ``CONDITIONAL_BARE_VARS`` configuration option (and the ``ANSIBLE_CONDITIONAL_BARE_VARS`` environment variable). This setting no longer has any effect. Users can work around the issue by using the ``|bool`` filter: .. code-block:: yaml diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index cd2a0553341..37086e360a3 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -249,14 +249,22 @@ PERMS_RE = re.compile(r'[^rwxXstugo]') # and should only restrict on our documented minimum versions _PY3_MIN = sys.version_info[:2] >= (3, 5) _PY2_MIN = (2, 6) <= sys.version_info[:2] < (3,) +_PY26 = (2, 6) == sys.version_info[:2] _PY_MIN = _PY3_MIN or _PY2_MIN if not _PY_MIN: print( '\n{"failed": true, ' - '"msg": "Ansible requires a minimum of Python2 version 2.6 or Python3 version 3.5. Current version: %s"}' % ''.join(sys.version.splitlines()) + '"msg": "ansible-core requires a minimum of Python2 version 2.6 or Python3 version 3.5. Current version: %s"}' % ''.join(sys.version.splitlines()) ) sys.exit(1) +if _PY26: + deprecate( + 'ansible-core 2.13 will require Python 2.7 or newer on the target. ' + 'Current version: %s' % ''.join(sys.version.splitlines()), + version='2.13', + ) + # # Deprecated functions diff --git a/test/integration/targets/module_utils/module_utils_test.yml b/test/integration/targets/module_utils/module_utils_test.yml index 96b2a9e0a6d..a6019cdaaf7 100644 --- a/test/integration/targets/module_utils/module_utils_test.yml +++ b/test/integration/targets/module_utils/module_utils_test.yml @@ -57,8 +57,8 @@ - name: Assert that the deprecation message is given correctly assert: that: - - result.deprecations[0].msg == "Alias 'baz' is deprecated. See the module docs for more information" - - result.deprecations[0].version == '9.99' + - result.deprecations[-1].msg == "Alias 'baz' is deprecated. See the module docs for more information" + - result.deprecations[-1].version == '9.99' - block: - name: Get a string with a \0 in it diff --git a/test/units/module_utils/basic/test_argument_spec.py b/test/units/module_utils/basic/test_argument_spec.py index 1b3f7035219..24bbe2e906a 100644 --- a/test/units/module_utils/basic/test_argument_spec.py +++ b/test/units/module_utils/basic/test_argument_spec.py @@ -15,6 +15,7 @@ import pytest from units.compat.mock import MagicMock from ansible.module_utils import basic from ansible.module_utils.api import basic_auth_argument_spec, rate_limit_argument_spec, retry_argument_spec +from ansible.module_utils.common import warnings from ansible.module_utils.common.warnings import get_deprecation_messages, get_warning_messages from ansible.module_utils.six import integer_types, string_types from ansible.module_utils.six.moves import builtins @@ -400,8 +401,10 @@ class TestComplexArgSpecs: assert am.params['bar3'][1] == 'test/' @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'zodraz': 'one'}], indirect=['stdin']) - def test_deprecated_alias(self, capfd, mocker, stdin, complex_argspec): + def test_deprecated_alias(self, capfd, mocker, stdin, complex_argspec, monkeypatch): """Test a deprecated alias""" + monkeypatch.setattr(warnings, '_global_deprecations', []) + am = basic.AnsibleModule(**complex_argspec) assert "Alias 'zodraz' is deprecated." in get_deprecation_messages()[0]['msg'] diff --git a/test/units/module_utils/basic/test_deprecate_warn.py b/test/units/module_utils/basic/test_deprecate_warn.py index 351cf25b193..7fd54ce0bb1 100644 --- a/test/units/module_utils/basic/test_deprecate_warn.py +++ b/test/units/module_utils/basic/test_deprecate_warn.py @@ -10,6 +10,8 @@ import json import pytest +from ansible.module_utils.common import warnings + @pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) def test_warn(am, capfd): @@ -23,7 +25,9 @@ def test_warn(am, capfd): @pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) -def test_deprecate(am, capfd): +def test_deprecate(am, capfd, monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) + am.deprecate('deprecation1') am.deprecate('deprecation2', '2.3') # pylint: disable=ansible-deprecated-no-collection-name am.deprecate('deprecation3', version='2.4') # pylint: disable=ansible-deprecated-no-collection-name diff --git a/test/units/module_utils/basic/test_exit_json.py b/test/units/module_utils/basic/test_exit_json.py index fe7a824510e..25b41450695 100644 --- a/test/units/module_utils/basic/test_exit_json.py +++ b/test/units/module_utils/basic/test_exit_json.py @@ -12,6 +12,7 @@ import datetime import pytest +from ansible.module_utils.common import warnings EMPTY_INVOCATION = {u'module_args': {}} DATETIME = datetime.datetime.strptime('2020-07-13 12:50:00', '%Y-%m-%d %H:%M:%S') @@ -37,7 +38,9 @@ class TestAnsibleModuleExitJson: # pylint bug: https://github.com/PyCQA/pylint/issues/511 # pylint: disable=undefined-variable @pytest.mark.parametrize('args, expected, stdin', ((a, e, {}) for a, e in DATA), indirect=['stdin']) - def test_exit_json_exits(self, am, capfd, args, expected): + def test_exit_json_exits(self, am, capfd, args, expected, monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) + with pytest.raises(SystemExit) as ctx: am.exit_json(**args) assert ctx.value.code == 0 @@ -51,7 +54,9 @@ class TestAnsibleModuleExitJson: @pytest.mark.parametrize('args, expected, stdin', ((a, e, {}) for a, e in DATA if 'msg' in a), # pylint: disable=undefined-variable indirect=['stdin']) - def test_fail_json_exits(self, am, capfd, args, expected): + def test_fail_json_exits(self, am, capfd, args, expected, monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) + with pytest.raises(SystemExit) as ctx: am.fail_json(**args) assert ctx.value.code == 1 @@ -63,7 +68,9 @@ class TestAnsibleModuleExitJson: assert return_val == expected @pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) - def test_fail_json_msg_positional(self, am, capfd): + def test_fail_json_msg_positional(self, am, capfd, monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) + with pytest.raises(SystemExit) as ctx: am.fail_json('This is the msg') assert ctx.value.code == 1 @@ -75,8 +82,10 @@ class TestAnsibleModuleExitJson: 'invocation': EMPTY_INVOCATION} @pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) - def test_fail_json_msg_as_kwarg_after(self, am, capfd): + def test_fail_json_msg_as_kwarg_after(self, am, capfd, monkeypatch): """Test that msg as a kwarg after other kwargs works""" + monkeypatch.setattr(warnings, '_global_deprecations', []) + with pytest.raises(SystemExit) as ctx: am.fail_json(arbitrary=42, msg='This is the msg') assert ctx.value.code == 1 @@ -139,7 +148,8 @@ class TestAnsibleModuleExitValuesRemoved: (({'username': {}, 'password': {'no_log': True}, 'token': {'no_log': True}}, s, r, e) for s, r, e in DATA), # pylint: disable=undefined-variable indirect=['am', 'stdin']) - def test_exit_json_removes_values(self, am, capfd, return_val, expected): + def test_exit_json_removes_values(self, am, capfd, return_val, expected, monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) with pytest.raises(SystemExit): am.exit_json(**return_val) out, err = capfd.readouterr() @@ -151,7 +161,8 @@ class TestAnsibleModuleExitValuesRemoved: (({'username': {}, 'password': {'no_log': True}, 'token': {'no_log': True}}, s, r, e) for s, r, e in DATA), # pylint: disable=undefined-variable indirect=['am', 'stdin']) - def test_fail_json_removes_values(self, am, capfd, return_val, expected): + def test_fail_json_removes_values(self, am, capfd, return_val, expected, monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) expected['failed'] = True with pytest.raises(SystemExit): am.fail_json(**return_val) == expected diff --git a/test/units/module_utils/common/arg_spec/test_module_validate.py b/test/units/module_utils/common/arg_spec/test_module_validate.py index 14e6e1e7c77..350e9da170e 100644 --- a/test/units/module_utils/common/arg_spec/test_module_validate.py +++ b/test/units/module_utils/common/arg_spec/test_module_validate.py @@ -25,7 +25,9 @@ def test_module_validate(): assert result.validated_parameters == expected -def test_module_alias_deprecations_warnings(): +def test_module_alias_deprecations_warnings(monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) + arg_spec = { 'path': { 'aliases': ['source', 'src', 'flamethrower'], diff --git a/test/units/module_utils/common/warnings/test_deprecate.py b/test/units/module_utils/common/warnings/test_deprecate.py index 42046bfebc7..02bac7a6295 100644 --- a/test/units/module_utils/common/warnings/test_deprecate.py +++ b/test/units/module_utils/common/warnings/test_deprecate.py @@ -26,50 +26,55 @@ def deprecation_messages(): ] -def test_deprecate_message_only(): +@pytest.fixture +def reset(monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) + + +def test_deprecate_message_only(reset): deprecate('Deprecation message') assert warnings._global_deprecations == [ {'msg': 'Deprecation message', 'version': None, 'collection_name': None}] -def test_deprecate_with_collection(): +def test_deprecate_with_collection(reset): deprecate(msg='Deprecation message', collection_name='ansible.builtin') assert warnings._global_deprecations == [ {'msg': 'Deprecation message', 'version': None, 'collection_name': 'ansible.builtin'}] -def test_deprecate_with_version(): +def test_deprecate_with_version(reset): deprecate(msg='Deprecation message', version='2.14') assert warnings._global_deprecations == [ {'msg': 'Deprecation message', 'version': '2.14', 'collection_name': None}] -def test_deprecate_with_version_and_collection(): +def test_deprecate_with_version_and_collection(reset): deprecate(msg='Deprecation message', version='2.14', collection_name='ansible.builtin') assert warnings._global_deprecations == [ {'msg': 'Deprecation message', 'version': '2.14', 'collection_name': 'ansible.builtin'}] -def test_deprecate_with_date(): +def test_deprecate_with_date(reset): deprecate(msg='Deprecation message', date='2199-12-31') assert warnings._global_deprecations == [ {'msg': 'Deprecation message', 'date': '2199-12-31', 'collection_name': None}] -def test_deprecate_with_date_and_collection(): +def test_deprecate_with_date_and_collection(reset): deprecate(msg='Deprecation message', date='2199-12-31', collection_name='ansible.builtin') assert warnings._global_deprecations == [ {'msg': 'Deprecation message', 'date': '2199-12-31', 'collection_name': 'ansible.builtin'}] -def test_multiple_deprecations(deprecation_messages): +def test_multiple_deprecations(deprecation_messages, reset): for d in deprecation_messages: deprecate(**d) assert deprecation_messages == warnings._global_deprecations -def test_get_deprecation_messages(deprecation_messages): +def test_get_deprecation_messages(deprecation_messages, reset): for d in deprecation_messages: deprecate(**d)