#!/usr/bin/python # Copyright (c) 2017, Giovanni Sciortino (@giovannisciortino) # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations DOCUMENTATION = r""" module: rhsm_repository short_description: Manage RHSM repositories using the subscription-manager command description: - Manage (Enable/Disable) RHSM repositories to the Red Hat Subscription Management entitlement platform using the C(subscription-manager) command. author: Giovanni Sciortino (@giovannisciortino) notes: - In order to manage RHSM repositories the system must be already registered to RHSM manually or using the Ansible M(community.general.redhat_subscription) module. - It is possible to interact with C(subscription-manager) only as root, so root permissions are required to successfully run this module. requirements: - subscription-manager extends_documentation_fragment: - community.general.attributes attributes: check_mode: support: full diff_mode: support: full options: state: description: - If state is equal to present or disabled, indicates the desired repository state. - In community.general 10.0.0 the states V(present) and V(absent) have been removed. Please use V(enabled) and V(disabled) instead. choices: [enabled, disabled] default: "enabled" type: str name: description: - The ID of repositories to enable. - To operate on several repositories this can accept a comma separated list or a YAML list. required: true type: list elements: str purge: description: - Disable all currently enabled repositories that are not not specified in O(name). Only set this to V(true) if passing in a list of repositories to the O(name) field. Using this with C(loop) is likely not to have the desired result. type: bool default: false """ EXAMPLES = r""" - name: Enable a RHSM repository community.general.rhsm_repository: name: rhel-7-server-rpms - name: Disable all RHSM repositories community.general.rhsm_repository: name: '*' state: disabled - name: Enable all repositories starting with rhel-6-server community.general.rhsm_repository: name: rhel-6-server* state: enabled - name: Disable all repositories except rhel-7-server-rpms community.general.rhsm_repository: name: rhel-7-server-rpms purge: true """ RETURN = r""" repositories: description: - The list of RHSM repositories with their states. - When this module is used to change the repository states, this list contains the updated states after the changes. returned: success type: list """ import os from copy import deepcopy from fnmatch import fnmatch from ansible.module_utils.basic import AnsibleModule class Rhsm: def __init__(self, module): self.module = module self.rhsm_bin = self.module.get_bin_path("subscription-manager", required=True) self.rhsm_kwargs = { "environ_update": dict(LANG="C", LC_ALL="C", LC_MESSAGES="C"), "expand_user_and_vars": False, "use_unsafe_shell": False, } def run_repos(self, arguments): """ Execute `subscription-manager repos` with arguments and manage common errors """ rc, out, err = self.module.run_command([self.rhsm_bin, "repos"] + arguments, **self.rhsm_kwargs) if rc == 0 and out == "This system has no repositories available through subscriptions.\n": self.module.fail_json(msg="This system has no repositories available through subscriptions") elif rc == 1: self.module.fail_json(msg=f"subscription-manager failed with the following error: {err}") else: return rc, out, err def list_repositories(self): """ Generate RHSM repository list and return a list of dict """ rc, out, err = self.run_repos(["--list"]) repo_id = "" repo_name = "" repo_url = "" repo_enabled = "" repo_result = [] for line in out.splitlines(): # ignore lines that are: # - empty # - "+---------[...]" -- i.e. header # - " Available Repositories [...]" -- i.e. header if line == "" or line[0] == "+" or line[0] == " ": continue if line.startswith("Repo ID: "): repo_id = line[9:].lstrip() continue if line.startswith("Repo Name: "): repo_name = line[11:].lstrip() continue if line.startswith("Repo URL: "): repo_url = line[10:].lstrip() continue if line.startswith("Enabled: "): repo_enabled = line[9:].lstrip() repo = { "id": repo_id, "name": repo_name, "url": repo_url, "enabled": True if repo_enabled == "1" else False, } repo_result.append(repo) return repo_result def repository_modify(module, rhsm, state, name, purge=False): name = set(name) current_repo_list = rhsm.list_repositories() updated_repo_list = deepcopy(current_repo_list) matched_existing_repo = {} for repoid in name: matched_existing_repo[repoid] = [] for idx, repo in enumerate(current_repo_list): if fnmatch(repo["id"], repoid): matched_existing_repo[repoid].append(repo) # Update current_repo_list to return it as result variable updated_repo_list[idx]["enabled"] = True if state == "enabled" else False changed = False results = [] diff_before = "" diff_after = "" rhsm_arguments = [] for repoid in matched_existing_repo: if len(matched_existing_repo[repoid]) == 0: results.append(f"{repoid} is not a valid repository ID") module.fail_json(results=results, msg=f"{repoid} is not a valid repository ID") for repo in matched_existing_repo[repoid]: if state in ["disabled", "absent"]: if repo["enabled"]: changed = True diff_before += f"Repository '{repo['id']}' is enabled for this system\n" diff_after += f"Repository '{repo['id']}' is disabled for this system\n" results.append(f"Repository '{repo['id']}' is disabled for this system") rhsm_arguments += ["--disable", repo["id"]] elif state in ["enabled", "present"]: if not repo["enabled"]: changed = True diff_before += f"Repository '{repo['id']}' is disabled for this system\n" diff_after += f"Repository '{repo['id']}' is enabled for this system\n" results.append(f"Repository '{repo['id']}' is enabled for this system") rhsm_arguments += ["--enable", repo["id"]] # Disable all enabled repos on the system that are not in the task and not # marked as disabled by the task if purge: enabled_repo_ids = {repo["id"] for repo in updated_repo_list if repo["enabled"]} matched_repoids_set = set(matched_existing_repo.keys()) difference = enabled_repo_ids.difference(matched_repoids_set) if len(difference) > 0: for repoid in difference: changed = True diff_before.join(f"Repository '{repoid}'' is enabled for this system\n") diff_after.join(f"Repository '{repoid}' is disabled for this system\n") results.append(f"Repository '{repoid}' is disabled for this system") rhsm_arguments.extend(["--disable", repoid]) for updated_repo in updated_repo_list: if updated_repo["id"] in difference: updated_repo["enabled"] = False diff = { "before": diff_before, "after": diff_after, "before_header": "RHSM repositories", "after_header": "RHSM repositories", } if not module.check_mode and changed: rc, out, err = rhsm.run_repos(rhsm_arguments) results = out.splitlines() module.exit_json(results=results, changed=changed, repositories=updated_repo_list, diff=diff) def main(): module = AnsibleModule( argument_spec=dict( name=dict(type="list", elements="str", required=True), state=dict(choices=["enabled", "disabled"], default="enabled"), purge=dict(type="bool", default=False), ), supports_check_mode=True, ) if os.getuid() != 0: module.fail_json(msg="Interacting with subscription-manager requires root permissions ('become: true')") rhsm = Rhsm(module) name = module.params["name"] state = module.params["state"] purge = module.params["purge"] repository_modify(module, rhsm, state, name, purge) if __name__ == "__main__": main()