diff --git a/.gitignore b/.gitignore index e19f668..b24fa94 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,4 @@ playbooks/debug.yml # Editors .*.sw[klmnop] +/tests/vars/satellite.yml diff --git a/README.md b/README.md index b3dc705..c502846 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ Role Variables | `rhsm_autosubscribe` | `[undefined]` | Whether or not to autosubscribe to available repositories. | | `rhsm_consumer_hostname` | `[undefined]` | Name of the system to use when registering. Defaults to using the system hostname if undefined. | | `rhsm_force_register` | `False` | Whether or not to force registration. | -| `rhsm_repos` | `[]` | The list of repositories to enable or disable. See `defaults/main.yml` for examples. | +| `rhsm_repos` | `[]` | The list of repositories to enable or disable. | +| `rhsm_repos_state` | `[undefined]` | The state of all repos in `rhsm_repos`. The module default is `enabled`.| +| `rhsm_repos_purge` | `[undefined]` | Whether or not to disable repos not specified in `rhsm_repos`. The module default is `False`. | | `rhsm_rhsm_port` | `443` | Port to use when connecting to subscription server. | | `rhsm_server_hostname` | `subscription.rhn.redhat.com` | FQDN of subscription server. | | `rhsm_server_prefix` | `/subscription` or `/rhsm` | RHS server prefix. `/subscription` when using registering via `portal`, `/rhsm` when registering via `satellite`. | diff --git a/defaults/main.yml b/defaults/main.yml index 740c45e..606bfee 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -10,9 +10,6 @@ rhsm_method: portal # portal or satellite rhsm_force_register: False rhsm_repos: [] - # - name: rhel-7-server-extras-rpms # wildcard or repo name - # state: enabled # enabled or disabled - ## /etc/rhsm/rhsm.conf settings rhsm_rhsm_port: 443 diff --git a/library/rhsm_repository.py b/library/rhsm_repository.py new file mode 100644 index 0000000..c961852 --- /dev/null +++ b/library/rhsm_repository.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +# +# Copyright (c) 2017 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import re + +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy +from fnmatch import fnmatch + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +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. +version_added: '2.5' +author: Giovanni Sciortino (@giovannisciortino) +notes: + - In order to manage RHSM repositories the system must be already registered + to RHSM manually or using the Ansible C(redhat_subscription) module. + +requirements: + - subscription-manager +options: + state: + description: + - If state is equal to present or disabled, indicates the desired + repository state. + choices: [present, enabled, absent, disabled] + required: True + default: "present" + 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 + purge: + description: + - Disable any repositories not listed in the task. + type: bool + default: False +''' + +EXAMPLES = ''' +- name: Enable a RHSM repository + rhsm_repository: + name: rhel-7-server-rpms + +- name: Disable all RHSM repositories + rhsm_repository: + name: '*' + state: disabled + +- name: Enable all repositories starting with rhel-6-server + rhsm_repository: + name: rhel-6-server* + state: enabled + +- name: Disable all repositories except rhel-7-server-rpms + rhsm_repository: + name: rhel-7-server-rpms + purge: True +''' + +RETURN = ''' +repositories: + description: + - The list of RHSM repositories with their states. + - When this module is used to change the repositories states, this list + contains the updated states after the changes. + returned: success + type: list +''' + + +def run_subscription_manager(module, arguments): + # Execute subuscription-manager with arguments and manage common errors + rhsm_bin = module.get_bin_path('subscription-manager') + if not rhsm_bin: + module.fail_json( + msg='The executable file subscription-manager was ' + 'not found in PATH') + + lang_env = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C') + rc, out, err = module.run_command("%s %s" % (rhsm_bin, " ".join( + arguments)), environ_update=lang_env) + + if rc == 1 and (err == 'The password you typed is invalid.\nPlease try ' + 'again.\n' or os.getuid() != 0): + module.fail_json(msg='The executable file subscription-manager must ' + 'be run using root privileges') + elif rc == 0 and out == \ + 'This system has no repositories available through subscriptions.\n': + module.fail_json( + msg='This system has no repositories available through ' + 'subscriptions') + elif rc == 1: + module.fail_json(msg='subscription-manager failed with the following ' + 'error: %s' % err) + else: + return rc, out, err + + +def get_repository_list(module, list_parameter): + # Generate RHSM repository list and return a list of dict + if list_parameter == 'list_enabled': + rhsm_arguments = ['repos', '--list-enabled'] + elif list_parameter == 'list_disabled': + rhsm_arguments = ['repos', '--list-disabled'] + elif list_parameter == 'list': + rhsm_arguments = ['repos', '--list'] + rc, out, err = run_subscription_manager(module, rhsm_arguments) + + skip_lines = [ + '+----------------------------------------------------------+', + ' Available Repositories in /etc/yum.repos.d/redhat.repo' + ] + repo_id_re = re.compile(r'Repo ID:\s+(.*)') + repo_name_re = re.compile(r'Repo Name:\s+(.*)') + repo_url_re = re.compile(r'Repo URL:\s+(.*)') + repo_enabled_re = re.compile(r'Enabled:\s+(.*)') + + repo_id = '' + repo_name = '' + repo_url = '' + repo_enabled = '' + + repo_result = [] + for line in out.splitlines(): + if line == '' or line in skip_lines: + continue + + repo_id_match = repo_id_re.match(line) + if repo_id_match: + repo_id = repo_id_match.group(1) + continue + + repo_name_match = repo_name_re.match(line) + if repo_name_match: + repo_name = repo_name_match.group(1) + continue + + repo_url_match = repo_url_re.match(line) + if repo_url_match: + repo_url = repo_url_match.group(1) + continue + + repo_enabled_match = repo_enabled_re.match(line) + if repo_enabled_match: + repo_enabled = repo_enabled_match.group(1) + + 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, state, name, purge=False): + name = set(name) + current_repo_list = get_repository_list(module, 'list') + 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 = ['repos'] + + for repoid in matched_existing_repo: + if len(matched_existing_repo[repoid]) == 0: + results.append("%s is not a valid repository ID" % repoid) + module.fail_json( + results=results, + msg="%s is not a valid repository ID" % repoid) + for repo in matched_existing_repo[repoid]: + if state in ['disabled', 'absent']: + if repo['enabled']: + changed = True + diff_before += "Repository '%s' is enabled for this " \ + "system\n" % repo['id'] + diff_after += "Repository '%s' is disabled for this " \ + "system\n" % repo['id'] + results.append( + "Repository '%s' is disabled for this system" % repo['id']) + rhsm_arguments += ['--disable', repo['id']] + + elif state in ['enabled', 'present']: + if not repo['enabled']: + changed = True + diff_before += "Repository '%s' is disabled for this " \ + "system\n" % repo['id'] + diff_after += "Repository '%s' is enabled for this " \ + "system\n" % repo['id'] + results.append("Repository '%s' is enabled for this " + "system" % repo['id']) + 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 = set(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("Repository '{repoid}'' is enabled for this " + "system\n".format(repoid=repoid)) + diff_after.join("Repository '{repoid}' is disabled for this " + "system\n".format(repoid=repoid)) + results.append("Repository '{repoid}' is disabled for this " + "system".format(repoid=repoid)) + rhsm_arguments.extend(['--disable', repoid]) + + diff = {'before': diff_before, + 'after': diff_after, + 'before_header': "RHSM repositories", + 'after_header': "RHSM repositories"} + + if not module.check_mode: + rc, out, err = run_subscription_manager(module, 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', required=True), + state=dict( + choices=['enabled', 'disabled', 'present', 'absent'], + default='enabled'), + purge=dict(type='bool', default=False), + ), + supports_check_mode=True, + ) + name = module.params['name'] + state = module.params['state'] + purge = module.params['purge'] + + repository_modify(module, state, name, purge) + + +if __name__ == '__main__': + main() diff --git a/tasks/portal.yml b/tasks/portal.yml index f19bed1..bdc0eb5 100644 --- a/tasks/portal.yml +++ b/tasks/portal.yml @@ -1,25 +1,38 @@ -- name: Manage Red Hat subscription - redhat_subscription: - username: "{{ rhsm_username | default(omit) }}" - password: "{{ rhsm_password | default(omit) }}" - activationkey: "{{ rhsm_activation_key | default(omit) }}" - org_id: "{{ rhsm_org_id | default(omit) }}" - pool_ids: "{{ rhsm_pool_ids | default(omit) }}" - state: "{{ rhsm_state | default(omit) }}" - autosubscribe: "{{ rhsm_autosubscribe | default(omit) }}" - consumer_name: "{{ rhsm_consumer_hostname | default(omit) }}" - force_register: "{{ rhsm_force_register | default(omit) }}" - rhsm_baseurl: "{{ rhsm_baseurl | default(omit) }}" - tags: - - rhsm - - rhsm_register +- block: + - import_tasks: register.yml + + rescue: + - block: + - name: Manage Red Hat subscription (force) + redhat_subscription: + username: "{{ rhsm_username | default(omit) }}" + password: "{{ rhsm_password | default(omit) }}" + activationkey: "{{ rhsm_activation_key | default(omit) }}" + org_id: "{{ rhsm_org_id | default(omit) }}" + pool_ids: "{{ rhsm_pool_ids | default(omit) }}" + state: "{{ rhsm_state | default(omit) }}" + autosubscribe: "{{ rhsm_autosubscribe | default(omit) }}" + consumer_name: "{{ rhsm_consumer_hostname | default(omit) }}" + force_register: True + rhsm_baseurl: "{{ rhsm_baseurl | default(omit) }}" + register: subscrition_result + tags: + - rhsm + - rhsm_register + + rescue: + - name: Clean all local subscription data + command: subscription-manager clean + + - import_tasks: register.yml - name: Configure repository subscriptions rhsm_repository: - name: "{{ item.name | default(item) }}" - state: "{{ item.state | default('enabled') }}" - with_items: "{{ rhsm_repos | default([]) }}" + name: "{{ rhsm_repos }}" + purge: "{{ rhsm_repos_purge | default(omit) }}" + state: "{{ rhsm_repos_state | default(omit) }}" when: + - rhsm_repos | length > 0 - rhsm_state == 'present' - rhsm_activation_key is not defined tags: diff --git a/tasks/register.yml b/tasks/register.yml new file mode 100644 index 0000000..0296cb3 --- /dev/null +++ b/tasks/register.yml @@ -0,0 +1,16 @@ +- name: Manage Red Hat subscription + redhat_subscription: + username: "{{ rhsm_username | default(omit) }}" + password: "{{ rhsm_password | default(omit) }}" + activationkey: "{{ rhsm_activation_key | default(omit) }}" + org_id: "{{ rhsm_org_id | default(omit) }}" + pool_ids: "{{ rhsm_pool_ids | default(omit) }}" + state: "{{ rhsm_state | default(omit) }}" + autosubscribe: "{{ rhsm_autosubscribe | default(omit) }}" + consumer_name: "{{ rhsm_consumer_hostname | default(omit) }}" + force_register: "{{ rhsm_force_register | default(omit) }}" + rhsm_baseurl: "{{ rhsm_baseurl | default(omit) }}" + register: subscrition_result + tags: + - rhsm + - rhsm_register diff --git a/tests/Vagrantfile b/tests/Vagrantfile index 83db94e..01e4d58 100644 --- a/tests/Vagrantfile +++ b/tests/Vagrantfile @@ -24,6 +24,7 @@ Vagrant.configure(2) do |config| config.vm.provision "ansible" do |ansible| ansible.playbook = 'vagrant.yml' + ansible.extra_vars = 'vars/portal.yml' ansible.compatibility_mode = '2.0' end end diff --git a/tests/vagrant.yml b/tests/vagrant.yml index 1277415..90e138d 100644 --- a/tests/vagrant.yml +++ b/tests/vagrant.yml @@ -2,24 +2,5 @@ remote_user: vagrant become: yes - vars: - rhsm_username: "{{ lookup('env', 'RHSM_USERNAME') }}" - rhsm_password: "{{ lookup('env', 'RHSM_PASSWORD') }}" - rhsm_pool_ids: "{{ lookup('env', 'RHSM_POOL_ID') }}" - rhsm_state: absent - # rhsm_force_register: yes - rhsm_repos: "{{ repos[ansible_distribution ~ '-' ~ ansible_distribution_major_version] }}" - - repos: - RedHat-6: - - name: rhel-{{ ansible_distribution_major_version }}-server-extras-rpms - state: present - - name: rhel-{{ ansible_distribution_major_version }}-server-rh-common-rpms - RedHat-7: - - name: rhel-{{ ansible_distribution_major_version }}-server-extras-rpms - state: present - - name: rhel-{{ ansible_distribution_major_version }}-server-rh-common-rpms - - name: rhel-{{ ansible_distribution_major_version }}-server-openstack-12-tools-rpms - roles: - ansible-role-redhat-subscription diff --git a/tests/vars.yml b/tests/vars.yml deleted file mode 100644 index 949dca4..0000000 --- a/tests/vars.yml +++ /dev/null @@ -1,16 +0,0 @@ -rhsm_username: "{{ lookup('env', 'RHSM_USERNAME') }}" -rhsm_password: "{{ lookup('env', 'RHSM_PASSWORD') }}" -rhsm_pool_ids: "{{ lookup('env', 'RHSM_POOL_ID') }}" -rhsm_state: present -rhsm_repos: "{{ repos[ansible_distribution ~ '-' ~ ansible_distribution_major_version] }}" - -repos: - RedHat-6: - - name: rhel-{{ ansible_distribution_major_version }}-server-extras-rpms - state: present - - name: rhel-{{ ansible_distribution_major_version }}-server-rh-common-rpms - RedHat-7: - - name: rhel-{{ ansible_distribution_major_version }}-server-extras-rpms - state: present - - name: rhel-{{ ansible_distribution_major_version }}-server-rh-common-rpms - - name: rhel-{{ ansible_distribution_major_version }}-server-openstack-12-tools-rpms diff --git a/tests/vars/portal.yml b/tests/vars/portal.yml new file mode 100644 index 0000000..a9e6a19 --- /dev/null +++ b/tests/vars/portal.yml @@ -0,0 +1,15 @@ +rhsm_username: "{{ lookup('env', 'RHSM_USERNAME') }}" +rhsm_password: "{{ lookup('env', 'RHSM_PASSWORD') }}" +rhsm_pool_ids: "{{ lookup('env', 'RHSM_POOL_ID') }}" +rhsm_repos: "{{ repos[ansible_distribution ~ '-' ~ ansible_distribution_major_version] }}" +# rhsm_state: present +# rhsm_repos_purge: True + +repos: + RedHat-6: + - rhel-{{ ansible_distribution_major_version }}-server-extras-rpms + - rhel-{{ ansible_distribution_major_version }}-server-rh-common-rpms + RedHat-7: + - rhel-{{ ansible_distribution_major_version }}-server-extras-rpms + - rhel-{{ ansible_distribution_major_version }}-server-rh-common-rpms + # - rhel-{{ ansible_distribution_major_version }}-server-openstack-12-tools-rpms