diff --git a/changelogs/fragments/yum_repository.yml b/changelogs/fragments/yum_repository.yml new file mode 100644 index 00000000000000..508760614dc367 --- /dev/null +++ b/changelogs/fragments/yum_repository.yml @@ -0,0 +1,9 @@ +deprecated_features: + - yum_repository - deprecate ``async`` option as it has been removed in RHEL 8 and will be removed in ansible-core 2.22. + - >- + yum_repository - the following options are deprecated: ``deltarpm_metadata_percentage``, ``gpgcakey``, ``http_caching``, + ``keepalive``, ``metadata_expire_filter``, ``mirrorlist_expire``, ``protect``, ``ssl_check_cert_permissions``, + ``ui_repoid_vars`` as they have no effect for dnf as an underlying package manager. + The options will be removed in ansible-core 2.22. +minor_changes: + - yum_repository - add ``excludepkgs`` alias to the ``exclude`` option. diff --git a/lib/ansible/modules/yum_repository.py b/lib/ansible/modules/yum_repository.py index 88f8cd065bd336..c0c02c22f2ea66 100644 --- a/lib/ansible/modules/yum_repository.py +++ b/lib/ansible/modules/yum_repository.py @@ -6,7 +6,6 @@ from __future__ import annotations - DOCUMENTATION = ''' --- module: yum_repository @@ -23,9 +22,11 @@ - If set to V(true) Yum will download packages and metadata from this repo in parallel, if possible. - In ansible-core 2.11, 2.12, and 2.13 the default value is V(true). - - This option has been deprecated in RHEL 8. If you're using one of the + - This option has been removed in RHEL 8. If you're using one of the versions listed above, you can set this option to None to avoid passing an unknown configuration option. + - This parameter is deprecated as it has been removed on systems supported by ansible-core + and will be removed in ansible-core 2.22. type: bool bandwidth: description: @@ -64,6 +65,8 @@ can give values over V(100), so V(200) means that the metadata is required to be half the size of the packages. Use V(0) to turn off this check, and always download metadata. + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. type: str deltarpm_percentage: description: @@ -93,8 +96,11 @@ space separated list. Shell globs using wildcards (for example V(*) and V(?)) are allowed. - The list can also be a regular YAML array. + - excludepkgs alias was added in ansible-core 2.18 type: list elements: str + aliases: + - excludepkgs failovermethod: choices: [roundrobin, priority] description: @@ -112,6 +118,8 @@ gpgcakey: description: - A URL pointing to the ASCII-armored CA key file for the repository. + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. type: str gpgcheck: description: @@ -140,6 +148,8 @@ - V(packages) means that only RPM package downloads should be cached (but not repository metadata downloads). - V(none) means that no HTTP downloads should be cached. + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. choices: [all, packages, none] type: str include: @@ -170,12 +180,15 @@ - This tells yum whether or not HTTP/1.1 keepalive should be used with this repository. This can improve transfer speeds by using one connection when downloading multiple files from a repository. + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. type: bool keepcache: description: - Either V(1) or V(0). Determines whether or not yum keeps the cache of headers and packages after successful installation. - - This parameter is deprecated and will be removed in version 2.20. + - This parameter is deprecated as it is only valid in the main configuration + and will be removed in ansible-core 2.20. choices: ['0', '1'] type: str metadata_expire: @@ -201,6 +214,8 @@ other commands which will require the latest metadata. Eg. C(yum check-update). - Note that this option does not override "yum clean expire-cache". + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. choices: [never, 'read-only:past', 'read-only:present', 'read-only:future'] type: str metalink: @@ -222,6 +237,8 @@ - Time (in seconds) after which the mirrorlist locally cached will expire. - Default value is 6 hours. + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. type: str name: description: @@ -243,6 +260,8 @@ protect: description: - Protect packages from updates from other repositories. + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. type: bool proxy: description: @@ -291,6 +310,8 @@ O(skip_if_unavailable) to be V(true). This is most useful for non-root processes which use yum on repos that have client cert files which are readable only by root. + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. type: bool sslcacert: description: @@ -336,6 +357,8 @@ - When a repository id is displayed, append these yum variables to the string if they are used in the O(baseurl)/etc. Variables are appended in the order listed (and found). + - This parameter is deprecated as it has no effect with dnf as an underlying package manager + and will be removed in ansible-core 2.22. type: str username: description: @@ -419,159 +442,86 @@ sample: "present" ''' +import configparser import os -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six.moves import configparser +from ansible.module_utils.basic import AnsibleModule, FILE_COMMON_ARGUMENTS from ansible.module_utils.common.text.converters import to_native -class YumRepo(object): - # Class global variables - module = None - params = None - section = None - repofile = configparser.RawConfigParser() - - # List of parameters which will be allowed in the repo file output - allowed_params = [ - 'async', - 'bandwidth', - 'baseurl', - 'cost', - 'countme', - 'deltarpm_metadata_percentage', - 'deltarpm_percentage', - 'enabled', - 'enablegroups', - 'exclude', - 'failovermethod', - 'gpgcakey', - 'gpgcheck', - 'gpgkey', - 'module_hotfixes', - 'http_caching', - 'include', - 'includepkgs', - 'ip_resolve', - 'keepalive', - 'keepcache', - 'metadata_expire', - 'metadata_expire_filter', - 'metalink', - 'mirrorlist', - 'mirrorlist_expire', - 'name', - 'password', - 'priority', - 'protect', - 'proxy', - 'proxy_password', - 'proxy_username', - 'repo_gpgcheck', - 'retries', - 's3_enabled', - 'skip_if_unavailable', - 'sslcacert', - 'ssl_check_cert_permissions', - 'sslclientcert', - 'sslclientkey', - 'sslverify', - 'throttle', - 'timeout', - 'ui_repoid_vars', - 'username'] - - # List of parameters which can be a list - list_params = ['exclude', 'includepkgs'] - - def __init__(self, module): - # To be able to use fail_json +class YumRepo: + def __init__(self, module, params, repoid, dest): self.module = module - # Shortcut for the params - self.params = self.module.params - # Section is always the repoid - self.section = self.params['repoid'] - - # Check if repo directory exists - repos_dir = self.params['reposdir'] - if not os.path.isdir(repos_dir): - self.module.fail_json( - msg="Repo directory '%s' does not exist." % repos_dir) - - # Set dest; also used to set dest parameter for the FS attributes - self.params['dest'] = os.path.join( - repos_dir, "%s.repo" % self.params['file']) - - # Read the repo file if it exists - if os.path.isfile(self.params['dest']): - self.repofile.read(self.params['dest']) + self.params = params + self.section = repoid + self.repofile = configparser.RawConfigParser() + self.dest = dest + if os.path.isfile(dest): + self.repofile.read(dest) def add(self): - # Remove already existing repo and create a new one - if self.repofile.has_section(self.section): - self.repofile.remove_section(self.section) - - # Add section + self.remove() self.repofile.add_section(self.section) - # Baseurl/mirrorlist is not required because for removal we need only - # the repo name. This is why we check if the baseurl/mirrorlist is - # defined. - req_params = (self.params['baseurl'], self.params['metalink'], self.params['mirrorlist']) - if req_params == (None, None, None): - self.module.fail_json( - msg="Parameter 'baseurl', 'metalink' or 'mirrorlist' is required for " - "adding a new repo.") - - # Set options for key, value in sorted(self.params.items()): - if key in self.list_params and isinstance(value, list): - # Join items into one string for specific parameters - value = ' '.join(value) - elif isinstance(value, bool): - # Convert boolean value to integer - value = int(value) - - # Set the value only if it was defined (default is None) - if value is not None and key in self.allowed_params: - if key == 'keepcache': - self.module.deprecate( - "'keepcache' parameter is deprecated.", - version='2.20' - ) - self.repofile.set(self.section, key, value) + if value is None: + continue + if key == 'keepcache': + self.module.deprecate( + "'keepcache' parameter is deprecated as it is only valid in " + "the main configuration.", + version='2.20' + ) + elif key == 'async': + self.module.deprecate( + "'async' parameter is deprecated as it has been removed on systems supported by ansible-core", + version='2.22', + ) + elif key in { + "deltarpm_metadata_percentage", + "gpgcakey", + "http_caching", + "keepalive", + "metadata_expire_filter", + "mirrorlist_expire", + "protect", + "ssl_check_cert_permissions", + "ui_repoid_vars", + }: + self.module.deprecate( + f"'{key}' parameter is deprecated as it has no effect with dnf " + "as an underlying package manager.", + version='2.22' + ) + if isinstance(value, bool): + value = str(int(value)) + self.repofile.set(self.section, key, value) def save(self): - if len(self.repofile.sections()): - # Write data into the file + if self.repofile.sections(): try: - with open(self.params['dest'], 'w') as fd: + with open(self.dest, 'w') as fd: self.repofile.write(fd) except IOError as e: self.module.fail_json( - msg="Problems handling file %s." % self.params['dest'], - details=to_native(e)) + msg=f"Problems handling file {self.dest}.", + details=to_native(e), + ) else: - # Remove the file if there are not repos try: - os.remove(self.params['dest']) + os.remove(self.dest) except OSError as e: self.module.fail_json( - msg=( - "Cannot remove empty repo file %s." % - self.params['dest']), - details=to_native(e)) + msg=f"Cannot remove empty repo file {self.dest}.", + details=to_native(e), + ) def remove(self): - # Remove section if exists - if self.repofile.has_section(self.section): - self.repofile.remove_section(self.section) + self.repofile.remove_section(self.section) def dump(self): repo_string = "" - # Compose the repo file for section in sorted(self.repofile.sections()): repo_string += "[%s]\n" % section @@ -584,7 +534,6 @@ def dump(self): def main(): - # Module settings argument_spec = dict( bandwidth=dict(), baseurl=dict(type='list', elements='str'), @@ -595,7 +544,7 @@ def main(): description=dict(), enabled=dict(type='bool'), enablegroups=dict(type='bool'), - exclude=dict(type='list', elements='str'), + exclude=dict(type='list', elements='str', aliases=['excludepkgs']), failovermethod=dict(choices=['roundrobin', 'priority']), file=dict(), gpgcakey=dict(no_log=False), @@ -642,78 +591,77 @@ def main(): username=dict(), ) + # async is a Python keyword argument_spec['async'] = dict(type='bool') module = AnsibleModule( + required_if=[ + ["state", "present", ["baseurl", "mirrorlist", "metalink"], True], + ["state", "present", ["description"]], + ], argument_spec=argument_spec, add_file_common_args=True, supports_check_mode=True, ) - name = module.params['name'] - state = module.params['state'] + # make copy of params as we need to split them into yum repo only and file params + yum_repo_params = module.params.copy() + for alias in module.aliases: + yum_repo_params.pop(alias, None) + + file_common_params = {} + for param in FILE_COMMON_ARGUMENTS: + file_common_params[param] = yum_repo_params.pop(param) + + state = yum_repo_params.pop("state") + name = yum_repo_params['name'] + yum_repo_params['name'] = yum_repo_params.pop('description') + + for list_param in ('baseurl', 'gpgkey'): + v = yum_repo_params[list_param] + if v is not None: + yum_repo_params[list_param] = '\n'.join(v) + + for list_param in ('exclude', 'includepkgs'): + v = yum_repo_params[list_param] + if v is not None: + yum_repo_params[list_param] = ' '.join(v) + + repos_dir = yum_repo_params.pop("reposdir") + if not os.path.isdir(repos_dir): + module.fail_json( + msg="Repo directory '%s' does not exist." % repos_dir + ) + + if (file := yum_repo_params.pop("file")) is None: + file = name + file_common_params["dest"] = os.path.join(repos_dir, f"{file}.repo") + + yumrepo = YumRepo(module, yum_repo_params, name, file_common_params["dest"]) - # Check if required parameters are present - if state == 'present': - if ( - module.params['baseurl'] is None and - module.params['metalink'] is None and - module.params['mirrorlist'] is None): - module.fail_json( - msg="Parameter 'baseurl', 'metalink' or 'mirrorlist' is required.") - if module.params['description'] is None: - module.fail_json( - msg="Parameter 'description' is required.") - - # Rename "name" and "description" to ensure correct key sorting - module.params['repoid'] = module.params['name'] - module.params['name'] = module.params['description'] - del module.params['description'] - - # Change list type to string for baseurl and gpgkey - for list_param in ['baseurl', 'gpgkey']: - if ( - list_param in module.params and - module.params[list_param] is not None): - module.params[list_param] = "\n".join(module.params[list_param]) - - # Define repo file name if it doesn't exist - if module.params['file'] is None: - module.params['file'] = module.params['repoid'] - - # Instantiate the YumRepo object - yumrepo = YumRepo(module) - - # Get repo status before change diff = { - 'before_header': yumrepo.params['dest'], + 'before_header': file_common_params["dest"], 'before': yumrepo.dump(), - 'after_header': yumrepo.params['dest'], + 'after_header': file_common_params["dest"], 'after': '' } - # Perform action depending on the state if state == 'present': yumrepo.add() elif state == 'absent': yumrepo.remove() - # Get repo status after change diff['after'] = yumrepo.dump() - # Compare repo states changed = diff['before'] != diff['after'] - # Save the file only if not in check mode and if there was a change if not module.check_mode and changed: yumrepo.save() - # Change file attributes if needed - if os.path.isfile(module.params['dest']): - file_args = module.load_file_common_arguments(module.params) + if os.path.isfile(file_common_params["dest"]): + file_args = module.load_file_common_arguments(file_common_params) changed = module.set_fs_attributes_if_different(file_args, changed) - # Print status of the change module.exit_json(changed=changed, repo=name, state=state, diff=diff)