From ec6496024f45a2aea65fb6506db8809ab33fcfbb Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 7 Oct 2024 23:37:44 +0200 Subject: [PATCH] Prepare 10.0.0 release (#8921) * Bump version to 10.0.0, remove deprecated modules and plugins. * Remove redhat module utils. * Drop support for ansible-core 2.13 and ansible-core 2.14. --- .github/BOTMETA.yml | 8 - .github/workflows/ansible-test.yml | 74 +- README.md | 2 +- changelogs/fragments/removals.yml | 10 + galaxy.yml | 2 +- meta/runtime.yml | 10 +- plugins/callback/hipchat.py | 240 ------ plugins/module_utils/redhat.py | 76 -- plugins/modules/consul_acl.py | 695 ------------------ plugins/modules/rhn_channel.py | 210 ------ plugins/modules/rhn_register.py | 465 ------------ tests/sanity/extra/botmeta.py | 1 - tests/unit/plugins/modules/rhn_conftest.py | 35 - .../unit/plugins/modules/test_rhn_channel.py | 147 ---- .../unit/plugins/modules/test_rhn_register.py | 293 -------- 15 files changed, 30 insertions(+), 2238 deletions(-) create mode 100644 changelogs/fragments/removals.yml delete mode 100644 plugins/callback/hipchat.py delete mode 100644 plugins/module_utils/redhat.py delete mode 100644 plugins/modules/consul_acl.py delete mode 100644 plugins/modules/rhn_channel.py delete mode 100644 plugins/modules/rhn_register.py delete mode 100644 tests/unit/plugins/modules/rhn_conftest.py delete mode 100644 tests/unit/plugins/modules/test_rhn_channel.py delete mode 100644 tests/unit/plugins/modules/test_rhn_register.py diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index be0bf6da30..bcf300025f 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -61,7 +61,6 @@ files: $callbacks/elastic.py: keywords: apm observability maintainers: v1v - $callbacks/hipchat.py: {} $callbacks/jabber.py: {} $callbacks/log_plays.py: {} $callbacks/loganalytics.py: @@ -1161,12 +1160,6 @@ files: keywords: kvm libvirt proxmox qemu labels: rhevm virt maintainers: $team_virt TimothyVandenbrande - $modules/rhn_channel.py: - labels: rhn_channel - maintainers: vincentvdk alikins $team_rhn - $modules/rhn_register.py: - labels: rhn_register - maintainers: jlaska $team_rhn $modules/rhsm_release.py: maintainers: seandst $team_rhsm $modules/rhsm_repository.py: @@ -1554,7 +1547,6 @@ macros: team_oracle: manojmeda mross22 nalsaber team_purestorage: bannaych dnix101 genegr lionmax opslounge raekins sdodsley sile16 team_redfish: mraineri tomasg2012 xmadsen renxulei rajeevkallur bhavya06 jyundt - team_rhn: FlossWare alikins barnabycourt vritant team_rhsm: cnsnyder ptoscano team_scaleway: remyleone abarbare team_solaris: bcoca fishman jasperla jpdasma mator scathatheworm troy2914 xen0l diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 89a3006f56..ca06791a38 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -29,8 +29,6 @@ jobs: strategy: matrix: ansible: - - '2.13' - - '2.14' - '2.15' # Ansible-test on various stable branches does not yet work well with cgroups v2. # Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04 @@ -67,16 +65,8 @@ jobs: exclude: - ansible: '' include: - - ansible: '2.13' + - ansible: '2.15' python: '2.7' - - ansible: '2.13' - python: '3.8' - - ansible: '2.13' - python: '2.7' - - ansible: '2.13' - python: '3.8' - - ansible: '2.14' - python: '3.9' - ansible: '2.15' python: '3.5' - ansible: '2.15' @@ -121,57 +111,19 @@ jobs: exclude: - ansible: '' include: - # 2.13 - - ansible: '2.13' - docker: fedora35 - python: '' - target: azp/posix/1/ - - ansible: '2.13' - docker: fedora35 - python: '' - target: azp/posix/2/ - - ansible: '2.13' - docker: fedora35 - python: '' - target: azp/posix/3/ - - ansible: '2.13' - docker: opensuse15py2 - python: '' - target: azp/posix/1/ - - ansible: '2.13' - docker: opensuse15py2 - python: '' - target: azp/posix/2/ - - ansible: '2.13' - docker: opensuse15py2 - python: '' - target: azp/posix/3/ - - ansible: '2.13' - docker: alpine3 - python: '' - target: azp/posix/1/ - - ansible: '2.13' - docker: alpine3 - python: '' - target: azp/posix/2/ - - ansible: '2.13' - docker: alpine3 - python: '' - target: azp/posix/3/ - # 2.14 - - ansible: '2.14' - docker: alpine3 - python: '' - target: azp/posix/1/ - - ansible: '2.14' - docker: alpine3 - python: '' - target: azp/posix/2/ - - ansible: '2.14' - docker: alpine3 - python: '' - target: azp/posix/3/ # 2.15 + - ansible: '2.15' + docker: alpine3 + python: '' + target: azp/posix/1/ + - ansible: '2.15' + docker: alpine3 + python: '' + target: azp/posix/2/ + - ansible: '2.15' + docker: alpine3 + python: '' + target: azp/posix/3/ - ansible: '2.15' docker: fedora37 python: '' diff --git a/README.md b/README.md index 4edd58edb3..03dad49f39 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ For more information about communication, see the [Ansible communication guide]( ## Tested with Ansible -Tested with the current ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, ansible-core 2.17, ansible-core 2.18 releases and the current development version of ansible-core. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases. +Tested with the current ansible-core 2.15, ansible-core 2.16, ansible-core 2.17, ansible-core 2.18 releases and the current development version of ansible-core. Ansible-core versions before 2.15.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases. ## External requirements diff --git a/changelogs/fragments/removals.yml b/changelogs/fragments/removals.yml new file mode 100644 index 0000000000..1a1f137194 --- /dev/null +++ b/changelogs/fragments/removals.yml @@ -0,0 +1,10 @@ +removed_features: + - "The hipchat callback plugin has been removed. The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020 (https://github.com/ansible-collections/community.general/pull/8921)." + - "The consul_acl module has been removed. Use community.general.consul_token and/or community.general.consul_policy instead (https://github.com/ansible-collections/community.general/pull/8921)." + - "The rhn_channel module has been removed (https://github.com/ansible-collections/community.general/pull/8921)." + - "The rhn_register module has been removed (https://github.com/ansible-collections/community.general/pull/8921)." + - "The redhat module utils has been removed (https://github.com/ansible-collections/community.general/pull/8921)." +breaking_changes: + - The collection no longer supports ansible-core 2.13 and ansible-core 2.14. + While most (or even all) modules and plugins might still work with these versions, they are no longer tested in CI and breakages regarding them will not be fixed + (https://github.com/ansible-collections/community.general/pull/8921)." diff --git a/galaxy.yml b/galaxy.yml index 5112bdc64f..3af5356d06 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -5,7 +5,7 @@ namespace: community name: general -version: 9.5.0 +version: 10.0.0 readme: README.md authors: - Ansible (https://github.com/ansible) diff --git a/meta/runtime.yml b/meta/runtime.yml index 5d4ed8cb89..f5adb64712 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -3,7 +3,7 @@ # 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 -requires_ansible: '>=2.13.0' +requires_ansible: '>=2.15.0' action_groups: consul: - consul_agent_check @@ -44,7 +44,7 @@ plugin_routing: warning_text: Use the 'default' callback plugin with 'display_skipped_hosts = no' option. hipchat: - deprecation: + tombstone: removal_version: 10.0.0 warning_text: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020. osx_say: @@ -72,7 +72,7 @@ plugin_routing: redirect: infoblox.nios_modules.nios_next_network modules: consul_acl: - deprecation: + tombstone: removal_version: 10.0.0 warning_text: Use community.general.consul_token and/or community.general.consul_policy instead. hipchat: @@ -184,12 +184,12 @@ plugin_routing: removal_version: 9.0.0 warning_text: This module relied on the deprecated package pyrax. rhn_channel: - deprecation: + tombstone: removal_version: 10.0.0 warning_text: RHN is EOL, please contact the community.general maintainers if still using this; see the module documentation for more details. rhn_register: - deprecation: + tombstone: removal_version: 10.0.0 warning_text: RHN is EOL, please contact the community.general maintainers if still using this; see the module documentation for more details. diff --git a/plugins/callback/hipchat.py b/plugins/callback/hipchat.py deleted file mode 100644 index bf0d425303..0000000000 --- a/plugins/callback/hipchat.py +++ /dev/null @@ -1,240 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2014, Matt Martz -# Copyright (c) 2017 Ansible Project -# 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 (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = ''' - author: Unknown (!UNKNOWN) - name: hipchat - type: notification - requirements: - - whitelist in configuration. - - prettytable (python lib) - short_description: post task events to hipchat - description: - - This callback plugin sends status updates to a HipChat channel during playbook execution. - - Before 2.4 only environment variables were available for configuring this plugin. - deprecated: - removed_in: 10.0.0 - why: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020. - alternative: There is none. - options: - token: - description: HipChat API token for v1 or v2 API. - type: str - required: true - env: - - name: HIPCHAT_TOKEN - ini: - - section: callback_hipchat - key: token - api_version: - description: HipChat API version, v1 or v2. - type: str - choices: - - v1 - - v2 - required: false - default: v1 - env: - - name: HIPCHAT_API_VERSION - ini: - - section: callback_hipchat - key: api_version - room: - description: HipChat room to post in. - type: str - default: ansible - env: - - name: HIPCHAT_ROOM - ini: - - section: callback_hipchat - key: room - from: - description: Name to post as - type: str - default: ansible - env: - - name: HIPCHAT_FROM - ini: - - section: callback_hipchat - key: from - notify: - description: Add notify flag to important messages - type: bool - default: true - env: - - name: HIPCHAT_NOTIFY - ini: - - section: callback_hipchat - key: notify - -''' - -import os -import json - -try: - import prettytable - HAS_PRETTYTABLE = True -except ImportError: - HAS_PRETTYTABLE = False - -from ansible.plugins.callback import CallbackBase -from ansible.module_utils.six.moves.urllib.parse import urlencode -from ansible.module_utils.urls import open_url - - -class CallbackModule(CallbackBase): - """This is an example ansible callback plugin that sends status - updates to a HipChat channel during playbook execution. - """ - - CALLBACK_VERSION = 2.0 - CALLBACK_TYPE = 'notification' - CALLBACK_NAME = 'community.general.hipchat' - CALLBACK_NEEDS_WHITELIST = True - - API_V1_URL = 'https://api.hipchat.com/v1/rooms/message' - API_V2_URL = 'https://api.hipchat.com/v2/' - - def __init__(self): - - super(CallbackModule, self).__init__() - - if not HAS_PRETTYTABLE: - self.disabled = True - self._display.warning('The `prettytable` python module is not installed. ' - 'Disabling the HipChat callback plugin.') - self.printed_playbook = False - self.playbook_name = None - self.play = None - - def set_options(self, task_keys=None, var_options=None, direct=None): - super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct) - - self.token = self.get_option('token') - self.api_version = self.get_option('api_version') - self.from_name = self.get_option('from') - self.allow_notify = self.get_option('notify') - self.room = self.get_option('room') - - if self.token is None: - self.disabled = True - self._display.warning('HipChat token could not be loaded. The HipChat ' - 'token can be provided using the `HIPCHAT_TOKEN` ' - 'environment variable.') - - # Pick the request handler. - if self.api_version == 'v2': - self.send_msg = self.send_msg_v2 - else: - self.send_msg = self.send_msg_v1 - - def send_msg_v2(self, msg, msg_format='text', color='yellow', notify=False): - """Method for sending a message to HipChat""" - - headers = {'Authorization': 'Bearer %s' % self.token, 'Content-Type': 'application/json'} - - body = {} - body['room_id'] = self.room - body['from'] = self.from_name[:15] # max length is 15 - body['message'] = msg - body['message_format'] = msg_format - body['color'] = color - body['notify'] = self.allow_notify and notify - - data = json.dumps(body) - url = self.API_V2_URL + "room/{room_id}/notification".format(room_id=self.room) - try: - response = open_url(url, data=data, headers=headers, method='POST') - return response.read() - except Exception as ex: - self._display.warning('Could not submit message to hipchat: {0}'.format(ex)) - - def send_msg_v1(self, msg, msg_format='text', color='yellow', notify=False): - """Method for sending a message to HipChat""" - - params = {} - params['room_id'] = self.room - params['from'] = self.from_name[:15] # max length is 15 - params['message'] = msg - params['message_format'] = msg_format - params['color'] = color - params['notify'] = int(self.allow_notify and notify) - - url = ('%s?auth_token=%s' % (self.API_V1_URL, self.token)) - try: - response = open_url(url, data=urlencode(params)) - return response.read() - except Exception as ex: - self._display.warning('Could not submit message to hipchat: {0}'.format(ex)) - - def v2_playbook_on_play_start(self, play): - """Display Playbook and play start messages""" - - self.play = play - name = play.name - # This block sends information about a playbook when it starts - # The playbook object is not immediately available at - # playbook_on_start so we grab it via the play - # - # Displays info about playbook being started by a person on an - # inventory, as well as Tags, Skip Tags and Limits - if not self.printed_playbook: - self.playbook_name, dummy = os.path.splitext(os.path.basename(self.play.playbook.filename)) - host_list = self.play.playbook.inventory.host_list - inventory = os.path.basename(os.path.realpath(host_list)) - self.send_msg("%s: Playbook initiated by %s against %s" % - (self.playbook_name, - self.play.playbook.remote_user, - inventory), notify=True) - self.printed_playbook = True - subset = self.play.playbook.inventory._subset - skip_tags = self.play.playbook.skip_tags - self.send_msg("%s:\nTags: %s\nSkip Tags: %s\nLimit: %s" % - (self.playbook_name, - ', '.join(self.play.playbook.only_tags), - ', '.join(skip_tags) if skip_tags else None, - ', '.join(subset) if subset else subset)) - - # This is where we actually say we are starting a play - self.send_msg("%s: Starting play: %s" % - (self.playbook_name, name)) - - def playbook_on_stats(self, stats): - """Display info about playbook statistics""" - hosts = sorted(stats.processed.keys()) - - t = prettytable.PrettyTable(['Host', 'Ok', 'Changed', 'Unreachable', - 'Failures']) - - failures = False - unreachable = False - - for h in hosts: - s = stats.summarize(h) - - if s['failures'] > 0: - failures = True - if s['unreachable'] > 0: - unreachable = True - - t.add_row([h] + [s[k] for k in ['ok', 'changed', 'unreachable', - 'failures']]) - - self.send_msg("%s: Playbook complete" % self.playbook_name, - notify=True) - - if failures or unreachable: - color = 'red' - self.send_msg("%s: Failures detected" % self.playbook_name, - color=color, notify=True) - else: - color = 'green' - - self.send_msg("/code %s:\n%s" % (self.playbook_name, t), color=color) diff --git a/plugins/module_utils/redhat.py b/plugins/module_utils/redhat.py deleted file mode 100644 index 321386a0a5..0000000000 --- a/plugins/module_utils/redhat.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# This code is part of Ansible, but is an independent component. -# This particular file snippet, and this file snippet only, is BSD licensed. -# Modules you write using this snippet, which is embedded dynamically by Ansible -# still belong to the author of the module, and may assign their own license -# to the complete work. -# -# Copyright (c), James Laska -# -# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause) -# SPDX-License-Identifier: BSD-2-Clause - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - - -import os -import shutil -import tempfile - -from ansible.module_utils.six.moves import configparser - - -class RegistrationBase(object): - """ - DEPRECATION WARNING - - This class is deprecated and will be removed in community.general 10.0.0. - There is no replacement for it; please contact the community.general - maintainers in case you are using it. - """ - - def __init__(self, module, username=None, password=None): - self.module = module - self.username = username - self.password = password - - def configure(self): - raise NotImplementedError("Must be implemented by a sub-class") - - def enable(self): - # Remove any existing redhat.repo - redhat_repo = '/etc/yum.repos.d/redhat.repo' - if os.path.isfile(redhat_repo): - os.unlink(redhat_repo) - - def register(self): - raise NotImplementedError("Must be implemented by a sub-class") - - def unregister(self): - raise NotImplementedError("Must be implemented by a sub-class") - - def unsubscribe(self): - raise NotImplementedError("Must be implemented by a sub-class") - - def update_plugin_conf(self, plugin, enabled=True): - plugin_conf = '/etc/yum/pluginconf.d/%s.conf' % plugin - - if os.path.isfile(plugin_conf): - tmpfd, tmpfile = tempfile.mkstemp() - shutil.copy2(plugin_conf, tmpfile) - cfg = configparser.ConfigParser() - cfg.read([tmpfile]) - - if enabled: - cfg.set('main', 'enabled', 1) - else: - cfg.set('main', 'enabled', 0) - - fd = open(tmpfile, 'w+') - cfg.write(fd) - fd.close() - self.module.atomic_move(tmpfile, plugin_conf) - - def subscribe(self, **kwargs): - raise NotImplementedError("Must be implemented by a sub-class") diff --git a/plugins/modules/consul_acl.py b/plugins/modules/consul_acl.py deleted file mode 100644 index 2d60af0625..0000000000 --- a/plugins/modules/consul_acl.py +++ /dev/null @@ -1,695 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2015, Steve Gargan -# 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 absolute_import, division, print_function -__metaclass__ = type - - -DOCUMENTATION = ''' -module: consul_acl -short_description: Manipulate Consul ACL keys and rules -description: - - Allows the addition, modification and deletion of ACL keys and associated - rules in a consul cluster via the agent. For more details on using and - configuring ACLs, see https://www.consul.io/docs/guides/acl.html. -author: - - Steve Gargan (@sgargan) - - Colin Nolan (@colin-nolan) -extends_documentation_fragment: - - community.general.attributes -attributes: - check_mode: - support: none - diff_mode: - support: none -deprecated: - removed_in: 10.0.0 - why: The legacy ACL system was removed from Consul. - alternative: Use M(community.general.consul_token) and/or M(community.general.consul_policy) instead. -options: - mgmt_token: - description: - - a management token is required to manipulate the acl lists - required: true - type: str - state: - description: - - whether the ACL pair should be present or absent - required: false - choices: ['present', 'absent'] - default: present - type: str - token_type: - description: - - the type of token that should be created - choices: ['client', 'management'] - default: client - type: str - name: - description: - - the name that should be associated with the acl key, this is opaque - to Consul - required: false - type: str - token: - description: - - the token key identifying an ACL rule set. If generated by consul - this will be a UUID - required: false - type: str - rules: - type: list - elements: dict - description: - - rules that should be associated with a given token - required: false - host: - description: - - host of the consul agent defaults to localhost - required: false - default: localhost - type: str - port: - type: int - description: - - the port on which the consul agent is running - required: false - default: 8500 - scheme: - description: - - the protocol scheme on which the consul agent is running - required: false - default: http - type: str - validate_certs: - type: bool - description: - - whether to verify the tls certificate of the consul agent - required: false - default: true -requirements: - - python-consul - - pyhcl - - requests -''' - -EXAMPLES = """ -- name: Create an ACL with rules - community.general.consul_acl: - host: consul1.example.com - mgmt_token: some_management_acl - name: Foo access - rules: - - key: "foo" - policy: read - - key: "private/foo" - policy: deny - -- name: Create an ACL with a specific token - community.general.consul_acl: - host: consul1.example.com - mgmt_token: some_management_acl - name: Foo access - token: my-token - rules: - - key: "foo" - policy: read - -- name: Update the rules associated to an ACL token - community.general.consul_acl: - host: consul1.example.com - mgmt_token: some_management_acl - name: Foo access - token: some_client_token - rules: - - event: "bbq" - policy: write - - key: "foo" - policy: read - - key: "private" - policy: deny - - keyring: write - - node: "hgs4" - policy: write - - operator: read - - query: "" - policy: write - - service: "consul" - policy: write - - session: "standup" - policy: write - -- name: Remove a token - community.general.consul_acl: - host: consul1.example.com - mgmt_token: some_management_acl - token: 172bd5c8-9fe9-11e4-b1b0-3c15c2c9fd5e - state: absent -""" - -RETURN = """ -token: - description: the token associated to the ACL (the ACL's ID) - returned: success - type: str - sample: a2ec332f-04cf-6fba-e8b8-acf62444d3da -rules: - description: the HCL JSON representation of the rules associated to the ACL, in the format described in the - Consul documentation (https://www.consul.io/docs/guides/acl.html#rule-specification). - returned: when O(state=present) - type: dict - sample: { - "key": { - "foo": { - "policy": "write" - }, - "bar": { - "policy": "deny" - } - } - } -operation: - description: the operation performed on the ACL - returned: changed - type: str - sample: update -""" - - -try: - import consul - python_consul_installed = True -except ImportError: - python_consul_installed = False - -try: - import hcl - pyhcl_installed = True -except ImportError: - pyhcl_installed = False - -try: - from requests.exceptions import ConnectionError - has_requests = True -except ImportError: - has_requests = False - -from collections import defaultdict -from ansible.module_utils.basic import to_text, AnsibleModule - - -RULE_SCOPES = [ - "agent", - "agent_prefix", - "event", - "event_prefix", - "key", - "key_prefix", - "keyring", - "node", - "node_prefix", - "operator", - "query", - "query_prefix", - "service", - "service_prefix", - "session", - "session_prefix", -] - -MANAGEMENT_PARAMETER_NAME = "mgmt_token" -HOST_PARAMETER_NAME = "host" -SCHEME_PARAMETER_NAME = "scheme" -VALIDATE_CERTS_PARAMETER_NAME = "validate_certs" -NAME_PARAMETER_NAME = "name" -PORT_PARAMETER_NAME = "port" -RULES_PARAMETER_NAME = "rules" -STATE_PARAMETER_NAME = "state" -TOKEN_PARAMETER_NAME = "token" -TOKEN_TYPE_PARAMETER_NAME = "token_type" - -PRESENT_STATE_VALUE = "present" -ABSENT_STATE_VALUE = "absent" - -CLIENT_TOKEN_TYPE_VALUE = "client" -MANAGEMENT_TOKEN_TYPE_VALUE = "management" - -REMOVE_OPERATION = "remove" -UPDATE_OPERATION = "update" -CREATE_OPERATION = "create" - -_POLICY_JSON_PROPERTY = "policy" -_RULES_JSON_PROPERTY = "Rules" -_TOKEN_JSON_PROPERTY = "ID" -_TOKEN_TYPE_JSON_PROPERTY = "Type" -_NAME_JSON_PROPERTY = "Name" -_POLICY_YML_PROPERTY = "policy" -_POLICY_HCL_PROPERTY = "policy" - -_ARGUMENT_SPEC = { - MANAGEMENT_PARAMETER_NAME: dict(required=True, no_log=True), - HOST_PARAMETER_NAME: dict(default='localhost'), - SCHEME_PARAMETER_NAME: dict(default='http'), - VALIDATE_CERTS_PARAMETER_NAME: dict(type='bool', default=True), - NAME_PARAMETER_NAME: dict(), - PORT_PARAMETER_NAME: dict(default=8500, type='int'), - RULES_PARAMETER_NAME: dict(type='list', elements='dict'), - STATE_PARAMETER_NAME: dict(default=PRESENT_STATE_VALUE, choices=[PRESENT_STATE_VALUE, ABSENT_STATE_VALUE]), - TOKEN_PARAMETER_NAME: dict(no_log=False), - TOKEN_TYPE_PARAMETER_NAME: dict(choices=[CLIENT_TOKEN_TYPE_VALUE, MANAGEMENT_TOKEN_TYPE_VALUE], - default=CLIENT_TOKEN_TYPE_VALUE) -} - - -def set_acl(consul_client, configuration): - """ - Sets an ACL based on the given configuration. - :param consul_client: the consul client - :param configuration: the run configuration - :return: the output of setting the ACL - """ - acls_as_json = decode_acls_as_json(consul_client.acl.list()) - existing_acls_mapped_by_name = {acl.name: acl for acl in acls_as_json if acl.name is not None} - existing_acls_mapped_by_token = {acl.token: acl for acl in acls_as_json} - if None in existing_acls_mapped_by_token: - raise AssertionError("expecting ACL list to be associated to a token: %s" % - existing_acls_mapped_by_token[None]) - - if configuration.token is None and configuration.name and configuration.name in existing_acls_mapped_by_name: - # No token but name given so can get token from name - configuration.token = existing_acls_mapped_by_name[configuration.name].token - - if configuration.token and configuration.token in existing_acls_mapped_by_token: - return update_acl(consul_client, configuration) - else: - if configuration.token in existing_acls_mapped_by_token: - raise AssertionError() - if configuration.name in existing_acls_mapped_by_name: - raise AssertionError() - return create_acl(consul_client, configuration) - - -def update_acl(consul_client, configuration): - """ - Updates an ACL. - :param consul_client: the consul client - :param configuration: the run configuration - :return: the output of the update - """ - existing_acl = load_acl_with_token(consul_client, configuration.token) - changed = existing_acl.rules != configuration.rules - - if changed: - name = configuration.name if configuration.name is not None else existing_acl.name - rules_as_hcl = encode_rules_as_hcl_string(configuration.rules) - updated_token = consul_client.acl.update( - configuration.token, name=name, type=configuration.token_type, rules=rules_as_hcl) - if updated_token != configuration.token: - raise AssertionError() - - return Output(changed=changed, token=configuration.token, rules=configuration.rules, operation=UPDATE_OPERATION) - - -def create_acl(consul_client, configuration): - """ - Creates an ACL. - :param consul_client: the consul client - :param configuration: the run configuration - :return: the output of the creation - """ - rules_as_hcl = encode_rules_as_hcl_string(configuration.rules) if len(configuration.rules) > 0 else None - token = consul_client.acl.create( - name=configuration.name, type=configuration.token_type, rules=rules_as_hcl, acl_id=configuration.token) - rules = configuration.rules - return Output(changed=True, token=token, rules=rules, operation=CREATE_OPERATION) - - -def remove_acl(consul, configuration): - """ - Removes an ACL. - :param consul: the consul client - :param configuration: the run configuration - :return: the output of the removal - """ - token = configuration.token - changed = consul.acl.info(token) is not None - if changed: - consul.acl.destroy(token) - return Output(changed=changed, token=token, operation=REMOVE_OPERATION) - - -def load_acl_with_token(consul, token): - """ - Loads the ACL with the given token (token == rule ID). - :param consul: the consul client - :param token: the ACL "token"/ID (not name) - :return: the ACL associated to the given token - :exception ConsulACLTokenNotFoundException: raised if the given token does not exist - """ - acl_as_json = consul.acl.info(token) - if acl_as_json is None: - raise ConsulACLNotFoundException(token) - return decode_acl_as_json(acl_as_json) - - -def encode_rules_as_hcl_string(rules): - """ - Converts the given rules into the equivalent HCL (string) representation. - :param rules: the rules - :return: the equivalent HCL (string) representation of the rules. Will be None if there is no rules (see internal - note for justification) - """ - if len(rules) == 0: - # Note: empty string is not valid HCL according to `hcl.load` however, the ACL `Rule` property will be an empty - # string if there is no rules... - return None - rules_as_hcl = "" - for rule in rules: - rules_as_hcl += encode_rule_as_hcl_string(rule) - return rules_as_hcl - - -def encode_rule_as_hcl_string(rule): - """ - Converts the given rule into the equivalent HCL (string) representation. - :param rule: the rule - :return: the equivalent HCL (string) representation of the rule - """ - if rule.pattern is not None: - return '%s "%s" {\n %s = "%s"\n}\n' % (rule.scope, rule.pattern, _POLICY_HCL_PROPERTY, rule.policy) - else: - return '%s = "%s"\n' % (rule.scope, rule.policy) - - -def decode_rules_as_hcl_string(rules_as_hcl): - """ - Converts the given HCL (string) representation of rules into a list of rule domain models. - :param rules_as_hcl: the HCL (string) representation of a collection of rules - :return: the equivalent domain model to the given rules - """ - rules_as_hcl = to_text(rules_as_hcl) - rules_as_json = hcl.loads(rules_as_hcl) - return decode_rules_as_json(rules_as_json) - - -def decode_rules_as_json(rules_as_json): - """ - Converts the given JSON representation of rules into a list of rule domain models. - :param rules_as_json: the JSON representation of a collection of rules - :return: the equivalent domain model to the given rules - """ - rules = RuleCollection() - for scope in rules_as_json: - if not isinstance(rules_as_json[scope], dict): - rules.add(Rule(scope, rules_as_json[scope])) - else: - for pattern, policy in rules_as_json[scope].items(): - rules.add(Rule(scope, policy[_POLICY_JSON_PROPERTY], pattern)) - return rules - - -def encode_rules_as_json(rules): - """ - Converts the given rules into the equivalent JSON representation according to the documentation: - https://www.consul.io/docs/guides/acl.html#rule-specification. - :param rules: the rules - :return: JSON representation of the given rules - """ - rules_as_json = defaultdict(dict) - for rule in rules: - if rule.pattern is not None: - if rule.pattern in rules_as_json[rule.scope]: - raise AssertionError() - rules_as_json[rule.scope][rule.pattern] = { - _POLICY_JSON_PROPERTY: rule.policy - } - else: - if rule.scope in rules_as_json: - raise AssertionError() - rules_as_json[rule.scope] = rule.policy - return rules_as_json - - -def decode_rules_as_yml(rules_as_yml): - """ - Converts the given YAML representation of rules into a list of rule domain models. - :param rules_as_yml: the YAML representation of a collection of rules - :return: the equivalent domain model to the given rules - """ - rules = RuleCollection() - if rules_as_yml: - for rule_as_yml in rules_as_yml: - rule_added = False - for scope in RULE_SCOPES: - if scope in rule_as_yml: - if rule_as_yml[scope] is None: - raise ValueError("Rule for '%s' does not have a value associated to the scope" % scope) - policy = rule_as_yml[_POLICY_YML_PROPERTY] if _POLICY_YML_PROPERTY in rule_as_yml \ - else rule_as_yml[scope] - pattern = rule_as_yml[scope] if _POLICY_YML_PROPERTY in rule_as_yml else None - rules.add(Rule(scope, policy, pattern)) - rule_added = True - break - if not rule_added: - raise ValueError("A rule requires one of %s and a policy." % ('/'.join(RULE_SCOPES))) - return rules - - -def decode_acl_as_json(acl_as_json): - """ - Converts the given JSON representation of an ACL into the equivalent domain model. - :param acl_as_json: the JSON representation of an ACL - :return: the equivalent domain model to the given ACL - """ - rules_as_hcl = acl_as_json[_RULES_JSON_PROPERTY] - rules = decode_rules_as_hcl_string(acl_as_json[_RULES_JSON_PROPERTY]) if rules_as_hcl.strip() != "" \ - else RuleCollection() - return ACL( - rules=rules, - token_type=acl_as_json[_TOKEN_TYPE_JSON_PROPERTY], - token=acl_as_json[_TOKEN_JSON_PROPERTY], - name=acl_as_json[_NAME_JSON_PROPERTY] - ) - - -def decode_acls_as_json(acls_as_json): - """ - Converts the given JSON representation of ACLs into a list of ACL domain models. - :param acls_as_json: the JSON representation of a collection of ACLs - :return: list of equivalent domain models for the given ACLs (order not guaranteed to be the same) - """ - return [decode_acl_as_json(acl_as_json) for acl_as_json in acls_as_json] - - -class ConsulACLNotFoundException(Exception): - """ - Exception raised if an ACL with is not found. - """ - - -class Configuration: - """ - Configuration for this module. - """ - - def __init__(self, management_token=None, host=None, scheme=None, validate_certs=None, name=None, port=None, - rules=None, state=None, token=None, token_type=None): - self.management_token = management_token # type: str - self.host = host # type: str - self.scheme = scheme # type: str - self.validate_certs = validate_certs # type: bool - self.name = name # type: str - self.port = port # type: int - self.rules = rules # type: RuleCollection - self.state = state # type: str - self.token = token # type: str - self.token_type = token_type # type: str - - -class Output: - """ - Output of an action of this module. - """ - - def __init__(self, changed=None, token=None, rules=None, operation=None): - self.changed = changed # type: bool - self.token = token # type: str - self.rules = rules # type: RuleCollection - self.operation = operation # type: str - - -class ACL: - """ - Consul ACL. See: https://www.consul.io/docs/guides/acl.html. - """ - - def __init__(self, rules, token_type, token, name): - self.rules = rules - self.token_type = token_type - self.token = token - self.name = name - - def __eq__(self, other): - return other \ - and isinstance(other, self.__class__) \ - and self.rules == other.rules \ - and self.token_type == other.token_type \ - and self.token == other.token \ - and self.name == other.name - - def __hash__(self): - return hash(self.rules) ^ hash(self.token_type) ^ hash(self.token) ^ hash(self.name) - - -class Rule: - """ - ACL rule. See: https://www.consul.io/docs/guides/acl.html#acl-rules-and-scope. - """ - - def __init__(self, scope, policy, pattern=None): - self.scope = scope - self.policy = policy - self.pattern = pattern - - def __eq__(self, other): - return isinstance(other, self.__class__) \ - and self.scope == other.scope \ - and self.policy == other.policy \ - and self.pattern == other.pattern - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return (hash(self.scope) ^ hash(self.policy)) ^ hash(self.pattern) - - def __str__(self): - return encode_rule_as_hcl_string(self) - - -class RuleCollection: - """ - Collection of ACL rules, which are part of a Consul ACL. - """ - - def __init__(self): - self._rules = {} - for scope in RULE_SCOPES: - self._rules[scope] = {} - - def __iter__(self): - all_rules = [] - for scope, pattern_keyed_rules in self._rules.items(): - for pattern, rule in pattern_keyed_rules.items(): - all_rules.append(rule) - return iter(all_rules) - - def __len__(self): - count = 0 - for scope in RULE_SCOPES: - count += len(self._rules[scope]) - return count - - def __eq__(self, other): - return isinstance(other, self.__class__) \ - and set(self) == set(other) - - def __ne__(self, other): - return not self.__eq__(other) - - def __str__(self): - return encode_rules_as_hcl_string(self) - - def add(self, rule): - """ - Adds the given rule to this collection. - :param rule: model of a rule - :raises ValueError: raised if there already exists a rule for a given scope and pattern - """ - if rule.pattern in self._rules[rule.scope]: - patten_info = " and pattern '%s'" % rule.pattern if rule.pattern is not None else "" - raise ValueError("Duplicate rule for scope '%s'%s" % (rule.scope, patten_info)) - self._rules[rule.scope][rule.pattern] = rule - - -def get_consul_client(configuration): - """ - Gets a Consul client for the given configuration. - - Does not check if the Consul client can connect. - :param configuration: the run configuration - :return: Consul client - """ - token = configuration.management_token - if token is None: - token = configuration.token - if token is None: - raise AssertionError("Expecting the management token to always be set") - return consul.Consul(host=configuration.host, port=configuration.port, scheme=configuration.scheme, - verify=configuration.validate_certs, token=token) - - -def check_dependencies(): - """ - Checks that the required dependencies have been imported. - :exception ImportError: if it is detected that any of the required dependencies have not been imported - """ - if not python_consul_installed: - raise ImportError("python-consul required for this module. " - "See: https://python-consul.readthedocs.io/en/latest/#installation") - - if not pyhcl_installed: - raise ImportError("pyhcl required for this module. " - "See: https://pypi.org/project/pyhcl/") - - if not has_requests: - raise ImportError("requests required for this module. See https://pypi.org/project/requests/") - - -def main(): - """ - Main method. - """ - module = AnsibleModule(_ARGUMENT_SPEC, supports_check_mode=False) - - try: - check_dependencies() - except ImportError as e: - module.fail_json(msg=str(e)) - - configuration = Configuration( - management_token=module.params.get(MANAGEMENT_PARAMETER_NAME), - host=module.params.get(HOST_PARAMETER_NAME), - scheme=module.params.get(SCHEME_PARAMETER_NAME), - validate_certs=module.params.get(VALIDATE_CERTS_PARAMETER_NAME), - name=module.params.get(NAME_PARAMETER_NAME), - port=module.params.get(PORT_PARAMETER_NAME), - rules=decode_rules_as_yml(module.params.get(RULES_PARAMETER_NAME)), - state=module.params.get(STATE_PARAMETER_NAME), - token=module.params.get(TOKEN_PARAMETER_NAME), - token_type=module.params.get(TOKEN_TYPE_PARAMETER_NAME) - ) - consul_client = get_consul_client(configuration) - - try: - if configuration.state == PRESENT_STATE_VALUE: - output = set_acl(consul_client, configuration) - else: - output = remove_acl(consul_client, configuration) - except ConnectionError as e: - module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % ( - configuration.host, configuration.port, str(e))) - raise - - return_values = dict(changed=output.changed, token=output.token, operation=output.operation) - if output.rules is not None: - return_values["rules"] = encode_rules_as_json(output.rules) - module.exit_json(**return_values) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/rhn_channel.py b/plugins/modules/rhn_channel.py deleted file mode 100644 index b69bb0c686..0000000000 --- a/plugins/modules/rhn_channel.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) Vincent Van de Kussen -# 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 absolute_import, division, print_function -__metaclass__ = type - -DOCUMENTATION = ''' ---- -module: rhn_channel -short_description: Adds or removes Red Hat software channels -description: - - Adds or removes Red Hat software channels. -author: - - Vincent Van der Kussen (@vincentvdk) -notes: - - This module fetches the system id from RHN. -extends_documentation_fragment: - - community.general.attributes -attributes: - check_mode: - support: none - diff_mode: - support: none -options: - name: - description: - - Name of the software channel. - required: true - type: str - sysname: - description: - - Name of the system as it is known in RHN/Satellite. - required: true - type: str - state: - description: - - Whether the channel should be present or not, taking action if the state is different from what is stated. - default: present - choices: [ present, absent ] - type: str - url: - description: - - The full URL to the RHN/Satellite API. - required: true - type: str - user: - description: - - RHN/Satellite login. - required: true - type: str - password: - description: - - RHN/Satellite password. - aliases: [pwd] - required: true - type: str - validate_certs: - description: - - If V(false), SSL certificates will not be validated. - - This should only set to V(false) when used on self controlled sites - using self-signed certificates, and you are absolutely sure that nobody - can modify traffic between the module and the site. - type: bool - default: true - version_added: '0.2.0' -deprecated: - removed_in: 10.0.0 - why: | - RHN hosted at redhat.com was discontinued years ago, and Spacewalk 5 - (which uses RHN) is EOL since 2020, May 31st; while this module could - work on Uyuni / SUSE Manager (fork of Spacewalk 5), we have not heard - about anyone using it in those setups. - alternative: | - Contact the community.general maintainers to report the usage of this - module, and potentially step up to maintain it. -''' - -EXAMPLES = ''' -- name: Add a Red Hat software channel - community.general.rhn_channel: - name: rhel-x86_64-server-v2vwin-6 - sysname: server01 - url: https://rhn.redhat.com/rpc/api - user: rhnuser - password: guessme - delegate_to: localhost -''' - -import ssl -from ansible.module_utils.common.text.converters import to_text -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six.moves import xmlrpc_client - - -def get_systemid(client, session, sysname): - systems = client.system.listUserSystems(session) - for system in systems: - if system.get('name') == sysname: - idres = system.get('id') - idd = int(idres) - return idd - - -def subscribe_channels(channelname, client, session, sysname, sys_id): - channels = base_channels(client, session, sys_id) - channels.append(channelname) - return client.system.setChildChannels(session, sys_id, channels) - - -def unsubscribe_channels(channelname, client, session, sysname, sys_id): - channels = base_channels(client, session, sys_id) - channels.remove(channelname) - return client.system.setChildChannels(session, sys_id, channels) - - -def base_channels(client, session, sys_id): - basechan = client.channel.software.listSystemChannels(session, sys_id) - try: - chans = [item['label'] for item in basechan] - except KeyError: - chans = [item['channel_label'] for item in basechan] - return chans - - -def main(): - - module = AnsibleModule( - argument_spec=dict( - state=dict(type='str', default='present', choices=['present', 'absent']), - name=dict(type='str', required=True), - sysname=dict(type='str', required=True), - url=dict(type='str', required=True), - user=dict(type='str', required=True), - password=dict(type='str', required=True, aliases=['pwd'], no_log=True), - validate_certs=dict(type='bool', default=True), - ) - ) - - state = module.params['state'] - channelname = module.params['name'] - systname = module.params['sysname'] - saturl = module.params['url'] - user = module.params['user'] - password = module.params['password'] - validate_certs = module.params['validate_certs'] - - ssl_context = None - if not validate_certs: - try: # Python 2.7.9 and newer - ssl_context = ssl.create_unverified_context() - except AttributeError: # Legacy Python that doesn't verify HTTPS certificates by default - ssl_context = ssl._create_unverified_context() - else: # Python 2.7.8 and older - ssl._create_default_https_context = ssl._create_unverified_https_context - - # initialize connection - if ssl_context: - client = xmlrpc_client.ServerProxy(saturl, context=ssl_context) - else: - client = xmlrpc_client.Server(saturl) - - try: - session = client.auth.login(user, password) - except Exception as e: - module.fail_json(msg="Unable to establish session with Satellite server: %s " % to_text(e)) - - if not session: - module.fail_json(msg="Failed to establish session with Satellite server.") - - # get systemid - try: - sys_id = get_systemid(client, session, systname) - except Exception as e: - module.fail_json(msg="Unable to get system id: %s " % to_text(e)) - - if not sys_id: - module.fail_json(msg="Failed to get system id.") - - # get channels for system - try: - chans = base_channels(client, session, sys_id) - except Exception as e: - module.fail_json(msg="Unable to get channel information: %s " % to_text(e)) - - try: - if state == 'present': - if channelname in chans: - module.exit_json(changed=False, msg="Channel %s already exists" % channelname) - else: - subscribe_channels(channelname, client, session, systname, sys_id) - module.exit_json(changed=True, msg="Channel %s added" % channelname) - - if state == 'absent': - if channelname not in chans: - module.exit_json(changed=False, msg="Not subscribed to channel %s." % channelname) - else: - unsubscribe_channels(channelname, client, session, systname, sys_id) - module.exit_json(changed=True, msg="Channel %s removed" % channelname) - except Exception as e: - module.fail_json(msg='Unable to %s channel (%s): %s' % ('add' if state == 'present' else 'remove', channelname, to_text(e))) - finally: - client.auth.logout(session) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/rhn_register.py b/plugins/modules/rhn_register.py deleted file mode 100644 index cd1b708e48..0000000000 --- a/plugins/modules/rhn_register.py +++ /dev/null @@ -1,465 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) James Laska -# 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 absolute_import, division, print_function -__metaclass__ = type - -DOCUMENTATION = r''' ---- -module: rhn_register -short_description: Manage Red Hat Network registration using the C(rhnreg_ks) command -description: - - Manage registration to the Red Hat Network. -author: - - James Laska (@jlaska) -notes: - - This is for older Red Hat products. You probably want the M(community.general.redhat_subscription) module instead. - - In order to register a system, C(rhnreg_ks) requires either a username and password, or an activationkey. -requirements: - - rhnreg_ks - - either libxml2 or lxml -extends_documentation_fragment: - - community.general.attributes -attributes: - check_mode: - support: none - diff_mode: - support: none -options: - state: - description: - - Whether to register (V(present)), or unregister (V(absent)) a system. - type: str - choices: [ absent, present ] - default: present - username: - description: - - Red Hat Network username. - type: str - password: - description: - - Red Hat Network password. - type: str - server_url: - description: - - Specify an alternative Red Hat Network server URL. - - The default is the current value of C(serverURL) from C(/etc/sysconfig/rhn/up2date). - type: str - activationkey: - description: - - Supply an activation key for use with registration. - type: str - profilename: - description: - - Supply an profilename for use with registration. - type: str - force: - description: - - Force registration, even if system is already registered. - type: bool - default: false - version_added: 2.0.0 - ca_cert: - description: - - Supply a custom ssl CA certificate file for use with registration. - type: path - aliases: [ sslcacert ] - systemorgid: - description: - - Supply an organizational id for use with registration. - type: str - channels: - description: - - Optionally specify a list of channels to subscribe to upon successful registration. - type: list - elements: str - default: [] - enable_eus: - description: - - If V(false), extended update support will be requested. - type: bool - default: false - nopackages: - description: - - If V(true), the registered node will not upload its installed packages information to Satellite server. - type: bool - default: false -deprecated: - removed_in: 10.0.0 - why: | - RHN hosted at redhat.com was discontinued years ago, and Spacewalk 5 - (which uses RHN) is EOL since 2020, May 31st; while this module could - work on Uyuni / SUSE Manager (fork of Spacewalk 5), we have not heard - about anyone using it in those setups. - alternative: | - Contact the community.general maintainers to report the usage of this - module, and potentially step up to maintain it. -''' - -EXAMPLES = r''' -- name: Unregister system from RHN - community.general.rhn_register: - state: absent - username: joe_user - password: somepass - -- name: Register as user with password and auto-subscribe to available content - community.general.rhn_register: - state: present - username: joe_user - password: somepass - -- name: Register with activationkey and enable extended update support - community.general.rhn_register: - state: present - activationkey: 1-222333444 - enable_eus: true - -- name: Register with activationkey and set a profilename which may differ from the hostname - community.general.rhn_register: - state: present - activationkey: 1-222333444 - profilename: host.example.com.custom - -- name: Register as user with password against a satellite server - community.general.rhn_register: - state: present - username: joe_user - password: somepass - server_url: https://xmlrpc.my.satellite/XMLRPC - -- name: Register as user with password and enable channels - community.general.rhn_register: - state: present - username: joe_user - password: somepass - channels: rhel-x86_64-server-6-foo-1,rhel-x86_64-server-6-bar-1 - -- name: Force-register as user with password to ensure registration is current on server - community.general.rhn_register: - state: present - username: joe_user - password: somepass - server_url: https://xmlrpc.my.satellite/XMLRPC - force: true -''' - -RETURN = r''' -# Default return values -''' - -import os -import sys - -# Attempt to import rhn client tools -sys.path.insert(0, '/usr/share/rhn') -try: - import up2date_client - import up2date_client.config - HAS_UP2DATE_CLIENT = True -except ImportError: - HAS_UP2DATE_CLIENT = False - -# INSERT REDHAT SNIPPETS -from ansible_collections.community.general.plugins.module_utils import redhat -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six.moves import urllib, xmlrpc_client - - -class Rhn(redhat.RegistrationBase): - - def __init__(self, module=None, username=None, password=None): - redhat.RegistrationBase.__init__(self, module, username, password) - self.config = self.load_config() - self.server = None - self.session = None - - def logout(self): - if self.session is not None: - self.server.auth.logout(self.session) - - def load_config(self): - ''' - Read configuration from /etc/sysconfig/rhn/up2date - ''' - if not HAS_UP2DATE_CLIENT: - return None - - config = up2date_client.config.initUp2dateConfig() - - return config - - @property - def server_url(self): - return self.config['serverURL'] - - @property - def hostname(self): - ''' - Return the non-xmlrpc RHN hostname. This is a convenience method - used for displaying a more readable RHN hostname. - - Returns: str - ''' - url = urllib.parse.urlparse(self.server_url) - return url[1].replace('xmlrpc.', '') - - @property - def systemid(self): - systemid = None - xpath_str = "//member[name='system_id']/value/string" - - if os.path.isfile(self.config['systemIdPath']): - fd = open(self.config['systemIdPath'], 'r') - xml_data = fd.read() - fd.close() - - # Ugh, xml parsing time ... - # First, try parsing with libxml2 ... - if systemid is None: - try: - import libxml2 - doc = libxml2.parseDoc(xml_data) - ctxt = doc.xpathNewContext() - systemid = ctxt.xpathEval(xpath_str)[0].content - doc.freeDoc() - ctxt.xpathFreeContext() - except ImportError: - pass - - # m-kay, let's try with lxml now ... - if systemid is None: - try: - from lxml import etree - root = etree.fromstring(xml_data) - systemid = root.xpath(xpath_str)[0].text - except ImportError: - raise Exception('"libxml2" or "lxml" is required for this module.') - - # Strip the 'ID-' prefix - if systemid is not None and systemid.startswith('ID-'): - systemid = systemid[3:] - - return int(systemid) - - @property - def is_registered(self): - ''' - Determine whether the current system is registered. - - Returns: True|False - ''' - return os.path.isfile(self.config['systemIdPath']) - - def configure_server_url(self, server_url): - ''' - Configure server_url for registration - ''' - - self.config.set('serverURL', server_url) - self.config.save() - - def enable(self): - ''' - Prepare the system for RHN registration. This includes ... - * enabling the rhnplugin yum plugin - * disabling the subscription-manager yum plugin - ''' - redhat.RegistrationBase.enable(self) - self.update_plugin_conf('rhnplugin', True) - self.update_plugin_conf('subscription-manager', False) - - def register(self, enable_eus=False, activationkey=None, profilename=None, sslcacert=None, systemorgid=None, nopackages=False): - ''' - Register system to RHN. If enable_eus=True, extended update - support will be requested. - ''' - register_cmd = ['/usr/sbin/rhnreg_ks', '--force'] - if self.username: - register_cmd.extend(['--username', self.username, '--password', self.password]) - if self.server_url: - register_cmd.extend(['--serverUrl', self.server_url]) - if enable_eus: - register_cmd.append('--use-eus-channel') - if nopackages: - register_cmd.append('--nopackages') - if activationkey is not None: - register_cmd.extend(['--activationkey', activationkey]) - if profilename is not None: - register_cmd.extend(['--profilename', profilename]) - if sslcacert is not None: - register_cmd.extend(['--sslCACert', sslcacert]) - if systemorgid is not None: - register_cmd.extend(['--systemorgid', systemorgid]) - rc, stdout, stderr = self.module.run_command(register_cmd, check_rc=True) - - def api(self, method, *args): - ''' - Convenience RPC wrapper - ''' - if self.server is None: - if self.hostname != 'rhn.redhat.com': - url = "https://%s/rpc/api" % self.hostname - else: - url = "https://xmlrpc.%s/rpc/api" % self.hostname - self.server = xmlrpc_client.ServerProxy(url) - self.session = self.server.auth.login(self.username, self.password) - - func = getattr(self.server, method) - return func(self.session, *args) - - def unregister(self): - ''' - Unregister a previously registered system - ''' - - # Initiate RPC connection - self.api('system.deleteSystems', [self.systemid]) - - # Remove systemid file - os.unlink(self.config['systemIdPath']) - - def subscribe(self, channels): - if not channels: - return - - if self._is_hosted(): - current_channels = self.api('channel.software.listSystemChannels', self.systemid) - new_channels = [item['channel_label'] for item in current_channels] - new_channels.extend(channels) - return self.api('channel.software.setSystemChannels', self.systemid, list(new_channels)) - - else: - current_channels = self.api('channel.software.listSystemChannels', self.systemid) - current_channels = [item['label'] for item in current_channels] - new_base = None - new_childs = [] - for ch in channels: - if ch in current_channels: - continue - if self.api('channel.software.getDetails', ch)['parent_channel_label'] == '': - new_base = ch - else: - if ch not in new_childs: - new_childs.append(ch) - out_base = 0 - out_childs = 0 - - if new_base: - out_base = self.api('system.setBaseChannel', self.systemid, new_base) - - if new_childs: - out_childs = self.api('system.setChildChannels', self.systemid, new_childs) - - return out_base and out_childs - - def _is_hosted(self): - ''' - Return True if we are running against Hosted (rhn.redhat.com) or - False otherwise (when running against Satellite or Spacewalk) - ''' - return 'rhn.redhat.com' in self.hostname - - -def main(): - - module = AnsibleModule( - argument_spec=dict( - state=dict(type='str', default='present', choices=['absent', 'present']), - username=dict(type='str'), - password=dict(type='str', no_log=True), - server_url=dict(type='str'), - activationkey=dict(type='str', no_log=True), - profilename=dict(type='str'), - ca_cert=dict(type='path', aliases=['sslcacert']), - systemorgid=dict(type='str'), - enable_eus=dict(type='bool', default=False), - force=dict(type='bool', default=False), - nopackages=dict(type='bool', default=False), - channels=dict(type='list', elements='str', default=[]), - ), - # username/password is required for state=absent, or if channels is not empty - # (basically anything that uses self.api requires username/password) but it doesn't - # look like we can express that with required_if/required_together/mutually_exclusive - - # only username+password can be used for unregister - required_if=[['state', 'absent', ['username', 'password']]], - ) - - if not HAS_UP2DATE_CLIENT: - module.fail_json(msg="Unable to import up2date_client. Is 'rhn-client-tools' installed?") - - server_url = module.params['server_url'] - username = module.params['username'] - password = module.params['password'] - - state = module.params['state'] - force = module.params['force'] - activationkey = module.params['activationkey'] - profilename = module.params['profilename'] - sslcacert = module.params['ca_cert'] - systemorgid = module.params['systemorgid'] - channels = module.params['channels'] - enable_eus = module.params['enable_eus'] - nopackages = module.params['nopackages'] - - rhn = Rhn(module=module, username=username, password=password) - - # use the provided server url and persist it to the rhn config. - if server_url: - rhn.configure_server_url(server_url) - - if not rhn.server_url: - module.fail_json( - msg="No serverURL was found (from either the 'server_url' module arg or the config file option 'serverURL' in /etc/sysconfig/rhn/up2date)" - ) - - # Ensure system is registered - if state == 'present': - - # Check for missing parameters ... - if not (activationkey or rhn.username or rhn.password): - module.fail_json(msg="Missing arguments, must supply an activationkey (%s) or username (%s) and password (%s)" % (activationkey, rhn.username, - rhn.password)) - if not activationkey and not (rhn.username and rhn.password): - module.fail_json(msg="Missing arguments, If registering without an activationkey, must supply username or password") - - # Register system - if rhn.is_registered and not force: - module.exit_json(changed=False, msg="System already registered.") - - try: - rhn.enable() - rhn.register(enable_eus, activationkey, profilename, sslcacert, systemorgid, nopackages) - rhn.subscribe(channels) - except Exception as exc: - module.fail_json(msg="Failed to register with '%s': %s" % (rhn.hostname, exc)) - finally: - rhn.logout() - - module.exit_json(changed=True, msg="System successfully registered to '%s'." % rhn.hostname) - - # Ensure system is *not* registered - if state == 'absent': - if not rhn.is_registered: - module.exit_json(changed=False, msg="System already unregistered.") - - if not (rhn.username and rhn.password): - module.fail_json(msg="Missing arguments, the system is currently registered and unregistration requires a username and password") - - try: - rhn.unregister() - except Exception as exc: - module.fail_json(msg="Failed to unregister: %s" % exc) - finally: - rhn.logout() - - module.exit_json(changed=True, msg="System successfully unregistered from %s." % rhn.hostname) - - -if __name__ == '__main__': - main() diff --git a/tests/sanity/extra/botmeta.py b/tests/sanity/extra/botmeta.py index d7828ebabb..07ca189e81 100755 --- a/tests/sanity/extra/botmeta.py +++ b/tests/sanity/extra/botmeta.py @@ -27,7 +27,6 @@ IGNORE_NO_MAINTAINERS = [ 'plugins/callback/cgroup_memory_recap.py', 'plugins/callback/context_demo.py', 'plugins/callback/counter_enabled.py', - 'plugins/callback/hipchat.py', 'plugins/callback/jabber.py', 'plugins/callback/log_plays.py', 'plugins/callback/logdna.py', diff --git a/tests/unit/plugins/modules/rhn_conftest.py b/tests/unit/plugins/modules/rhn_conftest.py deleted file mode 100644 index acc0e2f221..0000000000 --- a/tests/unit/plugins/modules/rhn_conftest.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) Ansible project -# 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 (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.module_utils.six.moves import xmlrpc_client - -import pytest - - -def get_method_name(request_body): - return xmlrpc_client.loads(request_body)[1] - - -@pytest.fixture -def mock_request(request, mocker): - responses = request.getfixturevalue('testcase')['calls'] - module_name = request.module.TESTED_MODULE - - def transport_request(host, handler, request_body, verbose=0): - """Fake request""" - method_name = get_method_name(request_body) - excepted_name, response = responses.pop(0) - if method_name == excepted_name: - if isinstance(response, Exception): - raise response - else: - return response - else: - raise Exception('Expected call: %r, called with: %r' % (excepted_name, method_name)) - - target = '{0}.xmlrpc_client.Transport.request'.format(module_name) - mocker.patch(target, side_effect=transport_request) diff --git a/tests/unit/plugins/modules/test_rhn_channel.py b/tests/unit/plugins/modules/test_rhn_channel.py deleted file mode 100644 index fd3bdc5fe0..0000000000 --- a/tests/unit/plugins/modules/test_rhn_channel.py +++ /dev/null @@ -1,147 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Pierre-Louis Bonicoli -# 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 (absolute_import, division, print_function) -__metaclass__ = type - -import json - -from ansible_collections.community.general.plugins.modules import rhn_channel - -from .rhn_conftest import mock_request # noqa: F401, pylint: disable=unused-import - -import pytest - - -pytestmark = pytest.mark.usefixtures('patch_ansible_module') - - -@pytest.mark.parametrize('patch_ansible_module', [{}], indirect=['patch_ansible_module']) -def test_without_required_parameters(capfd): - with pytest.raises(SystemExit): - rhn_channel.main() - out, err = capfd.readouterr() - results = json.loads(out) - assert results['failed'] - assert 'missing required arguments' in results['msg'] - - -TESTED_MODULE = rhn_channel.__name__ -TEST_CASES = [ - [ - # add channel already added, check that result isn't changed - { - 'name': 'rhel-x86_64-server-6', - 'sysname': 'server01', - 'url': 'https://rhn.redhat.com/rpc/api', - 'user': 'user', - 'password': 'pass', - }, - { - 'calls': [ - ('auth.login', ['X' * 43]), - ('system.listUserSystems', - [[{'last_checkin': '2017-08-06 19:49:52.0', 'id': '0123456789', 'name': 'server01'}]]), - ('channel.software.listSystemChannels', - [[{'channel_name': 'Red Hat Enterprise Linux Server (v. 6 for 64-bit x86_64)', 'channel_label': 'rhel-x86_64-server-6'}]]), - ('auth.logout', [1]), - ], - 'changed': False, - 'msg': 'Channel rhel-x86_64-server-6 already exists', - } - ], - [ - # add channel, check that result is changed - { - 'name': 'rhel-x86_64-server-6-debuginfo', - 'sysname': 'server01', - 'url': 'https://rhn.redhat.com/rpc/api', - 'user': 'user', - 'password': 'pass', - }, - { - 'calls': [ - ('auth.login', ['X' * 43]), - ('system.listUserSystems', - [[{'last_checkin': '2017-08-06 19:49:52.0', 'id': '0123456789', 'name': 'server01'}]]), - ('channel.software.listSystemChannels', - [[{'channel_name': 'Red Hat Enterprise Linux Server (v. 6 for 64-bit x86_64)', 'channel_label': 'rhel-x86_64-server-6'}]]), - ('channel.software.listSystemChannels', - [[{'channel_name': 'Red Hat Enterprise Linux Server (v. 6 for 64-bit x86_64)', 'channel_label': 'rhel-x86_64-server-6'}]]), - ('system.setChildChannels', [1]), - ('auth.logout', [1]), - ], - 'changed': True, - 'msg': 'Channel rhel-x86_64-server-6-debuginfo added', - } - ], - [ - # remove inexistent channel, check that result isn't changed - { - 'name': 'rhel-x86_64-server-6-debuginfo', - 'state': 'absent', - 'sysname': 'server01', - 'url': 'https://rhn.redhat.com/rpc/api', - 'user': 'user', - 'password': 'pass', - }, - { - 'calls': [ - ('auth.login', ['X' * 43]), - ('system.listUserSystems', - [[{'last_checkin': '2017-08-06 19:49:52.0', 'id': '0123456789', 'name': 'server01'}]]), - ('channel.software.listSystemChannels', - [[{'channel_name': 'Red Hat Enterprise Linux Server (v. 6 for 64-bit x86_64)', 'channel_label': 'rhel-x86_64-server-6'}]]), - ('auth.logout', [1]), - ], - 'changed': False, - 'msg': 'Not subscribed to channel rhel-x86_64-server-6-debuginfo.', - } - ], - [ - # remove channel, check that result is changed - { - 'name': 'rhel-x86_64-server-6-debuginfo', - 'state': 'absent', - 'sysname': 'server01', - 'url': 'https://rhn.redhat.com/rpc/api', - 'user': 'user', - 'password': 'pass', - }, - { - 'calls': [ - ('auth.login', ['X' * 43]), - ('system.listUserSystems', - [[{'last_checkin': '2017-08-06 19:49:52.0', 'id': '0123456789', 'name': 'server01'}]]), - ('channel.software.listSystemChannels', [[ - {'channel_name': 'RHEL Server Debuginfo (v.6 for x86_64)', 'channel_label': 'rhel-x86_64-server-6-debuginfo'}, - {'channel_name': 'Red Hat Enterprise Linux Server (v. 6 for 64-bit x86_64)', 'channel_label': 'rhel-x86_64-server-6'} - ]]), - ('channel.software.listSystemChannels', [[ - {'channel_name': 'RHEL Server Debuginfo (v.6 for x86_64)', 'channel_label': 'rhel-x86_64-server-6-debuginfo'}, - {'channel_name': 'Red Hat Enterprise Linux Server (v. 6 for 64-bit x86_64)', 'channel_label': 'rhel-x86_64-server-6'} - ]]), - ('system.setChildChannels', [1]), - ('auth.logout', [1]), - ], - 'changed': True, - 'msg': 'Channel rhel-x86_64-server-6-debuginfo removed' - } - ] -] - - -@pytest.mark.parametrize('patch_ansible_module, testcase', TEST_CASES, indirect=['patch_ansible_module']) -def test_rhn_channel(capfd, mocker, testcase, mock_request): - """Check 'msg' and 'changed' results""" - - with pytest.raises(SystemExit): - rhn_channel.main() - - out, err = capfd.readouterr() - results = json.loads(out) - assert results['changed'] == testcase['changed'] - assert results['msg'] == testcase['msg'] - assert not testcase['calls'] # all calls should have been consumed diff --git a/tests/unit/plugins/modules/test_rhn_register.py b/tests/unit/plugins/modules/test_rhn_register.py deleted file mode 100644 index 1394c07b65..0000000000 --- a/tests/unit/plugins/modules/test_rhn_register.py +++ /dev/null @@ -1,293 +0,0 @@ -# Copyright (c) Ansible project -# 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 (absolute_import, division, print_function) -__metaclass__ = type - -import json -import os - -from ansible_collections.community.general.tests.unit.compat.mock import mock_open -from ansible.module_utils import basic -from ansible.module_utils.common.text.converters import to_native -import ansible.module_utils.six -from ansible.module_utils.six.moves import xmlrpc_client -from ansible_collections.community.general.plugins.modules import rhn_register - -from .rhn_conftest import mock_request # noqa: F401, pylint: disable=unused-import - -import pytest - - -SYSTEMID = """ - - - - -system_id -ID-123456789 - - - - -""" - - -def skipWhenAllModulesMissing(modules): - """Skip the decorated test unless one of modules is available.""" - for module in modules: - try: - __import__(module) - return False - except ImportError: - continue - - return True - - -orig_import = __import__ - - -@pytest.fixture -def import_libxml(mocker): - def mock_import(name, *args, **kwargs): - if name in ['libxml2', 'libxml']: - raise ImportError() - else: - return orig_import(name, *args, **kwargs) - - if ansible.module_utils.six.PY3: - mocker.patch('builtins.__import__', side_effect=mock_import) - else: - mocker.patch('__builtin__.__import__', side_effect=mock_import) - - -@pytest.fixture -def patch_rhn(mocker): - load_config_return = { - 'serverURL': 'https://xmlrpc.rhn.redhat.com/XMLRPC', - 'systemIdPath': '/etc/sysconfig/rhn/systemid' - } - - mocker.patch.object(rhn_register.Rhn, 'load_config', return_value=load_config_return) - mocker.patch.object(rhn_register, 'HAS_UP2DATE_CLIENT', mocker.PropertyMock(return_value=True)) - - -@pytest.mark.skipif(skipWhenAllModulesMissing(['libxml2', 'libxml']), reason='none are available: libxml2, libxml') -def test_systemid_with_requirements(capfd, mocker, patch_rhn): - """Check 'msg' and 'changed' results""" - - mocker.patch.object(rhn_register.Rhn, 'enable') - mock_isfile = mocker.patch('os.path.isfile', return_value=True) - mocker.patch('ansible_collections.community.general.plugins.modules.rhn_register.open', mock_open(read_data=SYSTEMID), create=True) - rhn = rhn_register.Rhn() - assert '123456789' == to_native(rhn.systemid) - - -@pytest.mark.parametrize('patch_ansible_module', [{}], indirect=['patch_ansible_module']) -@pytest.mark.usefixtures('patch_ansible_module') -def test_systemid_requirements_missing(capfd, mocker, patch_rhn, import_libxml): - """Check that missing dependencies are detected""" - - mocker.patch('os.path.isfile', return_value=True) - mocker.patch('ansible_collections.community.general.plugins.modules.rhn_register.open', mock_open(read_data=SYSTEMID), create=True) - - with pytest.raises(SystemExit): - rhn_register.main() - - out, err = capfd.readouterr() - results = json.loads(out) - assert results['failed'] - assert 'Missing arguments' in results['msg'] - - -@pytest.mark.parametrize('patch_ansible_module', [{}], indirect=['patch_ansible_module']) -@pytest.mark.usefixtures('patch_ansible_module') -def test_without_required_parameters(capfd, patch_rhn): - """Failure must occurs when all parameters are missing""" - - with pytest.raises(SystemExit): - rhn_register.main() - out, err = capfd.readouterr() - results = json.loads(out) - assert results['failed'] - assert 'Missing arguments' in results['msg'] - - -TESTED_MODULE = rhn_register.__name__ -TEST_CASES = [ - [ - # Registering an unregistered host with channels - { - 'channels': 'rhel-x86_64-server-6', - 'username': 'user', - 'password': 'pass', - }, - { - 'calls': [ - ('auth.login', ['X' * 43]), - ('channel.software.listSystemChannels', - [[{'channel_name': 'Red Hat Enterprise Linux Server (v. 6 for 64-bit x86_64)', 'channel_label': 'rhel-x86_64-server-6'}]]), - ('channel.software.setSystemChannels', [1]), - ('auth.logout', [1]), - ], - 'is_registered': False, - 'is_registered.call_count': 1, - 'enable.call_count': 1, - 'systemid.call_count': 2, - 'changed': True, - 'msg': "System successfully registered to 'rhn.redhat.com'.", - 'run_command.call_count': 1, - 'run_command.call_args': '/usr/sbin/rhnreg_ks', - 'request_called': True, - 'unlink.call_count': 0, - } - ], - [ - # Registering an unregistered host without channels - { - 'activationkey': 'key', - 'username': 'user', - 'password': 'pass', - }, - { - 'calls': [ - ], - 'is_registered': False, - 'is_registered.call_count': 1, - 'enable.call_count': 1, - 'systemid.call_count': 0, - 'changed': True, - 'msg': "System successfully registered to 'rhn.redhat.com'.", - 'run_command.call_count': 1, - 'run_command.call_args': '/usr/sbin/rhnreg_ks', - 'request_called': False, - 'unlink.call_count': 0, - } - ], - [ - # Register an host already registered, check that result is unchanged - { - 'activationkey': 'key', - 'username': 'user', - 'password': 'pass', - }, - { - 'calls': [ - ], - 'is_registered': True, - 'is_registered.call_count': 1, - 'enable.call_count': 0, - 'systemid.call_count': 0, - 'changed': False, - 'msg': 'System already registered.', - 'run_command.call_count': 0, - 'request_called': False, - 'unlink.call_count': 0, - }, - ], - [ - # Unregister an host, check that result is changed - { - 'activationkey': 'key', - 'username': 'user', - 'password': 'pass', - 'state': 'absent', - }, - { - 'calls': [ - ('auth.login', ['X' * 43]), - ('system.deleteSystems', [1]), - ('auth.logout', [1]), - ], - 'is_registered': True, - 'is_registered.call_count': 1, - 'enable.call_count': 0, - 'systemid.call_count': 1, - 'changed': True, - 'msg': 'System successfully unregistered from rhn.redhat.com.', - 'run_command.call_count': 0, - 'request_called': True, - 'unlink.call_count': 1, - } - ], - [ - # Unregister a unregistered host (systemid missing) locally, check that result is unchanged - { - 'activationkey': 'key', - 'username': 'user', - 'password': 'pass', - 'state': 'absent', - }, - { - 'calls': [], - 'is_registered': False, - 'is_registered.call_count': 1, - 'enable.call_count': 0, - 'systemid.call_count': 0, - 'changed': False, - 'msg': 'System already unregistered.', - 'run_command.call_count': 0, - 'request_called': False, - 'unlink.call_count': 0, - } - - ], - [ - # Unregister an unknown host (an host with a systemid available locally, check that result contains failed - { - 'activationkey': 'key', - 'username': 'user', - 'password': 'pass', - 'state': 'absent', - }, - { - 'calls': [ - ('auth.login', ['X' * 43]), - ('system.deleteSystems', xmlrpc_client.Fault(1003, 'The following systems were NOT deleted: 123456789')), - ('auth.logout', [1]), - ], - 'is_registered': True, - 'is_registered.call_count': 1, - 'enable.call_count': 0, - 'systemid.call_count': 1, - 'failed': True, - 'msg': "Failed to unregister: ", - 'run_command.call_count': 0, - 'request_called': True, - 'unlink.call_count': 0, - } - ], -] - - -@pytest.mark.parametrize('patch_ansible_module, testcase', TEST_CASES, indirect=['patch_ansible_module']) -@pytest.mark.usefixtures('patch_ansible_module') -def test_register_parameters(mocker, capfd, mock_request, patch_rhn, testcase): - # successful execution, no output - mocker.patch.object(basic.AnsibleModule, 'run_command', return_value=(0, '', '')) - mock_is_registered = mocker.patch.object(rhn_register.Rhn, 'is_registered', mocker.PropertyMock(return_value=testcase['is_registered'])) - mocker.patch.object(rhn_register.Rhn, 'enable') - mock_systemid = mocker.patch.object(rhn_register.Rhn, 'systemid', mocker.PropertyMock(return_value=12345)) - mocker.patch('os.unlink', return_value=True) - - with pytest.raises(SystemExit): - rhn_register.main() - - assert basic.AnsibleModule.run_command.call_count == testcase['run_command.call_count'] - if basic.AnsibleModule.run_command.call_count: - assert basic.AnsibleModule.run_command.call_args[0][0][0] == testcase['run_command.call_args'] - - assert mock_is_registered.call_count == testcase['is_registered.call_count'] - assert rhn_register.Rhn.enable.call_count == testcase['enable.call_count'] - assert mock_systemid.call_count == testcase['systemid.call_count'] - assert xmlrpc_client.Transport.request.called == testcase['request_called'] - assert os.unlink.call_count == testcase['unlink.call_count'] - - out, err = capfd.readouterr() - results = json.loads(out) - assert results.get('changed') == testcase.get('changed') - assert results.get('failed') == testcase.get('failed') - assert results['msg'] == testcase['msg'] - assert not testcase['calls'] # all calls should have been consumed