From c30f96a2b4574258c1dd48829a5978d27e64aad6 Mon Sep 17 00:00:00 2001 From: Patrick Kingston Date: Tue, 28 Oct 2025 14:08:25 -0400 Subject: [PATCH 1/8] Refactor add_file_common_args and decrypt Unify the system for handling common arguments by adding an extends_common_args argument to the AnsibleModule __init__. This stops the proliferation of add_whatever_common_args bools to the __init__ --- lib/ansible/module_utils/basic.py | 13 ++++++++++--- lib/ansible/modules/assemble.py | 9 +++++---- lib/ansible/modules/blockinfile.py | 4 ++-- lib/ansible/modules/copy.py | 4 ++-- lib/ansible/modules/file.py | 10 +++++----- lib/ansible/modules/get_url.py | 4 ++-- lib/ansible/modules/lineinfile.py | 4 ++-- lib/ansible/modules/replace.py | 4 ++-- lib/ansible/modules/unarchive.py | 8 +++++--- lib/ansible/modules/uri.py | 4 ++-- lib/ansible/modules/yum_repository.py | 2 +- lib/ansible/plugins/action/script.py | 3 ++- .../validate_modules/module_args.py | 11 ++++++----- .../validate-modules/validate_modules/schema.py | 2 +- test/units/module_utils/basic/test_argument_spec.py | 5 +++-- 15 files changed, 50 insertions(+), 37 deletions(-) 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 From e49a9e55e8e98bdf77e9df31aea1540fd373e1f1 Mon Sep 17 00:00:00 2001 From: Patrick Kingston Date: Tue, 28 Oct 2025 16:17:47 -0400 Subject: [PATCH 2/8] Revert changes in script action plugin --- lib/ansible/plugins/action/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py index 1de96b1d358..2edda092806 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -50,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']], ) From 4ac6f41655ec02c7258a3c46a1dafbed55b2841b Mon Sep 17 00:00:00 2001 From: Patrick Kingston Date: Wed, 29 Oct 2025 14:15:45 -0400 Subject: [PATCH 3/8] Add automatic docs for common args Automatically pull in docs when an AnsibleModule uses the COMMON_ARGUMENT(s) defined in basic.py --- lib/ansible/module_utils/basic.py | 5 +++ lib/ansible/modules/copy.py | 1 - lib/ansible/parsing/plugin_docs.py | 60 ++++++++++++++++++------------ 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 4a424c70b03..ac1e33be357 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -211,6 +211,11 @@ DECRYPT_COMMON_ARGUMENT = dict( ), ) +COMMON_ARGUMENTS_TO_FRAGMENTS = { + 'FILE_COMMON_ARGUMENTS': 'files', + 'DECRYPT_COMMON_ARGUMENT': 'decrypt', +} + PASSWD_ARG_RE = re.compile(r'^[-]{0,2}pass[-]?(word|wd)?') # Used for parsing symbolic file perms diff --git a/lib/ansible/modules/copy.py b/lib/ansible/modules/copy.py index 720b2772436..091e5508c4e 100644 --- a/lib/ansible/modules/copy.py +++ b/lib/ansible/modules/copy.py @@ -119,7 +119,6 @@ options: version_added: '2.5' extends_documentation_fragment: - decrypt - - files - validate - action_common_attributes - action_common_attributes.files diff --git a/lib/ansible/parsing/plugin_docs.py b/lib/ansible/parsing/plugin_docs.py index e6a44aef90f..f18ac57e465 100644 --- a/lib/ansible/parsing/plugin_docs.py +++ b/lib/ansible/parsing/plugin_docs.py @@ -9,6 +9,7 @@ import yaml from ansible import constants as C from ansible.errors import AnsibleError, AnsibleParserError +from ansible.module_utils.basic import COMMON_ARGUMENTS_TO_FRAGMENTS from ansible.module_utils.common.text.converters import to_text, to_native from ansible.parsing.yaml.loader import AnsibleLoader from ansible.utils.display import Display @@ -65,31 +66,44 @@ def read_docstring_from_python_file(filename, verbose=True, ignore_errors=True): with open(filename, 'rb') as b_module_data: M = ast.parse(b_module_data.read()) - for child in M.body: + for child in ast.walk(M): if isinstance(child, ast.Assign): - for t in child.targets: - try: - theid = t.id - except AttributeError: - # skip errors can happen when trying to use the normal code - display.warning("Building documentation, failed to assign id for %s on %s, skipping" % (t, filename)) - continue - - if theid in string_to_vars: - varkey = string_to_vars[theid] - if isinstance(child.value, ast.Dict): - data[varkey] = ast.literal_eval(child.value) - else: - if theid == 'EXAMPLES': - # examples 'can' be yaml, but even if so, we dont want to parse as such here - # as it can create undesired 'objects' that don't display well as docs. - data[varkey] = to_text(child.value.value) + # Find the AnsibleModule(extend_common_args=...) and extend fragments + if isinstance(child.value, ast.Call) and getattr(child.value.func, 'id', None) == 'AnsibleModule': # Magic string + for kw in child.value.keywords: + if kw.arg == 'extends_common_args' and isinstance(kw.value, ast.Tuple): # Magic string + if data['doc'] is None: + if 'extends_documentation_fragment' not in data['doc']: + data['doc']['extends_documentation_fragment'] = [] + for arg_name_node in kw.value.elts: + if isinstance(arg_name_node, ast.Name): + fragment_name = COMMON_ARGUMENTS_TO_FRAGMENTS[arg_name_node.id] + data['doc']['extends_documentation_fragment'].append(fragment_name) + # If it's top level, find the specified docs + if child in M.body: + for t in child.targets: + try: + theid = t.id + except AttributeError: + # skip errors can happen when trying to use the normal code + display.warning("Building documentation, failed to assign id for %s on %s, skipping" % (t, filename)) + continue + + if theid in string_to_vars: + varkey = string_to_vars[theid] + if isinstance(child.value, ast.Dict): + data[varkey] = ast.literal_eval(child.value) else: - # string should be yaml if already not a dict - child_value = _tags.Origin(path=filename, line_num=child.value.lineno).tag(child.value.value) - data[varkey] = yaml.load(child_value, Loader=AnsibleLoader) - - display.debug('Documentation assigned: %s' % varkey) + if theid == 'EXAMPLES': + # examples 'can' be yaml, but even if so, we dont want to parse as such here + # as it can create undesired 'objects' that don't display well as docs. + data[varkey] = to_text(child.value.value) + else: + # string should be yaml if already not a dict + child_value = _tags.Origin(path=filename, line_num=child.value.lineno).tag(child.value.value) + data[varkey] = yaml.load(child_value, Loader=AnsibleLoader) + + display.debug('Documentation assigned: %s' % varkey) except Exception as ex: msg = f"Unable to parse documentation in python file {filename!r}" From 93f84d2b3c43afbfa9e90e91d83b11cba3c43c90 Mon Sep 17 00:00:00 2001 From: Patrick Kingston Date: Wed, 29 Oct 2025 15:01:32 -0400 Subject: [PATCH 4/8] Add validate common argument --- lib/ansible/module_utils/basic.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index ac1e33be357..524dac0b9ef 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -211,6 +211,12 @@ DECRYPT_COMMON_ARGUMENT = dict( ), ) +VALIDATE_COMMON_ARGUMENT = dict( + validate=dict( + type='str', + ), +) + COMMON_ARGUMENTS_TO_FRAGMENTS = { 'FILE_COMMON_ARGUMENTS': 'files', 'DECRYPT_COMMON_ARGUMENT': 'decrypt', From dd99e57a45544ff1646a6164b027cada43ad1570 Mon Sep 17 00:00:00 2001 From: Patrick Kingston Date: Wed, 29 Oct 2025 15:11:59 -0400 Subject: [PATCH 5/8] Remove doc fragment extensions from modules --- lib/ansible/modules/assemble.py | 2 -- lib/ansible/modules/blockinfile.py | 7 ++----- lib/ansible/modules/copy.py | 7 ++----- lib/ansible/modules/file.py | 2 +- lib/ansible/modules/get_url.py | 1 - lib/ansible/modules/lineinfile.py | 7 ++----- lib/ansible/modules/replace.py | 7 ++----- lib/ansible/modules/script.py | 1 - lib/ansible/modules/yum_repository.py | 3 +-- lib/ansible/plugins/action/script.py | 2 +- 10 files changed, 11 insertions(+), 28 deletions(-) diff --git a/lib/ansible/modules/assemble.py b/lib/ansible/modules/assemble.py index 08d674ff0f6..d2ce3c39c06 100644 --- a/lib/ansible/modules/assemble.py +++ b/lib/ansible/modules/assemble.py @@ -100,8 +100,6 @@ extends_documentation_fragment: - action_common_attributes - action_common_attributes.flow - action_common_attributes.files - - decrypt - - files """ EXAMPLES = r""" diff --git a/lib/ansible/modules/blockinfile.py b/lib/ansible/modules/blockinfile.py index 5d153523f15..c07ba8a6ce0 100644 --- a/lib/ansible/modules/blockinfile.py +++ b/lib/ansible/modules/blockinfile.py @@ -118,8 +118,6 @@ notes: extends_documentation_fragment: - action_common_attributes - action_common_attributes.files - - files - - validate attributes: check_mode: support: full @@ -200,7 +198,7 @@ import re import os import tempfile -from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT from ansible.module_utils.common.text.converters import to_native @@ -249,7 +247,6 @@ def main(): insertbefore=dict(type='str'), create=dict(type='bool', default=False), backup=dict(type='bool', default=False), - validate=dict(type='str'), marker_begin=dict(type='str', default='BEGIN'), marker_end=dict(type='str', default='END'), append_newline=dict(type='bool', default=False), @@ -257,7 +254,7 @@ def main(): encoding=dict(type='str', default='utf-8'), ), mutually_exclusive=[['insertbefore', 'insertafter']], - extends_common_args=(FILE_COMMON_ARGUMENTS,), + extends_common_args=(FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT), supports_check_mode=True ) params = module.params diff --git a/lib/ansible/modules/copy.py b/lib/ansible/modules/copy.py index 091e5508c4e..42451abf9cb 100644 --- a/lib/ansible/modules/copy.py +++ b/lib/ansible/modules/copy.py @@ -118,8 +118,6 @@ options: type: str version_added: '2.5' extends_documentation_fragment: - - decrypt - - validate - action_common_attributes - action_common_attributes.files - action_common_attributes.flow @@ -292,7 +290,7 @@ import stat import tempfile from ansible.module_utils.common.text.converters import to_bytes, to_native -from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT class AnsibleModuleError(Exception): @@ -471,14 +469,13 @@ def main(): dest=dict(type='path', required=True), backup=dict(type='bool', default=False), force=dict(type='bool', default=True), - validate=dict(type='str'), directory_mode=dict(type='raw'), remote_src=dict(type='bool', default=False), local_follow=dict(type='bool'), checksum=dict(type='str'), follow=dict(type='bool', default=False), ), - extends_common_args=(FILE_COMMON_ARGUMENTS,), + extends_common_args=(FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT), supports_check_mode=True, ) diff --git a/lib/ansible/modules/file.py b/lib/ansible/modules/file.py index b036e3bd204..089b8b01924 100644 --- a/lib/ansible/modules/file.py +++ b/lib/ansible/modules/file.py @@ -12,7 +12,7 @@ DOCUMENTATION = r""" module: file version_added: historical short_description: Manage files and file properties -extends_documentation_fragment: [files, action_common_attributes] +extends_documentation_fragment: [action_common_attributes] description: - Set attributes of files, directories, or symlinks and their targets. - Alternatively, remove files, symlinks or directories. diff --git a/lib/ansible/modules/get_url.py b/lib/ansible/modules/get_url.py index 0afd75089bd..5abf4225d9e 100644 --- a/lib/ansible/modules/get_url.py +++ b/lib/ansible/modules/get_url.py @@ -202,7 +202,6 @@ options: version_added: '2.14' # informational: requirements for nodes extends_documentation_fragment: - - files - action_common_attributes attributes: check_mode: diff --git a/lib/ansible/modules/lineinfile.py b/lib/ansible/modules/lineinfile.py index cbf896d8a1a..345bbffdbf1 100644 --- a/lib/ansible/modules/lineinfile.py +++ b/lib/ansible/modules/lineinfile.py @@ -133,8 +133,6 @@ options: extends_documentation_fragment: - action_common_attributes - action_common_attributes.files - - files - - validate attributes: check_mode: support: full @@ -253,7 +251,7 @@ import re import tempfile # import module snippets -from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text @@ -581,11 +579,10 @@ def main(): create=dict(type='bool', default=False), backup=dict(type='bool', default=False), firstmatch=dict(type='bool', default=False), - validate=dict(type='str'), ), mutually_exclusive=[ ['insertbefore', 'insertafter'], ['regexp', 'search_string'], ['backrefs', 'search_string']], - extends_common_args=(FILE_COMMON_ARGUMENTS,), + extends_common_args=(FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT), supports_check_mode=True, ) diff --git a/lib/ansible/modules/replace.py b/lib/ansible/modules/replace.py index 5946f30a9dc..cbc5d3d52fc 100644 --- a/lib/ansible/modules/replace.py +++ b/lib/ansible/modules/replace.py @@ -14,8 +14,6 @@ author: Evan Kaufman (@EvanK) extends_documentation_fragment: - action_common_attributes - action_common_attributes.files - - files - - validate attributes: check_mode: support: full @@ -184,7 +182,7 @@ import re import tempfile from ansible.module_utils.common.text.converters import to_text -from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT def write_changes(module, contents, path, encoding='utf-8'): @@ -229,10 +227,9 @@ def main(): after=dict(type='str'), before=dict(type='str'), backup=dict(type='bool', default=False), - validate=dict(type='str'), encoding=dict(type='str', default='utf-8'), ), - extends_common_args=(FILE_COMMON_ARGUMENTS,), + extends_common_args=(FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT), supports_check_mode=True, ) diff --git a/lib/ansible/modules/script.py b/lib/ansible/modules/script.py index 5d4b45f7822..a2bfccc0c14 100644 --- a/lib/ansible/modules/script.py +++ b/lib/ansible/modules/script.py @@ -64,7 +64,6 @@ extends_documentation_fragment: - action_common_attributes - action_common_attributes.files - action_common_attributes.raw - - decrypt attributes: check_mode: support: partial diff --git a/lib/ansible/modules/yum_repository.py b/lib/ansible/modules/yum_repository.py index a97cfd41863..51fe0ed049f 100644 --- a/lib/ansible/modules/yum_repository.py +++ b/lib/ansible/modules/yum_repository.py @@ -359,7 +359,6 @@ options: extends_documentation_fragment: - action_common_attributes - - files attributes: check_mode: support: full @@ -586,7 +585,7 @@ def main(): ["state", "present", ["description"]], ], argument_spec=argument_spec, - extends_common_args=FILE_COMMON_ARGUMENTS, + 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 2edda092806..289002564db 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -99,7 +99,7 @@ class ActionModule(ActionBase): if executable: executable = to_native(new_module_args['executable'], errors='surrogate_or_strict') try: - source = self._loader.get_real_file(self._find_needle('files', source), decrypt=self._task.args.get('decrypt', True)) + source = self._loader.get_real_file(self._find_needle('files', source)) except AnsibleError as e: raise AnsibleActionFail(to_native(e)) From ba82aacb8657f0216f83f9f8fcaf02bc6e495d68 Mon Sep 17 00:00:00 2001 From: Patrick Kingston Date: Wed, 29 Oct 2025 15:13:22 -0400 Subject: [PATCH 6/8] Add VALIDATE_COMMON_ARGUMENT key --- lib/ansible/module_utils/basic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 524dac0b9ef..38bf1c6c6a1 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -220,6 +220,7 @@ VALIDATE_COMMON_ARGUMENT = dict( COMMON_ARGUMENTS_TO_FRAGMENTS = { 'FILE_COMMON_ARGUMENTS': 'files', 'DECRYPT_COMMON_ARGUMENT': 'decrypt', + 'VALIDATE_COMMON_ARGUMENT': 'validate', } PASSWD_ARG_RE = re.compile(r'^[-]{0,2}pass[-]?(word|wd)?') From 7518de1e51d945f032d6ed5646c14f69104cf565 Mon Sep 17 00:00:00 2001 From: Patrick Kingston Date: Wed, 29 Oct 2025 15:52:41 -0400 Subject: [PATCH 7/8] Add decrypt back to copy module --- lib/ansible/modules/copy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ansible/modules/copy.py b/lib/ansible/modules/copy.py index 42451abf9cb..9131d4cf160 100644 --- a/lib/ansible/modules/copy.py +++ b/lib/ansible/modules/copy.py @@ -290,7 +290,7 @@ import stat import tempfile from ansible.module_utils.common.text.converters import to_bytes, to_native -from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT, DECRYPT_COMMON_ARGUMENT class AnsibleModuleError(Exception): @@ -475,7 +475,7 @@ def main(): checksum=dict(type='str'), follow=dict(type='bool', default=False), ), - extends_common_args=(FILE_COMMON_ARGUMENTS, VALIDATE_COMMON_ARGUMENT), + extends_common_args=(FILE_COMMON_ARGUMENTS, DECRYPT_COMMON_ARGUMENT, VALIDATE_COMMON_ARGUMENT), supports_check_mode=True, ) From 52bb792159dd863b74fcabcfb656bbdacde43f0e Mon Sep 17 00:00:00 2001 From: Patrick Kingston Date: Thu, 30 Oct 2025 11:17:04 -0400 Subject: [PATCH 8/8] Remove decrypt import from script action --- lib/ansible/plugins/action/script.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py index 289002564db..0f096fdaadf 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -25,7 +25,6 @@ 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