diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 0333c5bf25e..4a424c70b03 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -204,6 +204,13 @@ FILE_COMMON_ARGUMENTS = dict( unsafe_writes=dict(type='bool', default=False, fallback=(env_fallback, ['ANSIBLE_UNSAFE_WRITES'])), # should be available to any module using atomic_move ) +DECRYPT_COMMON_ARGUMENT = dict( + decrypt=dict( + type='bool', + default=True, + ), +) + PASSWD_ARG_RE = re.compile(r'^[-]{0,2}pass[-]?(word|wd)?') # Used for parsing symbolic file perms @@ -368,7 +375,7 @@ def missing_required_lib(library, reason=None, url=None): class AnsibleModule(object): def __init__(self, argument_spec, bypass_checks=False, no_log=False, mutually_exclusive=None, required_together=None, - required_one_of=None, add_file_common_args=False, + required_one_of=None, extends_common_args=(), supports_check_mode=False, required_if=None, required_by=None): """ @@ -408,8 +415,8 @@ class AnsibleModule(object): self._options_context = list() self._tmpdir = None - if add_file_common_args: - for k, v in FILE_COMMON_ARGUMENTS.items(): + for shared_arguments in extends_common_args: + for k, v in shared_arguments.items(): if k not in self.argument_spec: self.argument_spec[k] = v diff --git a/lib/ansible/modules/assemble.py b/lib/ansible/modules/assemble.py index b28b696ea85..08d674ff0f6 100644 --- a/lib/ansible/modules/assemble.py +++ b/lib/ansible/modules/assemble.py @@ -130,7 +130,7 @@ import os import re import tempfile -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS, DECRYPT_COMMON_ARGUMENT from ansible.module_utils.common.text.converters import to_native @@ -201,13 +201,14 @@ def main(): regexp=dict(type='str'), ignore_hidden=dict(type='bool', default=False), validate=dict(type='str'), - + ), + extends_common_args=( + FILE_COMMON_ARGUMENTS, # Options that are for the action plugin, but ignored by the module itself. # We have them here so that the tests pass without ignores, which # reduces the likelihood of further bugs added. - decrypt=dict(type='bool', default=True), + DECRYPT_COMMON_ARGUMENT, ), - add_file_common_args=True, supports_check_mode=True, ) diff --git a/lib/ansible/modules/blockinfile.py b/lib/ansible/modules/blockinfile.py index 8b8e4905cac..5d153523f15 100644 --- a/lib/ansible/modules/blockinfile.py +++ b/lib/ansible/modules/blockinfile.py @@ -200,7 +200,7 @@ import re import os import tempfile -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS from ansible.module_utils.common.text.converters import to_native @@ -257,7 +257,7 @@ def main(): encoding=dict(type='str', default='utf-8'), ), mutually_exclusive=[['insertbefore', 'insertafter']], - add_file_common_args=True, + extends_common_args=(FILE_COMMON_ARGUMENTS,), supports_check_mode=True ) params = module.params diff --git a/lib/ansible/modules/copy.py b/lib/ansible/modules/copy.py index 28c14f4d71f..720b2772436 100644 --- a/lib/ansible/modules/copy.py +++ b/lib/ansible/modules/copy.py @@ -293,7 +293,7 @@ import stat import tempfile from ansible.module_utils.common.text.converters import to_bytes, to_native -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS class AnsibleModuleError(Exception): @@ -479,7 +479,7 @@ def main(): checksum=dict(type='str'), follow=dict(type='bool', default=False), ), - add_file_common_args=True, + extends_common_args=(FILE_COMMON_ARGUMENTS,), supports_check_mode=True, ) diff --git a/lib/ansible/modules/file.py b/lib/ansible/modules/file.py index da035c97be6..b036e3bd204 100644 --- a/lib/ansible/modules/file.py +++ b/lib/ansible/modules/file.py @@ -236,7 +236,7 @@ import time from pwd import getpwnam, getpwuid from grp import getgrnam, getgrgid -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS from ansible.module_utils.common.text.converters import to_bytes, to_native from ansible.module_utils.common.sentinel import Sentinel @@ -963,16 +963,16 @@ def main(): path=dict(type='path', required=True, aliases=['dest', 'name']), _original_basename=dict(type='str'), # Internal use only, for recursive ops recurse=dict(type='bool', default=False), - force=dict(type='bool', default=False), # Note: Should not be in file_common_args in future - follow=dict(type='bool', default=True), # Note: Different default than file_common_args + force=dict(type='bool', default=False), + follow=dict(type='bool', default=True), _diff_peek=dict(type='bool'), # Internal use only, for internal checks in the action plugins - src=dict(type='path'), # Note: Should not be in file_common_args in future + src=dict(type='path'), modification_time=dict(type='str'), modification_time_format=dict(type='str', default='%Y%m%d%H%M.%S'), access_time=dict(type='str'), access_time_format=dict(type='str', default='%Y%m%d%H%M.%S'), ), - add_file_common_args=True, + extends_common_args=(FILE_COMMON_ARGUMENTS,), supports_check_mode=True, ) diff --git a/lib/ansible/modules/get_url.py b/lib/ansible/modules/get_url.py index b679d08d9c4..0afd75089bd 100644 --- a/lib/ansible/modules/get_url.py +++ b/lib/ansible/modules/get_url.py @@ -376,7 +376,7 @@ import tempfile from datetime import datetime, timezone from urllib.parse import urlsplit -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.urls import fetch_url, url_argument_spec @@ -535,7 +535,7 @@ def main(): module = AnsibleModule( # not checking because of daisy chain to file module argument_spec=argument_spec, - add_file_common_args=True, + extends_common_args=(FILE_COMMON_ARGUMENTS,), supports_check_mode=True, ) diff --git a/lib/ansible/modules/lineinfile.py b/lib/ansible/modules/lineinfile.py index 9b820096d3d..cbf896d8a1a 100644 --- a/lib/ansible/modules/lineinfile.py +++ b/lib/ansible/modules/lineinfile.py @@ -253,7 +253,7 @@ import re import tempfile # import module snippets -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text @@ -585,7 +585,7 @@ def main(): ), mutually_exclusive=[ ['insertbefore', 'insertafter'], ['regexp', 'search_string'], ['backrefs', 'search_string']], - add_file_common_args=True, + extends_common_args=(FILE_COMMON_ARGUMENTS,), supports_check_mode=True, ) diff --git a/lib/ansible/modules/replace.py b/lib/ansible/modules/replace.py index f8dffd25c70..5946f30a9dc 100644 --- a/lib/ansible/modules/replace.py +++ b/lib/ansible/modules/replace.py @@ -184,7 +184,7 @@ import re import tempfile from ansible.module_utils.common.text.converters import to_text -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS def write_changes(module, contents, path, encoding='utf-8'): @@ -232,7 +232,7 @@ def main(): validate=dict(type='str'), encoding=dict(type='str', default='utf-8'), ), - add_file_common_args=True, + extends_common_args=(FILE_COMMON_ARGUMENTS,), supports_check_mode=True, ) diff --git a/lib/ansible/modules/unarchive.py b/lib/ansible/modules/unarchive.py index 18f90f727e0..75d16aae34c 100644 --- a/lib/ansible/modules/unarchive.py +++ b/lib/ansible/modules/unarchive.py @@ -254,7 +254,7 @@ from functools import partial from zipfile import ZipFile from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS, DECRYPT_COMMON_ARGUMENT from ansible.module_utils.common.process import get_bin_path from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.urls import fetch_file @@ -1067,9 +1067,11 @@ def main(): # We have them here so that the sanity tests pass without ignores, which # reduces the likelihood of further bugs added. copy=dict(type='bool', default=True), - decrypt=dict(type='bool', default=True), ), - add_file_common_args=True, + extends_common_args=( + FILE_COMMON_ARGUMENTS, + DECRYPT_COMMON_ARGUMENT, # Also here for the action plugin + ), # check-mode only works for zip files, we cover that later supports_check_mode=True, mutually_exclusive=[('include', 'exclude')], diff --git a/lib/ansible/modules/uri.py b/lib/ansible/modules/uri.py index ceb6bcae764..36223b4ce3d 100644 --- a/lib/ansible/modules/uri.py +++ b/lib/ansible/modules/uri.py @@ -442,7 +442,7 @@ from collections.abc import Mapping, Sequence from datetime import datetime, timezone from urllib.parse import urlencode, urljoin -from ansible.module_utils.basic import AnsibleModule, sanitize_keys +from ansible.module_utils.basic import AnsibleModule, sanitize_keys, FILE_COMMON_ARGUMENTS from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.urls import ( fetch_url, @@ -614,7 +614,7 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, - add_file_common_args=True, + extends_common_args=(FILE_COMMON_ARGUMENTS,), mutually_exclusive=[['body', 'src']], ) diff --git a/lib/ansible/modules/yum_repository.py b/lib/ansible/modules/yum_repository.py index 013e85d2d8e..a97cfd41863 100644 --- a/lib/ansible/modules/yum_repository.py +++ b/lib/ansible/modules/yum_repository.py @@ -586,7 +586,7 @@ def main(): ["state", "present", ["description"]], ], argument_spec=argument_spec, - add_file_common_args=True, + extends_common_args=FILE_COMMON_ARGUMENTS, supports_check_mode=True, ) diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py index 2149bef91ca..1de96b1d358 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -25,6 +25,7 @@ import typing as _t from ansible.errors import AnsibleError, AnsibleActionFail, AnsibleActionSkip from ansible.executor.powershell import module_manifest as ps_manifest from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text +from ansible.module_utils.basic import DECRYPT_COMMON_ARGUMENT from ansible.plugins.action import ActionBase @@ -49,7 +50,7 @@ class ActionModule(ActionBase): 'removes': {'type': 'str'}, 'chdir': {'type': 'str'}, 'executable': {'type': 'str'}, - }, + } | DECRYPT_COMMON_ARGUMENT, required_one_of=[['_raw_params', 'cmd']], mutually_exclusive=[['_raw_params', 'cmd']], ) diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py index 9c6b44f1d98..61ec0c32f14 100644 --- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py @@ -163,13 +163,14 @@ def get_py_argument_spec(filename, collection): fake.kwargs[arg_name] = arg # for ping kwargs == {'argument_spec':{'data':{'type':'str','default':'pong'}}, 'supports_check_mode':True} argument_spec = fake.kwargs.get('argument_spec') or {} - # If add_file_common_args is truish, add options from FILE_COMMON_ARGUMENTS when not present. + # Add the common arguments from the extends_common_args # This is the only modification to argument_spec done by AnsibleModule itself, and which is # not caught by setup_env's AnsibleModule replacement - if fake.kwargs.get('add_file_common_args'): - for k, v in FILE_COMMON_ARGUMENTS.items(): - if k not in argument_spec: - argument_spec[k] = v + if args := fake.kwargs.get('extends_common_arguments'): + for common in args: + for k, v in common.items(): + if k not in argument_spec: + argument_spec[k] = v return argument_spec, fake.kwargs except (TypeError, IndexError): return {}, {} diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py index 6c2b3415430..d8732ae11cf 100644 --- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py @@ -367,7 +367,7 @@ def ansible_module_kwargs_schema(module_name, for_collection): 'bypass_checks': bool, 'no_log': bool, 'check_invalid_arguments': Any(None, bool), - 'add_file_common_args': bool, + 'extends_common_args': tuple[dict], 'supports_check_mode': bool, } if module_name.endswith(('_info', '_facts')): diff --git a/test/units/module_utils/basic/test_argument_spec.py b/test/units/module_utils/basic/test_argument_spec.py index 1e0d5523be4..6da4a5fc303 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 unittest.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.basic import FILE_COMMON_ARGUMENTS from ansible.module_utils.common.warnings import get_deprecation_messages, get_warning_messages import builtins @@ -138,7 +139,7 @@ def complex_argspec(): mutually_exclusive=mut_ex, required_together=req_to, no_log=True, - add_file_common_args=True, + extends_common_args=FILE_COMMON_ARGUMENTS, supports_check_mode=True, ) return kwargs @@ -188,7 +189,7 @@ def options_argspec_list(): kwargs = dict( argument_spec=arg_spec, no_log=True, - add_file_common_args=True, + extends_common_args=FILE_COMMON_ARGUMENTS, supports_check_mode=True ) return kwargs