diff options
Diffstat (limited to 'collections-debian-merged/ansible_collections/community/rabbitmq/plugins')
15 files changed, 2976 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/doc_fragments/__init__.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/doc_fragments/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/doc_fragments/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/doc_fragments/rabbitmq.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/doc_fragments/rabbitmq.py new file mode 100644 index 00000000..acae82e8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/doc_fragments/rabbitmq.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Jorge Rodriguez <jorge.rodriguez@tiriel.eu> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class ModuleDocFragment(object): + # Parameters for RabbitMQ modules + DOCUMENTATION = r''' +options: + login_user: + description: + - RabbitMQ user for connection. + type: str + default: guest + login_password: + description: + - RabbitMQ password for connection. + type: str + default: guest + login_host: + description: + - RabbitMQ host for connection. + type: str + default: localhost + login_port: + description: + - RabbitMQ management API port. + type: str + default: '15672' + login_protocol: + description: + - RabbitMQ management API protocol. + type: str + choices: [ http , https ] + default: http + ca_cert: + description: + - CA certificate to verify SSL connection to management API. + type: path + aliases: [ cacert ] + client_cert: + description: + - Client certificate to send on SSL connections to management API. + type: path + aliases: [ cert ] + client_key: + description: + - Private key matching the client certificate. + type: path + aliases: [ key ] + vhost: + description: + - RabbitMQ virtual host. + type: str + default: "/" +''' diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/lookup/rabbitmq.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/lookup/rabbitmq.py new file mode 100644 index 00000000..406936de --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/lookup/rabbitmq.py @@ -0,0 +1,189 @@ +# (c) 2018, John Imison <john+github@imison.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + lookup: rabbitmq + author: John Imison <@Im0> + short_description: Retrieve messages from an AMQP/AMQPS RabbitMQ queue. + description: + - This lookup uses a basic get to retrieve all, or a limited number C(count), messages from a RabbitMQ queue. + options: + url: + description: + - An URI connection string to connect to the AMQP/AMQPS RabbitMQ server. + - For more information refer to the URI spec U(https://www.rabbitmq.com/uri-spec.html). + required: True + queue: + description: + - The queue to get messages from. + required: True + count: + description: + - How many messages to collect from the queue. + - If not set, defaults to retrieving all the messages from the queue. + requirements: + - The python pika package U(https://pypi.org/project/pika/). + notes: + - This lookup implements BlockingChannel.basic_get to get messages from a RabbitMQ server. + - After retrieving a message from the server, receipt of the message is acknowledged and the message on the server is deleted. + - Pika is a pure-Python implementation of the AMQP 0-9-1 protocol that tries to stay fairly independent of the underlying network support library. + - More information about pika can be found at U(https://pika.readthedocs.io/en/stable/). + - This plugin is tested against RabbitMQ. Other AMQP 0.9.1 protocol based servers may work but not tested/guaranteed. + - Assigning the return messages to a variable under C(vars) may result in unexpected results as the lookup is evaluated every time the + variable is referenced. + - Currently this plugin only handles text based messages from a queue. Unexpected results may occur when retrieving binary data. +''' + + +EXAMPLES = """ +- name: Get all messages off a queue + debug: + msg: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@192.168.0.10:5672/%2F', queue='hello') }}" + + +# If you are intending on using the returned messages as a variable in more than +# one task (eg. debug, template), it is recommended to set_fact. + +- name: Get 2 messages off a queue and set a fact for re-use + set_fact: + messages: "{{ lookup('community.rabbitmq.rabbiotmq', url='amqp://guest:guest@192.168.0.10:5672/%2F', queue='hello', count=2) }}" + +- name: Dump out contents of the messages + debug: + var: messages + +""" + +RETURN = """ + _list: + description: + - A list of dictionaries with keys and value from the queue. + type: list + contains: + content_type: + description: The content_type on the message in the queue. + type: str + delivery_mode: + description: The delivery_mode on the message in the queue. + type: str + delivery_tag: + description: The delivery_tag on the message in the queue. + type: str + exchange: + description: The exchange the message came from. + type: str + message_count: + description: The message_count for the message on the queue. + type: str + msg: + description: The content of the message. + type: str + redelivered: + description: The redelivered flag. True if the message has been delivered before. + type: bool + routing_key: + description: The routing_key on the message in the queue. + type: str + headers: + description: The headers for the message returned from the queue. + type: dict + json: + description: If application/json is specified in content_type, json will be loaded into variables. + type: dict + +""" + +import json + +from ansible.errors import AnsibleError, AnsibleParserError +from ansible.plugins.lookup import LookupBase +from ansible.module_utils._text import to_native, to_text +from ansible.utils.display import Display + +try: + import pika + from pika import spec + HAS_PIKA = True +except ImportError: + HAS_PIKA = False + +display = Display() + + +class LookupModule(LookupBase): + + def run(self, terms, variables=None, url=None, queue=None, count=None): + if not HAS_PIKA: + raise AnsibleError('pika python package is required for rabbitmq lookup.') + if not url: + raise AnsibleError('URL is required for rabbitmq lookup.') + if not queue: + raise AnsibleError('Queue is required for rabbitmq lookup.') + + display.vvv(u"terms:%s : variables:%s url:%s queue:%s count:%s" % (terms, variables, url, queue, count)) + + try: + parameters = pika.URLParameters(url) + except Exception as e: + raise AnsibleError("URL malformed: %s" % to_native(e)) + + try: + connection = pika.BlockingConnection(parameters) + except Exception as e: + raise AnsibleError("Connection issue: %s" % to_native(e)) + + try: + conn_channel = connection.channel() + except pika.exceptions.AMQPChannelError as e: + try: + connection.close() + except pika.exceptions.AMQPConnectionError as ie: + raise AnsibleError("Channel and connection closing issues: %s / %s" % to_native(e), to_native(ie)) + raise AnsibleError("Channel issue: %s" % to_native(e)) + + ret = [] + idx = 0 + + while True: + method_frame, properties, body = conn_channel.basic_get(queue=queue) + if method_frame: + display.vvv(u"%s, %s, %s " % (method_frame, properties, to_text(body))) + + # TODO: In the future consider checking content_type and handle text/binary data differently. + msg_details = dict({ + 'msg': to_text(body), + 'message_count': method_frame.message_count, + 'routing_key': method_frame.routing_key, + 'delivery_tag': method_frame.delivery_tag, + 'redelivered': method_frame.redelivered, + 'exchange': method_frame.exchange, + 'delivery_mode': properties.delivery_mode, + 'content_type': properties.content_type, + 'headers': properties.headers + }) + if properties.content_type == 'application/json': + try: + msg_details['json'] = json.loads(msg_details['msg']) + except ValueError as e: + raise AnsibleError("Unable to decode JSON for message %s: %s" % (method_frame.delivery_tag, to_native(e))) + + ret.append(msg_details) + conn_channel.basic_ack(method_frame.delivery_tag) + idx += 1 + if method_frame.message_count == 0 or idx == count: + break + # If we didn't get a method_frame, exit. + else: + break + + if connection.is_closed: + return [ret] + else: + try: + connection.close() + except pika.exceptions.AMQPConnectionError: + pass + return [ret] diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/module_utils/rabbitmq.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/module_utils/rabbitmq.py new file mode 100644 index 00000000..cf764006 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/module_utils/rabbitmq.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2016, Jorge Rodriguez <jorge.rodriguez@tiriel.eu> +# Copyright: (c) 2018, John Imison <john+github@imison.net> +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from mimetypes import MimeTypes + +import os +import json +import traceback + +PIKA_IMP_ERR = None +try: + import pika + import pika.exceptions + from pika import spec + HAS_PIKA = True +except ImportError: + PIKA_IMP_ERR = traceback.format_exc() + HAS_PIKA = False + + +def rabbitmq_argument_spec(): + return dict( + login_user=dict(type='str', default='guest'), + login_password=dict(type='str', default='guest', no_log=True), + login_host=dict(type='str', default='localhost'), + login_port=dict(type='str', default='15672'), + login_protocol=dict(type='str', default='http', choices=['http', 'https']), + ca_cert=dict(type='path', aliases=['cacert']), + client_cert=dict(type='path', aliases=['cert']), + client_key=dict(type='path', aliases=['key']), + vhost=dict(type='str', default='/'), + ) + + +# notification/rabbitmq_basic_publish.py +class RabbitClient(): + def __init__(self, module): + self.module = module + self.params = module.params + self.check_required_library() + self.check_host_params() + self.url = self.params['url'] + self.proto = self.params['proto'] + self.username = self.params['username'] + self.password = self.params['password'] + self.host = self.params['host'] + self.port = self.params['port'] + self.vhost = self.params['vhost'] + self.queue = self.params['queue'] + self.headers = self.params['headers'] + self.cafile = self.params['cafile'] + self.certfile = self.params['certfile'] + self.keyfile = self.params['keyfile'] + + if self.host is not None: + self.build_url() + + if self.cafile is not None: + self.append_ssl_certs() + + self.connect_to_rabbitmq() + + def check_required_library(self): + if not HAS_PIKA: + self.module.fail_json(msg=missing_required_lib("pika"), exception=PIKA_IMP_ERR) + + def check_host_params(self): + # Fail if url is specified and other conflicting parameters have been specified + if self.params['url'] is not None and any(self.params[k] is not None for k in ['proto', 'host', 'port', 'password', 'username', 'vhost']): + self.module.fail_json(msg="url and proto, host, port, vhost, username or password cannot be specified at the same time.") + + # Fail if url not specified and there is a missing parameter to build the url + if self.params['url'] is None and any(self.params[k] is None for k in ['proto', 'host', 'port', 'password', 'username', 'vhost']): + self.module.fail_json(msg="Connection parameters must be passed via url, or, proto, host, port, vhost, username or password.") + + def append_ssl_certs(self): + ssl_options = {} + if self.cafile: + ssl_options['cafile'] = self.cafile + if self.certfile: + ssl_options['certfile'] = self.certfile + if self.keyfile: + ssl_options['keyfile'] = self.keyfile + + self.url = self.url + '?ssl_options=' + urllib_parse.quote(json.dumps(ssl_options)) + + @staticmethod + def rabbitmq_argument_spec(): + return dict( + url=dict(type='str'), + proto=dict(type='str', choices=['amqp', 'amqps']), + host=dict(type='str'), + port=dict(type='int'), + username=dict(type='str'), + password=dict(type='str', no_log=True), + vhost=dict(type='str'), + queue=dict(type='str') + ) + + ''' Consider some file size limits here ''' + def _read_file(self, path): + try: + with open(path, "rb") as file_handle: + return file_handle.read() + except IOError as e: + self.module.fail_json(msg="Unable to open file %s: %s" % (path, to_native(e))) + + @staticmethod + def _check_file_mime_type(path): + mime = MimeTypes() + return mime.guess_type(path) + + def build_url(self): + self.url = '{0}://{1}:{2}@{3}:{4}/{5}'.format(self.proto, + self.username, + self.password, + self.host, + self.port, + self.vhost) + + def connect_to_rabbitmq(self): + """ + Function to connect to rabbitmq using username and password + """ + try: + parameters = pika.URLParameters(self.url) + except Exception as e: + self.module.fail_json(msg="URL malformed: %s" % to_native(e)) + + try: + self.connection = pika.BlockingConnection(parameters) + except Exception as e: + self.module.fail_json(msg="Connection issue: %s" % to_native(e)) + + try: + self.conn_channel = self.connection.channel() + except pika.exceptions.AMQPChannelError as e: + self.close_connection() + self.module.fail_json(msg="Channel issue: %s" % to_native(e)) + + def close_connection(self): + try: + self.connection.close() + except pika.exceptions.AMQPConnectionError: + pass + + def basic_publish(self): + self.content_type = self.params.get("content_type") + + if self.params.get("body") is not None: + args = dict( + body=self.params.get("body"), + exchange=self.params.get("exchange"), + routing_key=self.params.get("routing_key"), + properties=pika.BasicProperties(content_type=self.content_type, delivery_mode=1, headers=self.headers)) + + # If src (file) is defined and content_type is left as default, do a mime lookup on the file + if self.params.get("src") is not None and self.content_type == 'text/plain': + self.content_type = RabbitClient._check_file_mime_type(self.params.get("src"))[0] + self.headers.update( + filename=os.path.basename(self.params.get("src")) + ) + + args = dict( + body=self._read_file(self.params.get("src")), + exchange=self.params.get("exchange"), + routing_key=self.params.get("routing_key"), + properties=pika.BasicProperties(content_type=self.content_type, + delivery_mode=1, + headers=self.headers + )) + elif self.params.get("src") is not None: + args = dict( + body=self._read_file(self.params.get("src")), + exchange=self.params.get("exchange"), + routing_key=self.params.get("routing_key"), + properties=pika.BasicProperties(content_type=self.content_type, + delivery_mode=1, + headers=self.headers + )) + + try: + # If queue is not defined, RabbitMQ will return the queue name of the automatically generated queue. + if self.queue is None: + result = self.conn_channel.queue_declare(durable=self.params.get("durable"), + exclusive=self.params.get("exclusive"), + auto_delete=self.params.get("auto_delete")) + self.conn_channel.confirm_delivery() + self.queue = result.method.queue + else: + self.conn_channel.queue_declare(queue=self.queue, + durable=self.params.get("durable"), + exclusive=self.params.get("exclusive"), + auto_delete=self.params.get("auto_delete")) + self.conn_channel.confirm_delivery() + except Exception as e: + self.module.fail_json(msg="Queue declare issue: %s" % to_native(e)) + + # https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/cloudstack.py#L150 + if args['routing_key'] is None: + args['routing_key'] = self.queue + + if args['exchange'] is None: + args['exchange'] = '' + + try: + self.conn_channel.basic_publish(**args) + return True + except pika.exceptions.UnroutableError: + return False diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_binding.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_binding.py new file mode 100644 index 00000000..80194e20 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_binding.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Manuel Sousa <manuel.sousa@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_binding +author: Manuel Sousa (@manuel-sousa) + +short_description: Manage rabbitMQ bindings +description: + - This module uses rabbitMQ REST APIs to create / delete bindings. +requirements: [ "requests >= 1.0.0" ] +options: + state: + description: + - Whether the bindings should be present or absent. + type: str + choices: [ "present", "absent" ] + default: present + name: + description: + - source exchange to create binding on. + type: str + required: true + aliases: [ "src", "source" ] + destination: + description: + - destination exchange or queue for the binding. + type: str + required: true + aliases: [ "dst", "dest" ] + destination_type: + description: + - Either queue or exchange. + type: str + required: true + choices: [ "queue", "exchange" ] + aliases: [ "type", "dest_type" ] + routing_key: + description: + - routing key for the binding. + type: str + default: "#" + arguments: + description: + - extra arguments for exchange. If defined this argument is a key/value dictionary + type: dict + required: false + default: {} +extends_documentation_fragment: + - community.rabbitmq.rabbitmq + +''' + +EXAMPLES = ''' +# Bind myQueue to directExchange with routing key info +- community.rabbitmq.rabbitmq_binding: + name: directExchange + destination: myQueue + type: queue + routing_key: info + +# Bind directExchange to topicExchange with routing key *.info +- community.rabbitmq.rabbitmq_binding: + name: topicExchange + destination: topicExchange + type: exchange + routing_key: '*.info' +''' + +import json +import traceback + +REQUESTS_IMP_ERR = None +try: + import requests + HAS_REQUESTS = True +except ImportError: + REQUESTS_IMP_ERR = traceback.format_exc() + HAS_REQUESTS = False + +from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible_collections.community.rabbitmq.plugins.module_utils.rabbitmq import rabbitmq_argument_spec + + +class RabbitMqBinding(object): + def __init__(self, module): + """ + :param module: + """ + self.module = module + self.name = self.module.params['name'] + self.login_user = self.module.params['login_user'] + self.login_password = self.module.params['login_password'] + self.login_host = self.module.params['login_host'] + self.login_port = self.module.params['login_port'] + self.login_protocol = self.module.params['login_protocol'] + self.vhost = self.module.params['vhost'] + self.destination = self.module.params['destination'] + self.destination_type = 'q' if self.module.params['destination_type'] == 'queue' else 'e' + self.routing_key = self.module.params['routing_key'] + self.arguments = self.module.params['arguments'] + self.verify = self.module.params['ca_cert'] + self.cert = self.module.params['client_cert'] + self.key = self.module.params['client_key'] + self.props = urllib_parse.quote(self.routing_key) if self.routing_key != '' else '~' + self.base_url = '{0}://{1}:{2}/api/bindings'.format(self.login_protocol, + self.login_host, + self.login_port) + self.url = '{0}/{1}/e/{2}/{3}/{4}/{5}'.format(self.base_url, + urllib_parse.quote(self.vhost, safe=''), + urllib_parse.quote(self.name, safe=''), + self.destination_type, + urllib_parse.quote(self.destination, safe=''), + self.props) + self.result = { + 'changed': False, + 'name': self.module.params['name'], + } + self.authentication = ( + self.login_user, + self.login_password + ) + self.request = requests + self.http_check_states = { + 200: True, + 404: False, + } + self.http_actionable_states = { + 201: True, + 204: True, + } + self.api_result = self.request.get(self.url, auth=self.authentication, verify=self.verify, cert=(self.cert, self.key)) + + def run(self): + """ + :return: + """ + self.check_presence() + self.check_mode() + self.action_mode() + + def check_presence(self): + """ + :return: + """ + if self.check_should_throw_fail(): + self.fail() + + def change_required(self): + """ + :return: + """ + if self.module.params['state'] == 'present': + if not self.is_present(): + return True + elif self.module.params['state'] == 'absent': + if self.is_present(): + return True + return False + + def is_present(self): + """ + :return: + """ + return self.http_check_states.get(self.api_result.status_code, False) + + def check_mode(self): + """ + :return: + """ + if self.module.check_mode: + result = self.result + result['changed'] = self.change_required() + result['details'] = self.api_result.json() if self.is_present() else self.api_result.text + result['arguments'] = self.module.params['arguments'] + self.module.exit_json(**result) + + def check_reply_is_correct(self): + """ + :return: + """ + if self.api_result.status_code in self.http_check_states: + return True + return False + + def check_should_throw_fail(self): + """ + :return: + """ + if not self.is_present(): + if not self.check_reply_is_correct(): + return True + return False + + def action_mode(self): + """ + :return: + """ + result = self.result + if self.change_required(): + if self.module.params['state'] == 'present': + self.create() + if self.module.params['state'] == 'absent': + self.remove() + if self.action_should_throw_fail(): + self.fail() + result['changed'] = True + result['destination'] = self.module.params['destination'] + self.module.exit_json(**result) + else: + result['changed'] = False + self.module.exit_json(**result) + + def action_reply_is_correct(self): + """ + :return: + """ + if self.api_result.status_code in self.http_actionable_states: + return True + return False + + def action_should_throw_fail(self): + """ + :return: + """ + if not self.action_reply_is_correct(): + return True + return False + + def create(self): + """ + :return: + """ + self.url = '{0}/{1}/e/{2}/{3}/{4}'.format(self.base_url, + urllib_parse.quote(self.vhost, safe=''), + urllib_parse.quote(self.name, safe=''), + self.destination_type, + urllib_parse.quote(self.destination, safe='')) + self.api_result = self.request.post(self.url, + auth=self.authentication, + verify=self.verify, + cert=(self.cert, self.key), + headers={"content-type": "application/json"}, + data=json.dumps({ + 'routing_key': self.routing_key, + 'arguments': self.arguments + })) + + def remove(self): + """ + :return: + """ + self.api_result = self.request.delete(self.url, auth=self.authentication, verify=self.verify, cert=(self.cert, self.key)) + + def fail(self): + """ + :return: + """ + self.module.fail_json( + msg="Unexpected reply from API", + status=self.api_result.status_code, + details=self.api_result.text + ) + + +def main(): + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( + state=dict(default='present', choices=['present', 'absent'], type='str'), + name=dict(required=True, aliases=["src", "source"], type='str'), + destination=dict(required=True, aliases=["dst", "dest"], type='str'), + destination_type=dict(required=True, aliases=["type", "dest_type"], choices=["queue", "exchange"], + type='str'), + routing_key=dict(default='#', type='str'), + arguments=dict(default=dict(), type='dict') + ) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_REQUESTS: + module.fail_json(msg=missing_required_lib("requests"), exception=REQUESTS_IMP_ERR) + + RabbitMqBinding(module).run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_exchange.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_exchange.py new file mode 100644 index 00000000..0feb07ed --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_exchange.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Manuel Sousa <manuel.sousa@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_exchange +author: Manuel Sousa (@manuel-sousa) + +short_description: Manage rabbitMQ exchanges +description: + - This module uses rabbitMQ Rest API to create/delete exchanges +requirements: [ "requests >= 1.0.0" ] +options: + name: + description: + - Name of the exchange to create + type: str + required: true + state: + description: + - Whether the exchange should be present or absent + type: str + choices: [ "present", "absent" ] + required: false + default: present + durable: + description: + - whether exchange is durable or not + type: bool + required: false + default: yes + exchange_type: + description: + - type for the exchange + type: str + required: false + choices: [ "fanout", "direct", "headers", "topic" ] + aliases: [ "type" ] + default: direct + auto_delete: + description: + - if the exchange should delete itself after all queues/exchanges unbound from it + type: bool + required: false + default: no + internal: + description: + - exchange is available only for other exchanges + type: bool + required: false + default: no + arguments: + description: + - extra arguments for exchange. If defined this argument is a key/value dictionary + type: dict + required: false + default: {} +extends_documentation_fragment: + - community.rabbitmq.rabbitmq + +''' + +EXAMPLES = ''' +# Create direct exchange +- community.rabbitmq.rabbitmq_exchange: + name: directExchange + +# Create topic exchange on vhost +- community.rabbitmq.rabbitmq_exchange: + name: topicExchange + type: topic + vhost: myVhost +''' + +import json +import traceback + +REQUESTS_IMP_ERR = None +try: + import requests + HAS_REQUESTS = True +except ImportError: + REQUESTS_IMP_ERR = traceback.format_exc() + HAS_REQUESTS = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from ansible_collections.community.rabbitmq.plugins.module_utils.rabbitmq import rabbitmq_argument_spec + + +def main(): + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( + state=dict(default='present', choices=['present', 'absent'], type='str'), + name=dict(required=True, type='str'), + durable=dict(default=True, type='bool'), + auto_delete=dict(default=False, type='bool'), + internal=dict(default=False, type='bool'), + exchange_type=dict(default='direct', aliases=['type'], + choices=['fanout', 'direct', 'headers', 'topic'], + type='str'), + arguments=dict(default=dict(), type='dict') + ) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + url = "%s://%s:%s/api/exchanges/%s/%s" % ( + module.params['login_protocol'], + module.params['login_host'], + module.params['login_port'], + urllib_parse.quote(module.params['vhost'], ''), + urllib_parse.quote(module.params['name'], '') + ) + + if not HAS_REQUESTS: + module.fail_json(msg=missing_required_lib("requests"), exception=REQUESTS_IMP_ERR) + + result = dict(changed=False, name=module.params['name']) + + # Check if exchange already exists + r = requests.get(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['ca_cert'], cert=(module.params['client_cert'], module.params['client_key'])) + + if r.status_code == 200: + exchange_exists = True + response = r.json() + elif r.status_code == 404: + exchange_exists = False + response = r.text + else: + module.fail_json( + msg="Invalid response from RESTAPI when trying to check if exchange exists", + details=r.text + ) + + if module.params['state'] == 'present': + change_required = not exchange_exists + else: + change_required = exchange_exists + + # Check if attributes change on existing exchange + if not change_required and r.status_code == 200 and module.params['state'] == 'present': + if not ( + response['durable'] == module.params['durable'] and + response['auto_delete'] == module.params['auto_delete'] and + response['internal'] == module.params['internal'] and + response['type'] == module.params['exchange_type'] + ): + module.fail_json( + msg="RabbitMQ RESTAPI doesn't support attribute changes for existing exchanges" + ) + + # Exit if check_mode + if module.check_mode: + result['changed'] = change_required + result['details'] = response + result['arguments'] = module.params['arguments'] + module.exit_json(**result) + + # Do changes + if change_required: + if module.params['state'] == 'present': + r = requests.put( + url, + auth=(module.params['login_user'], module.params['login_password']), + headers={"content-type": "application/json"}, + data=json.dumps({ + "durable": module.params['durable'], + "auto_delete": module.params['auto_delete'], + "internal": module.params['internal'], + "type": module.params['exchange_type'], + "arguments": module.params['arguments'] + }), + verify=module.params['ca_cert'], + cert=(module.params['client_cert'], module.params['client_key']) + ) + elif module.params['state'] == 'absent': + r = requests.delete(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['ca_cert'], cert=(module.params['client_cert'], module.params['client_key'])) + + # RabbitMQ 3.6.7 changed this response code from 204 to 201 + if r.status_code == 204 or r.status_code == 201: + result['changed'] = True + module.exit_json(**result) + else: + module.fail_json( + msg="Error creating exchange", + status=r.status_code, + details=r.text + ) + + else: + module.exit_json( + changed=False, + name=module.params['name'] + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_global_parameter.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_global_parameter.py new file mode 100644 index 00000000..97fc90cb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_global_parameter.py @@ -0,0 +1,155 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# Copyright: (c) 2017, Juergen Kirschbaum <jk@jk-itc.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_global_parameter +short_description: Manage RabbitMQ global parameters +description: + - Manage dynamic, cluster-wide global parameters for RabbitMQ +author: "Juergen Kirschbaum (@jgkirschbaum)" +options: + name: + description: + - Name of the global parameter being set + type: str + required: true + default: null + value: + description: + - Value of the global parameter, as a JSON term + type: str + required: false + default: null + node: + description: + - erlang node name of the rabbit we wish to configure + type: str + required: false + default: rabbit + state: + description: + - Specify if user is to be added or removed + type: str + required: false + default: present + choices: [ 'present', 'absent'] +''' + +EXAMPLES = ''' +# Set the global parameter 'cluster_name' to a value of 'mq-cluster' (in quotes) +- community.rabbitmq.rabbitmq_global_parameter: + name: cluster_name + value: "{{ 'mq-cluster' | to_json }}" + state: present +''' + +RETURN = ''' +name: + description: name of the global parameter being set + returned: success + type: str + sample: "cluster_name" +value: + description: value of the global parameter, as a JSON term + returned: changed + type: str + sample: "the-cluster-name" +''' + +import json +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqGlobalParameter(object): + def __init__(self, module, name, value, node): + self.module = module + self.name = name + self.value = value + self.node = node + + self._value = None + + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + def _exec(self, args, run_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and run_in_check_mode): + cmd = [self._rabbitmqctl, '-q', '-n', self.node] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.splitlines() + return list() + + def get(self): + global_parameters = self._exec(['list_global_parameters'], True) + + for param_item in global_parameters: + name, value = param_item.split('\t') + + if name == self.name: + self._value = json.loads(value) + return True + return False + + def set(self): + self._exec(['set_global_parameter', + self.name, + json.dumps(self.value)]) + + def delete(self): + self._exec(['clear_global_parameter', self.name]) + + def has_modifications(self): + return self.value != self._value + + +def main(): + arg_spec = dict( + name=dict(type='str', required=True), + value=dict(type='str', default=None), + state=dict(default='present', choices=['present', 'absent']), + node=dict(type='str', default='rabbit') + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + name = module.params['name'] + value = module.params['value'] + if isinstance(value, str): + value = json.loads(value) + state = module.params['state'] + node = module.params['node'] + + result = dict(changed=False) + rabbitmq_global_parameter = RabbitMqGlobalParameter(module, name, value, node) + + if rabbitmq_global_parameter.get(): + if state == 'absent': + rabbitmq_global_parameter.delete() + result['changed'] = True + else: + if rabbitmq_global_parameter.has_modifications(): + rabbitmq_global_parameter.set() + result['changed'] = True + elif state == 'present': + rabbitmq_global_parameter.set() + result['changed'] = True + + result['name'] = name + result['value'] = value + result['state'] = state + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_parameter.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_parameter.py new file mode 100644 index 00000000..d88bdcd6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_parameter.py @@ -0,0 +1,155 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_parameter +short_description: Manage RabbitMQ parameters +description: + - Manage dynamic, cluster-wide parameters for RabbitMQ +author: Chris Hoffman (@chrishoffman) +options: + component: + description: + - Name of the component of which the parameter is being set + type: str + required: true + name: + description: + - Name of the parameter being set + type: str + required: true + value: + description: + - Value of the parameter, as a JSON term + type: str + vhost: + description: + - vhost to apply access privileges. + type: str + default: / + node: + description: + - erlang node name of the rabbit we wish to configure + type: str + default: rabbit + state: + description: + - Specify if user is to be added or removed + type: str + default: present + choices: [ 'present', 'absent'] +''' + +EXAMPLES = """ +# Set the federation parameter 'local_username' to a value of 'guest' (in quotes) +- community.rabbitmq.rabbitmq_parameter: + component: federation + name: local-username + value: '"guest"' + state: present +""" +import json +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqParameter(object): + def __init__(self, module, component, name, value, vhost, node): + self.module = module + self.component = component + self.name = name + self.value = value + self.vhost = vhost + self.node = node + + self._value = None + + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + def _exec(self, args, run_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and run_in_check_mode): + cmd = [self._rabbitmqctl, '-q', '-n', self.node] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.strip().splitlines() + return list() + + def get(self): + parameters = [param for param in self._exec(['list_parameters', '-p', self.vhost], True) if param.strip()] + + for param_item in parameters: + component, name, value = param_item.split('\t') + + if component == self.component and name == self.name: + self._value = json.loads(value) + return True + return False + + def set(self): + self._exec(['set_parameter', + '-p', + self.vhost, + self.component, + self.name, + json.dumps(self.value)]) + + def delete(self): + self._exec(['clear_parameter', '-p', self.vhost, self.component, self.name]) + + def has_modifications(self): + return self.value != self._value + + +def main(): + arg_spec = dict( + component=dict(required=True), + name=dict(required=True), + value=dict(default=None), + vhost=dict(default='/'), + state=dict(default='present', choices=['present', 'absent']), + node=dict(default='rabbit') + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + component = module.params['component'] + name = module.params['name'] + value = module.params['value'] + if isinstance(value, str): + value = json.loads(value) + vhost = module.params['vhost'] + state = module.params['state'] + node = module.params['node'] + + result = dict(changed=False) + rabbitmq_parameter = RabbitMqParameter(module, component, name, value, vhost, node) + + if rabbitmq_parameter.get(): + if state == 'absent': + rabbitmq_parameter.delete() + result['changed'] = True + else: + if rabbitmq_parameter.has_modifications(): + rabbitmq_parameter.set() + result['changed'] = True + elif state == 'present': + rabbitmq_parameter.set() + result['changed'] = True + + result['component'] = component + result['name'] = name + result['vhost'] = vhost + result['state'] = state + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_plugin.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_plugin.py new file mode 100644 index 00000000..87a08b9f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_plugin.py @@ -0,0 +1,187 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_plugin +short_description: Manage RabbitMQ plugins +description: + - This module can be used to enable or disable RabbitMQ plugins. +author: + - Chris Hoffman (@chrishoffman) +options: + names: + description: + - Comma-separated list of plugin names. Also, accepts plugin name. + type: str + required: true + aliases: [name] + new_only: + description: + - Only enable missing plugins. + - Does not disable plugins that are not in the names list. + type: bool + default: "no" + state: + description: + - Specify if plugins are to be enabled or disabled. + type: str + default: enabled + choices: [enabled, disabled] + broker_state: + description: + - Specify whether the broker should be online or offline for the plugin change. + type: str + default: online + choices: [online, offline] + prefix: + description: + - Specify a custom install prefix to a Rabbit. + type: str +''' + +EXAMPLES = ''' +- name: Enables the rabbitmq_management plugin + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_management + state: enabled + +- name: Enable multiple rabbitmq plugins + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_management,rabbitmq_management_visualiser + state: enabled + +- name: Disable plugin + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_management + state: disabled + +- name: Enable every plugin in list with existing plugins + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_management,rabbitmq_management_visualiser,rabbitmq_shovel,rabbitmq_shovel_management + state: enabled + new_only: 'yes' + +- name: Enables the rabbitmq_peer_discovery_aws plugin without requiring a broker connection. + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_peer_discovery_aws_plugin + state: enabled + broker_state: offline +''' + +RETURN = ''' +enabled: + description: list of plugins enabled during task run + returned: always + type: list + sample: ["rabbitmq_management"] +disabled: + description: list of plugins disabled during task run + returned: always + type: list + sample: ["rabbitmq_management"] +''' + +import os +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqPlugins(object): + + def __init__(self, module): + self.module = module + bin_path = '' + if module.params['prefix']: + if os.path.isdir(os.path.join(module.params['prefix'], 'bin')): + bin_path = os.path.join(module.params['prefix'], 'bin') + elif os.path.isdir(os.path.join(module.params['prefix'], 'sbin')): + bin_path = os.path.join(module.params['prefix'], 'sbin') + else: + # No such path exists. + module.fail_json(msg="No binary folder in prefix %s" % module.params['prefix']) + + self._rabbitmq_plugins = os.path.join(bin_path, "rabbitmq-plugins") + else: + self._rabbitmq_plugins = module.get_bin_path('rabbitmq-plugins', True) + + def _exec(self, args, run_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and run_in_check_mode): + cmd = [self._rabbitmq_plugins] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.splitlines() + return list() + + def get_all(self): + list_output = self._exec(['list', '-E', '-m'], True) + plugins = [] + for plugin in list_output: + if not plugin: + break + plugins.append(plugin) + + return plugins + + def enable(self, name): + self._exec(['enable', "--%s" % self.module.params['broker_state'], name]) + + def disable(self, name): + self._exec(['disable', "--%s" % self.module.params['broker_state'], name]) + + +def main(): + arg_spec = dict( + names=dict(required=True, aliases=['name']), + new_only=dict(default='no', type='bool'), + state=dict(default='enabled', choices=['enabled', 'disabled']), + broker_state=dict(default='online', choices=['online', 'offline']), + prefix=dict(required=False, default=None) + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + result = dict() + names = module.params['names'].split(',') + new_only = module.params['new_only'] + state = module.params['state'] + + rabbitmq_plugins = RabbitMqPlugins(module) + enabled_plugins = rabbitmq_plugins.get_all() + + enabled = [] + disabled = [] + if state == 'enabled': + if not new_only: + for plugin in enabled_plugins: + if " " in plugin: + continue + if plugin not in names: + rabbitmq_plugins.disable(plugin) + disabled.append(plugin) + + for name in names: + if name not in enabled_plugins: + rabbitmq_plugins.enable(name) + enabled.append(name) + else: + for plugin in enabled_plugins: + if plugin in names: + rabbitmq_plugins.disable(plugin) + disabled.append(plugin) + + result['changed'] = len(enabled) > 0 or len(disabled) > 0 + result['enabled'] = enabled + result['disabled'] = disabled + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_policy.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_policy.py new file mode 100644 index 00000000..41faebaa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_policy.py @@ -0,0 +1,248 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, John Dewey <john@dewey.ws> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_policy +short_description: Manage the state of policies in RabbitMQ +description: + - Manage the state of a policy in RabbitMQ. +author: John Dewey (@retr0h) +options: + name: + description: + - The name of the policy to manage. + type: str + required: true + vhost: + description: + - The name of the vhost to apply to. + type: str + default: / + apply_to: + description: + - What the policy applies to. Requires RabbitMQ 3.2.0 or later. + type: str + default: all + choices: [all, exchanges, queues] + pattern: + description: + - A regex of queues to apply the policy to. Required when + C(state=present). This option is no longer required as of Ansible 2.9. + type: str + required: false + default: null + tags: + description: + - A dict or string describing the policy. Required when + C(state=present). This option is no longer required as of Ansible 2.9. + type: dict + required: false + default: null + priority: + description: + - The priority of the policy. + type: str + default: '0' + node: + description: + - Erlang node name of the rabbit we wish to configure. + type: str + default: rabbit + state: + description: + - The state of the policy. + type: str + default: present + choices: [present, absent] +''' + +EXAMPLES = ''' +- name: ensure the default vhost contains the HA policy via a dict + community.rabbitmq.rabbitmq_policy: + name: HA + pattern: .* + args: + tags: + ha-mode: all + +- name: ensure the default vhost contains the HA policy + community.rabbitmq.rabbitmq_policy: + name: HA + pattern: .* + tags: + ha-mode: all +''' + +import json +import re +from distutils.version import LooseVersion as Version + +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqPolicy(object): + + def __init__(self, module, name): + self._module = module + self._name = name + self._vhost = module.params['vhost'] + self._pattern = module.params['pattern'] + self._apply_to = module.params['apply_to'] + self._tags = module.params['tags'] + self._priority = module.params['priority'] + self._node = module.params['node'] + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + self._version = self._rabbit_version() + + def _exec(self, + args, + run_in_check_mode=False, + split_lines=True, + add_vhost=True): + if (not self._module.check_mode + or (self._module.check_mode and run_in_check_mode)): + cmd = [self._rabbitmqctl, '-q', '-n', self._node] + + if add_vhost: + args.insert(1, '-p') + args.insert(2, self._vhost) + + rc, out, err = self._module.run_command(cmd + args, check_rc=True) + if split_lines: + return out.splitlines() + + return out + return list() + + def _rabbit_version(self): + status = self._exec(['status'], True, False, False) + + # 3.7.x erlang style output + version_match = re.search('{rabbit,".*","(?P<version>.*)"}', status) + if version_match: + return Version(version_match.group('version')) + + # 3.8.x style ouput + version_match = re.search('RabbitMQ version: (?P<version>.*)', status) + if version_match: + return Version(version_match.group('version')) + + return None + + def _list_policies(self): + if self._version and self._version >= Version('3.7.9'): + # Remove first header line from policies list for version > 3.7.9 + return self._exec(['list_policies'], True)[1:] + + return self._exec(['list_policies'], True) + + def has_modifications(self): + if self._pattern is None or self._tags is None: + self._module.fail_json( + msg=('pattern and tags are required for ' + 'state=present')) + + if self._version and self._version >= Version('3.7.0'): + # Change fields order in rabbitmqctl output in version 3.7 + return not any( + self._policy_check(policy, apply_to_fno=3, pattern_fno=2) + for policy in self._list_policies()) + else: + return not any( + self._policy_check(policy) for policy in self._list_policies()) + + def should_be_deleted(self): + return any( + self._policy_check_by_name(policy) + for policy in self._list_policies()) + + def set(self): + args = ['set_policy'] + args.append(self._name) + args.append(self._pattern) + args.append(json.dumps(self._tags)) + args.append('--priority') + args.append(self._priority) + if self._apply_to != 'all': + args.append('--apply-to') + args.append(self._apply_to) + return self._exec(args) + + def clear(self): + return self._exec(['clear_policy', self._name]) + + def _policy_check(self, + policy, + name_fno=1, + apply_to_fno=2, + pattern_fno=3, + tags_fno=4, + priority_fno=5): + if not policy: + return False + + policy_data = policy.split('\t') + + policy_name = policy_data[name_fno] + apply_to = policy_data[apply_to_fno] + pattern = policy_data[pattern_fno].replace('\\\\', '\\') + tags = json.loads(policy_data[tags_fno]) + priority = policy_data[priority_fno] + + return (policy_name == self._name and apply_to == self._apply_to + and tags == self._tags and priority == self._priority + and pattern == self._pattern) + + def _policy_check_by_name(self, policy): + if not policy: + return False + + policy_name = policy.split('\t')[1] + + return policy_name == self._name + + +def main(): + arg_spec = dict( + name=dict(required=True), + vhost=dict(default='/'), + pattern=dict(required=False, default=None), + apply_to=dict(default='all', choices=['all', 'exchanges', 'queues']), + tags=dict(type='dict', required=False, default=None), + priority=dict(default='0'), + node=dict(default='rabbit'), + state=dict(default='present', choices=['present', 'absent']), + ) + + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + name = module.params['name'] + state = module.params['state'] + rabbitmq_policy = RabbitMqPolicy(module, name) + + result = dict(changed=False, name=name, state=state) + + if state == 'present' and rabbitmq_policy.has_modifications(): + rabbitmq_policy.set() + result['changed'] = True + elif state == 'absent' and rabbitmq_policy.should_be_deleted(): + rabbitmq_policy.clear() + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_publish.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_publish.py new file mode 100644 index 00000000..12f7af31 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_publish.py @@ -0,0 +1,220 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, John Imison <john+github@imison.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_publish +short_description: Publish a message to a RabbitMQ queue. +description: + - Publish a message on a RabbitMQ queue using a blocking connection. +options: + url: + description: + - An URL connection string to connect to the RabbitMQ server. + - I(url) and I(host)/I(port)/I(user)/I(pass)/I(vhost) are mutually exclusive, use either or but not both. + type: str + proto: + description: + - The protocol to use. + type: str + choices: [amqps, amqp] + host: + description: + - The RabbitMQ server hostname or IP. + type: str + port: + description: + - The RabbitMQ server port. + type: int + username: + description: + - The RabbitMQ username. + type: str + password: + description: + - The RabbitMQ password. + type: str + vhost: + description: + - The virtual host to target. + - If default vhost is required, use C('%2F'). + type: str + queue: + description: + - The queue to publish a message to. If no queue is specified, RabbitMQ will return a random queue name. + type: str + exchange: + description: + - The exchange to publish a message to. + type: str + routing_key: + description: + - The routing key. + type: str + body: + description: + - The body of the message. + - A C(body) cannot be provided if a C(src) is specified. + type: str + src: + description: + - A file to upload to the queue. Automatic mime type detection is attempted if content_type is not defined (left as default). + - A C(src) cannot be provided if a C(body) is specified. + - The filename is added to the headers of the posted message to RabbitMQ. Key being the C(filename), value is the filename. + type: path + aliases: ['file'] + content_type: + description: + - The content type of the body. + type: str + default: text/plain + durable: + description: + - Set the queue to be durable. + type: bool + default: False + exclusive: + description: + - Set the queue to be exclusive. + type: bool + default: False + auto_delete: + description: + - Set the queue to auto delete. + type: bool + default: False + headers: + description: + - A dictionary of headers to post with the message. + type: dict + default: {} + cafile: + description: + - CA file used during connection to the RabbitMQ server over SSL. + - If this option is specified, also I(certfile) and I(keyfile) must be specified. + type: str + certfile: + description: + - Client certificate to establish SSL connection. + - If this option is specified, also I(cafile) and I(keyfile) must be specified. + type: str + keyfile: + description: + - Client key to establish SSL connection. + - If this option is specified, also I(cafile) and I(certfile) must be specified. + type: str + + + +requirements: [ pika ] +notes: + - This module requires the pika python library U(https://pika.readthedocs.io/). + - Pika is a pure-Python implementation of the AMQP 0-9-1 protocol that tries to stay fairly independent of the underlying network support library. + - This module is tested against RabbitMQ. Other AMQP 0.9.1 protocol based servers may work but not tested/guaranteed. + - The certificate authentication was tested with certificates created + via U(https://www.rabbitmq.com/ssl.html#automated-certificate-generation) and RabbitMQ + configuration variables C(ssl_options.verify = verify_peer) & C(ssl_options.fail_if_no_peer_cert = true). +author: "John Imison (@Im0)" +''' + +EXAMPLES = ''' +- name: Publish a message to a queue with headers + community.rabbitmq.rabbitmq_publish: + url: "amqp://guest:guest@192.168.0.32:5672/%2F" + queue: 'test' + body: "Hello world from ansible module rabbitmq_publish" + content_type: "text/plain" + headers: + myHeader: myHeaderValue + + +- name: Publish a file to a queue + community.rabbitmq.rabbitmq_publish: + url: "amqp://guest:guest@192.168.0.32:5672/%2F" + queue: 'images' + file: 'path/to/logo.gif' + +- name: RabbitMQ auto generated queue + community.rabbitmq.rabbitmq_publish: + url: "amqp://guest:guest@192.168.0.32:5672/%2F" + body: "Hello world random queue from ansible module rabbitmq_publish" + content_type: "text/plain" + +- name: Publish with certs + community.rabbitmq.rabbitmq_publish: + url: "amqps://guest:guest@192.168.0.32:5671/%2F" + body: "Hello test queue from ansible module rabbitmq_publish via SSL certs" + queue: 'test' + content_type: "text/plain" + cafile: 'ca_certificate.pem' + certfile: 'client_certificate.pem' + keyfile: 'client_key.pem' + +''' + +RETURN = ''' +result: + description: + - Contains the status I(msg), content type I(content_type) and the queue name I(queue). + returned: success + type: dict + sample: | + 'result': { 'content_type': 'text/plain', 'msg': 'Successfully published to queue test', 'queue': 'test' } +''' + +try: + import pika + HAS_PIKA = True +except ImportError: + HAS_PIKA = False + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native, to_text +from ansible_collections.community.rabbitmq.plugins.module_utils.rabbitmq import RabbitClient + + +def main(): + argument_spec = RabbitClient.rabbitmq_argument_spec() + argument_spec.update( + exchange=dict(type='str', default=''), + routing_key=dict(type='str', required=False), + body=dict(type='str', required=False), + src=dict(aliases=['file'], type='path', required=False), + content_type=dict(default="text/plain", type='str'), + durable=dict(default=False, type='bool'), + exclusive=dict(default=False, type='bool'), + auto_delete=dict(default=False, type='bool'), + headers=dict(default={}, type='dict'), + cafile=dict(type='str', required=False), + certfile=dict(type='str', required=False), + keyfile=dict(type='str', required=False), + ) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[['body', 'src']], + required_together=[['cafile', 'certfile', 'keyfile']], + supports_check_mode=False + ) + + rabbitmq = RabbitClient(module) + + if rabbitmq.basic_publish(): + rabbitmq.close_connection() + module.exit_json(changed=True, result={"msg": "Successfully published to queue %s" % rabbitmq.queue, + "queue": rabbitmq.queue, + "content_type": rabbitmq.content_type}) + else: + rabbitmq.close_connection() + module.fail_json(changed=False, msg="Unsuccessful publishing to queue %s" % rabbitmq.queue) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_queue.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_queue.py new file mode 100644 index 00000000..85878773 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_queue.py @@ -0,0 +1,257 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Manuel Sousa <manuel.sousa@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_queue +author: Manuel Sousa (@manuel-sousa) + +short_description: Manage rabbitMQ queues +description: + - This module uses rabbitMQ Rest API to create/delete queues +requirements: [ "requests >= 1.0.0" ] +options: + name: + description: + - Name of the queue + type: str + required: true + state: + description: + - Whether the queue should be present or absent + type: str + choices: [ "present", "absent" ] + default: present + durable: + description: + - whether queue is durable or not + type: bool + default: 'yes' + auto_delete: + description: + - if the queue should delete itself after all queues/queues unbound from it + type: bool + default: 'no' + message_ttl: + description: + - How long a message can live in queue before it is discarded (milliseconds) + type: int + auto_expires: + description: + - How long a queue can be unused before it is automatically deleted (milliseconds) + type: int + max_length: + description: + - How many messages can the queue contain before it starts rejecting + type: int + dead_letter_exchange: + description: + - Optional name of an exchange to which messages will be republished if they + - are rejected or expire + type: str + dead_letter_routing_key: + description: + - Optional replacement routing key to use when a message is dead-lettered. + - Original routing key will be used if unset + type: str + max_priority: + description: + - Maximum number of priority levels for the queue to support. + - If not set, the queue will not support message priorities. + - Larger numbers indicate higher priority. + type: int + arguments: + description: + - extra arguments for queue. If defined this argument is a key/value dictionary + type: dict + default: {} +extends_documentation_fragment: +- community.rabbitmq.rabbitmq + +''' + +EXAMPLES = ''' +# Create a queue +- community.rabbitmq.rabbitmq_queue: + name: myQueue + +# Create a queue on remote host +- community.rabbitmq.rabbitmq_queue: + name: myRemoteQueue + login_user: user + login_password: secret + login_host: remote.example.org +''' + +import json +import traceback + +REQUESTS_IMP_ERR = None +try: + import requests + HAS_REQUESTS = True +except ImportError: + REQUESTS_IMP_ERR = traceback.format_exc() + HAS_REQUESTS = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from ansible_collections.community.rabbitmq.plugins.module_utils.rabbitmq import rabbitmq_argument_spec + + +def main(): + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( + state=dict(default='present', choices=['present', 'absent'], type='str'), + name=dict(required=True, type='str'), + durable=dict(default=True, type='bool'), + auto_delete=dict(default=False, type='bool'), + message_ttl=dict(default=None, type='int'), + auto_expires=dict(default=None, type='int'), + max_length=dict(default=None, type='int'), + dead_letter_exchange=dict(default=None, type='str'), + dead_letter_routing_key=dict(default=None, type='str'), + arguments=dict(default=dict(), type='dict'), + max_priority=dict(default=None, type='int') + ) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + url = "%s://%s:%s/api/queues/%s/%s" % ( + module.params['login_protocol'], + module.params['login_host'], + module.params['login_port'], + urllib_parse.quote(module.params['vhost'], ''), + module.params['name'] + ) + + if not HAS_REQUESTS: + module.fail_json(msg=missing_required_lib("requests"), exception=REQUESTS_IMP_ERR) + + result = dict(changed=False, name=module.params['name']) + + # Check if queue already exists + r = requests.get(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['ca_cert'], cert=(module.params['client_cert'], module.params['client_key'])) + + if r.status_code == 200: + queue_exists = True + response = r.json() + elif r.status_code == 404: + queue_exists = False + response = r.text + else: + module.fail_json( + msg="Invalid response from RESTAPI when trying to check if queue exists", + details=r.text + ) + + if module.params['state'] == 'present': + change_required = not queue_exists + else: + change_required = queue_exists + + # Check if attributes change on existing queue + if not change_required and r.status_code == 200 and module.params['state'] == 'present': + if not ( + response['durable'] == module.params['durable'] and + response['auto_delete'] == module.params['auto_delete'] and + ( + ('x-message-ttl' in response['arguments'] and response['arguments']['x-message-ttl'] == module.params['message_ttl']) or + ('x-message-ttl' not in response['arguments'] and module.params['message_ttl'] is None) + ) and + ( + ('x-expires' in response['arguments'] and response['arguments']['x-expires'] == module.params['auto_expires']) or + ('x-expires' not in response['arguments'] and module.params['auto_expires'] is None) + ) and + ( + ('x-max-length' in response['arguments'] and response['arguments']['x-max-length'] == module.params['max_length']) or + ('x-max-length' not in response['arguments'] and module.params['max_length'] is None) + ) and + ( + ('x-dead-letter-exchange' in response['arguments'] and + response['arguments']['x-dead-letter-exchange'] == module.params['dead_letter_exchange']) or + ('x-dead-letter-exchange' not in response['arguments'] and module.params['dead_letter_exchange'] is None) + ) and + ( + ('x-dead-letter-routing-key' in response['arguments'] and + response['arguments']['x-dead-letter-routing-key'] == module.params['dead_letter_routing_key']) or + ('x-dead-letter-routing-key' not in response['arguments'] and module.params['dead_letter_routing_key'] is None) + ) and + ( + ('x-max-priority' in response['arguments'] and + response['arguments']['x-max-priority'] == module.params['max_priority']) or + ('x-max-priority' not in response['arguments'] and module.params['max_priority'] is None) + ) + ): + module.fail_json( + msg="RabbitMQ RESTAPI doesn't support attribute changes for existing queues", + ) + + # Copy parameters to arguments as used by RabbitMQ + for k, v in { + 'message_ttl': 'x-message-ttl', + 'auto_expires': 'x-expires', + 'max_length': 'x-max-length', + 'dead_letter_exchange': 'x-dead-letter-exchange', + 'dead_letter_routing_key': 'x-dead-letter-routing-key', + 'max_priority': 'x-max-priority' + }.items(): + if module.params[k] is not None: + module.params['arguments'][v] = module.params[k] + + # Exit if check_mode + if module.check_mode: + result['changed'] = change_required + result['details'] = response + result['arguments'] = module.params['arguments'] + module.exit_json(**result) + + # Do changes + if change_required: + if module.params['state'] == 'present': + r = requests.put( + url, + auth=(module.params['login_user'], module.params['login_password']), + headers={"content-type": "application/json"}, + data=json.dumps({ + "durable": module.params['durable'], + "auto_delete": module.params['auto_delete'], + "arguments": module.params['arguments'] + }), + verify=module.params['ca_cert'], + cert=(module.params['client_cert'], module.params['client_key']) + ) + elif module.params['state'] == 'absent': + r = requests.delete(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['ca_cert'], cert=(module.params['client_cert'], module.params['client_key'])) + + # RabbitMQ 3.6.7 changed this response code from 204 to 201 + if r.status_code == 204 or r.status_code == 201: + result['changed'] = True + module.exit_json(**result) + else: + module.fail_json( + msg="Error creating queue", + status=r.status_code, + details=r.text + ) + + else: + module.exit_json( + changed=False, + name=module.params['name'] + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user.py new file mode 100644 index 00000000..1902e88c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user.py @@ -0,0 +1,463 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_user +short_description: Manage RabbitMQ users +description: + - Add or remove users to RabbitMQ and assign permissions +author: Chris Hoffman (@chrishoffman) +options: + user: + description: + - Name of user to add + type: str + required: true + aliases: [username, name] + password: + description: + - Password of user to add. + - To change the password of an existing user, you must also specify + C(update_password=always). + type: str + tags: + description: + - User tags specified as comma delimited + type: str + permissions: + description: + - a list of dicts, each dict contains vhost, configure_priv, write_priv, and read_priv, + and represents a permission rule for that vhost. + - This option should be preferable when you care about all permissions of the user. + - You should use vhost, configure_priv, write_priv, and read_priv options instead + if you care about permissions for just some vhosts. + type: list + elements: dict + default: [] + vhost: + description: + - vhost to apply access privileges. + - This option will be ignored when permissions option is used. + type: str + default: / + node: + description: + - erlang node name of the rabbit we wish to configure + type: str + default: rabbit + configure_priv: + description: + - Regular expression to restrict configure actions on a resource + for the specified vhost. + - By default all actions are restricted. + - This option will be ignored when permissions option is used. + type: str + default: '^$' + write_priv: + description: + - Regular expression to restrict configure actions on a resource + for the specified vhost. + - By default all actions are restricted. + - This option will be ignored when permissions option is used. + type: str + default: '^$' + read_priv: + description: + - Regular expression to restrict configure actions on a resource + for the specified vhost. + - By default all actions are restricted. + - This option will be ignored when permissions option is used. + type: str + default: '^$' + force: + description: + - Deletes and recreates the user. + type: bool + default: 'no' + state: + description: + - Specify if user is to be added or removed + type: str + default: present + choices: ['present', 'absent'] + update_password: + description: + - C(on_create) will only set the password for newly created users. C(always) will update passwords if they differ. + type: str + required: false + default: on_create + choices: ['on_create', 'always'] +''' + +EXAMPLES = ''' +# Add user to server and assign full access control on / vhost. +# The user might have permission rules for other vhost but you don't care. +- community.rabbitmq.rabbitmq_user: + user: joe + password: changeme + vhost: / + configure_priv: .* + read_priv: .* + write_priv: .* + state: present + +# Add user to server and assign full access control on / vhost. +# The user doesn't have permission rules for other vhosts +- community.rabbitmq.rabbitmq_user: + user: joe + password: changeme + permissions: + - vhost: / + configure_priv: .* + read_priv: .* + write_priv: .* + state: present +''' + +import distutils.version +import json +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.collections import count + + +def normalized_permissions(vhost_permission_list): + """Older versions of RabbitMQ output permissions with slightly different names. + + In older versions of RabbitMQ, the names of the permissions had the `_priv` suffix, which was removed in versions + >= 3.7.6. For simplicity we only check the `configure` permission. If it's in the old format then all the other + ones will be wrong too. + """ + for vhost_permission in vhost_permission_list: + if 'configure_priv' in vhost_permission: + yield { + 'configure': vhost_permission['configure_priv'], + 'read': vhost_permission['read_priv'], + 'write': vhost_permission['write_priv'], + 'vhost': vhost_permission['vhost'] + } + else: + yield vhost_permission + + +def as_permission_dict(vhost_permission_list): + return dict([(vhost_permission['vhost'], vhost_permission) for vhost_permission + in normalized_permissions(vhost_permission_list)]) + + +def only(vhost, vhost_permissions): + return {vhost: vhost_permissions.get(vhost, {})} + + +def first(iterable): + return next(iter(iterable)) + + +class RabbitMqUser(object): + def __init__(self, module, username, password, tags, permissions, + node, bulk_permissions=False): + self.module = module + self.username = username + self.password = password or '' + self.node = node + self.tags = list() if not tags else tags.replace(' ', '').split(',') + self.permissions = as_permission_dict(permissions) + self.bulk_permissions = bulk_permissions + + self.existing_tags = None + self.existing_permissions = dict() + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + self._version = self._check_version() + + def _check_version(self): + """Get the version of the RabbitMQ server.""" + version = self._rabbitmq_version_post_3_7(fail_on_error=False) + if not version: + version = self._rabbitmq_version_pre_3_7(fail_on_error=False) + if not version: + self.module.fail_json(msg="Could not determine the version of the RabbitMQ server.") + return version + + def _fail(self, msg, stop_execution=False): + if stop_execution: + self.module.fail_json(msg=msg) + # This is a dummy return to prevent linters from throwing errors. + return None + + def _rabbitmq_version_post_3_7(self, fail_on_error=False): + """Use the JSON formatter to get a machine readable output of the version. + + At this point we do not know which RabbitMQ server version we are dealing with and which + version of `rabbitmqctl` we are using, so we will try to use the JSON formatter and see + what happens. In some versions of + """ + def int_list_to_str(ints): + return ''.join([chr(i) for i in ints]) + + rc, output, err = self._exec(['status', '--formatter', 'json'], check_rc=False) + if rc != 0: + return self._fail(msg="Could not parse the version of the RabbitMQ server, " + "because `rabbitmqctl status` returned no output.", + stop_execution=fail_on_error) + try: + status_json = json.loads(output) + if 'rabbitmq_version' in status_json: + return distutils.version.StrictVersion(status_json['rabbitmq_version']) + for application in status_json.get('running_applications', list()): + if application[0] == 'rabbit': + if isinstance(application[1][0], int): + return distutils.version.StrictVersion(int_list_to_str(application[2])) + else: + return distutils.version.StrictVersion(application[1]) + return self._fail(msg="Could not find RabbitMQ version of `rabbitmqctl status` command.", + stop_execution=fail_on_error) + except ValueError as e: + return self._fail(msg="Could not parse output of `rabbitmqctl status` as JSON: {exc}.".format(exc=repr(e)), + stop_execution=fail_on_error) + + def _rabbitmq_version_pre_3_7(self, fail_on_error=False): + """Get the version of the RabbitMQ Server. + + Before version 3.7.6 the `rabbitmqctl` utility did not support the + `--formatter` flag, so the output has to be parsed using regular expressions. + """ + version_reg_ex = r"{rabbit,\"RabbitMQ\",\"([0-9]+\.[0-9]+\.[0-9]+)\"}" + rc, output, err = self._exec(['status'], check_rc=False) + if rc != 0: + if fail_on_error: + self.module.fail_json(msg="Could not parse the version of the RabbitMQ server, because" + " `rabbitmqctl status` returned no output.") + else: + return None + reg_ex_res = re.search(version_reg_ex, output, re.IGNORECASE) + if not reg_ex_res: + return self._fail(msg="Could not parse the version of the RabbitMQ server from the output of " + "`rabbitmqctl status` command: {output}.".format(output=output), + stop_execution=fail_on_error) + try: + return distutils.version.StrictVersion(reg_ex_res.group(1)) + except ValueError as e: + return self._fail(msg="Could not parse the version of the RabbitMQ server: {exc}.".format(exc=repr(e)), + stop_execution=fail_on_error) + + def _exec(self, args, check_rc=True): + """Execute a command using the `rabbitmqctl` utility. + + By default the _exec call will cause the module to fail, if the error code is non-zero. If the `check_rc` + flag is set to False, then the exit_code, stdout and stderr will be returned to the calling function to + perform whatever error handling it needs. + + :param args: the arguments to pass to the `rabbitmqctl` utility + :param check_rc: when set to True, fail if the utility's exit code is non-zero + :return: the output of the command or all the outputs plus the error code in case of error + """ + cmd = [self._rabbitmqctl, '-q'] + if self.node: + cmd.extend(['-n', self.node]) + rc, out, err = self.module.run_command(cmd + args) + if check_rc and rc != 0: + # check_rc is not passed to the `run_command` method directly to allow for more fine grained checking of + # error messages returned by `rabbitmqctl`. + user_error_msg_regex = r"(Only root or .* .* run rabbitmqctl)" + user_error_msg = re.search(user_error_msg_regex, out) + if user_error_msg: + self.module.fail_json(msg="Wrong user used to run the `rabbitmqctl` utility: {err}" + .format(err=user_error_msg.group(1))) + else: + self.module.fail_json(msg="rabbitmqctl exited with non-zero code: {err}".format(err=err), + rc=rc, stdout=out) + return out if check_rc else (rc, out, err) + + def get(self): + """Retrieves the list of registered users from the node. + + If the user is already present, the node will also be queried for the user's permissions. + If the version of the node is >= 3.7.6 the JSON formatter will be used, otherwise the plaintext will be + parsed. + """ + if self._version >= distutils.version.StrictVersion('3.7.6'): + users = dict([(user_entry['user'], user_entry['tags']) + for user_entry in json.loads(self._exec(['list_users', '--formatter', 'json']))]) + else: + users = self._exec(['list_users']) + + def process_tags(tags): + if not tags: + return list() + return tags.replace('[', '').replace(']', '').replace(' ', '').strip('\t').split(',') + + users_and_tags = [user_entry.split('\t') for user_entry in users.strip().split('\n')] + users = dict([(user, process_tags(tags)) for user, tags in users_and_tags]) + + self.existing_tags = users.get(self.username, list()) + self.existing_permissions = self._get_permissions() if self.username in users else dict() + return self.username in users + + def _get_permissions(self): + """Get permissions of the user from RabbitMQ.""" + if self._version >= distutils.version.StrictVersion('3.7.6'): + permissions = json.loads(self._exec(['list_user_permissions', self.username, '--formatter', 'json'])) + else: + output = self._exec(['list_user_permissions', self.username]).strip().split('\n') + perms_out = [perm.split('\t') for perm in output if perm.strip()] + # Filter out headers from the output of the command in case they are still present + perms_out = [perm for perm in perms_out if perm != ["vhost", "configure", "write", "read"]] + + permissions = list() + for vhost, configure, write, read in perms_out: + permissions.append(dict(vhost=vhost, configure=configure, write=write, read=read)) + + if self.bulk_permissions: + return as_permission_dict(permissions) + else: + return only(first(self.permissions.keys()), as_permission_dict(permissions)) + + def check_password(self): + """Return `True` if the user can authenticate successfully.""" + rc, out, err = self._exec(['authenticate_user', self.username, self.password], check_rc=False) + return rc == 0 + + def add(self): + self._exec(['add_user', self.username, self.password or '']) + if not self.password: + self._exec(['clear_password', self.username]) + + def delete(self): + self._exec(['delete_user', self.username]) + + def change_password(self): + if self.password: + self._exec(['change_password', self.username, self.password]) + else: + self._exec(['clear_password', self.username]) + + def set_tags(self): + self._exec(['set_user_tags', self.username] + self.tags) + + def set_permissions(self): + permissions_to_add = list() + for vhost, permission_dict in self.permissions.items(): + if permission_dict != self.existing_permissions.get(vhost, {}): + permissions_to_add.append(permission_dict) + + permissions_to_clear = list() + for vhost in self.existing_permissions.keys(): + if vhost not in self.permissions: + permissions_to_clear.append(vhost) + + for vhost in permissions_to_clear: + cmd = 'clear_permissions -p {vhost} {username}'.format(username=self.username, vhost=vhost) + self._exec(cmd.split(' ')) + for permissions in permissions_to_add: + cmd = ('set_permissions -p {vhost} {username} {configure} {write} {read}' + .format(username=self.username, **permissions)) + self._exec(cmd.split(' ')) + self.existing_permissions = self._get_permissions() + + def has_tags_modifications(self): + return set(self.tags) != set(self.existing_tags) + + def has_permissions_modifications(self): + return self.existing_permissions != self.permissions + + +def main(): + arg_spec = dict( + user=dict(required=True, aliases=['username', 'name']), + password=dict(default=None, no_log=True), + tags=dict(default=None), + permissions=dict(default=list(), type='list', elements='dict'), + vhost=dict(default='/'), + configure_priv=dict(default='^$'), + write_priv=dict(default='^$'), + read_priv=dict(default='^$'), + force=dict(default='no', type='bool'), + state=dict(default='present', choices=['present', 'absent']), + node=dict(default='rabbit'), + update_password=dict(default='on_create', choices=['on_create', 'always'], no_log=False) + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + username = module.params['user'] + password = module.params['password'] + tags = module.params['tags'] + permissions = module.params['permissions'] + vhost = module.params['vhost'] + configure_priv = module.params['configure_priv'] + write_priv = module.params['write_priv'] + read_priv = module.params['read_priv'] + force = module.params['force'] + state = module.params['state'] + node = module.params['node'] + update_password = module.params['update_password'] + + if permissions: + vhosts = [permission.get('vhost', '/') for permission in permissions] + if any([vhost_count > 1 for vhost_count in count(vhosts).values()]): + module.fail_json(msg="Error parsing vhost permissions: You can't " + "have two permission dicts for the same vhost") + bulk_permissions = True + else: + perm = { + 'vhost': vhost, + 'configure_priv': configure_priv, + 'write_priv': write_priv, + 'read_priv': read_priv + } + permissions.append(perm) + bulk_permissions = False + + for permission in permissions: + if not permission['vhost']: + module.fail_json(msg="Error parsing vhost permissions: You can't" + "have an empty vhost when setting permissions") + + rabbitmq_user = RabbitMqUser(module, username, password, tags, permissions, + node, bulk_permissions=bulk_permissions) + + result = dict(changed=False, user=username, state=state) + if rabbitmq_user.get(): + if state == 'absent': + rabbitmq_user.delete() + result['changed'] = True + else: + if force: + rabbitmq_user.delete() + rabbitmq_user.add() + rabbitmq_user.get() + result['changed'] = True + elif update_password == 'always': + if not rabbitmq_user.check_password(): + rabbitmq_user.change_password() + result['changed'] = True + + if rabbitmq_user.has_tags_modifications(): + rabbitmq_user.set_tags() + result['changed'] = True + + if rabbitmq_user.has_permissions_modifications(): + rabbitmq_user.set_permissions() + result['changed'] = True + elif state == 'present': + rabbitmq_user.add() + rabbitmq_user.set_tags() + rabbitmq_user.set_permissions() + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost.py new file mode 100644 index 00000000..3c1eee52 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost.py @@ -0,0 +1,142 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_vhost +short_description: Manage the state of a virtual host in RabbitMQ +description: + - Manage the state of a virtual host in RabbitMQ +author: Chris Hoffman (@chrishoffman) +options: + name: + description: + - The name of the vhost to manage + type: str + required: true + aliases: [vhost] + node: + description: + - erlang node name of the rabbit we wish to configure + type: str + default: rabbit + tracing: + description: + - Enable/disable tracing for a vhost + type: bool + default: 'no' + aliases: [trace] + state: + description: + - The state of vhost + type: str + default: present + choices: [present, absent] +''' + +EXAMPLES = ''' +# Ensure that the vhost /test exists. +- community.rabbitmq.rabbitmq_vhost: + name: /test + state: present +''' + +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqVhost(object): + def __init__(self, module, name, tracing, node): + self.module = module + self.name = name + self.tracing = tracing + self.node = node + + self._tracing = False + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + def _exec(self, args, run_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and run_in_check_mode): + cmd = [self._rabbitmqctl, '-q', '-n', self.node] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.splitlines() + return list() + + def get(self): + vhosts = self._exec(['list_vhosts', 'name', 'tracing'], True) + + for vhost in vhosts: + if '\t' not in vhost: + continue + + name, tracing = vhost.split('\t') + if name == self.name: + self._tracing = self.module.boolean(tracing) + return True + return False + + def add(self): + return self._exec(['add_vhost', self.name]) + + def delete(self): + return self._exec(['delete_vhost', self.name]) + + def set_tracing(self): + if self.tracing != self._tracing: + if self.tracing: + self._enable_tracing() + else: + self._disable_tracing() + return True + return False + + def _enable_tracing(self): + return self._exec(['trace_on', '-p', self.name]) + + def _disable_tracing(self): + return self._exec(['trace_off', '-p', self.name]) + + +def main(): + arg_spec = dict( + name=dict(required=True, aliases=['vhost']), + tracing=dict(default='off', aliases=['trace'], type='bool'), + state=dict(default='present', choices=['present', 'absent']), + node=dict(default='rabbit'), + ) + + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + name = module.params['name'] + tracing = module.params['tracing'] + state = module.params['state'] + node = module.params['node'] + result = dict(changed=False, name=name, state=state) + rabbitmq_vhost = RabbitMqVhost(module, name, tracing, node) + + if rabbitmq_vhost.get(): + if state == 'absent': + rabbitmq_vhost.delete() + result['changed'] = True + else: + if rabbitmq_vhost.set_tracing(): + result['changed'] = True + elif state == 'present': + rabbitmq_vhost.add() + rabbitmq_vhost.set_tracing() + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost_limits.py b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost_limits.py new file mode 100644 index 00000000..4d54df10 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost_limits.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Hiroyuki Matsuo <h.matsuo.engineer@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: rabbitmq_vhost_limits +author: Hiroyuki Matsuo (@h-matsuo) + +short_description: Manage the state of virtual host limits in RabbitMQ +description: + - This module sets/clears certain limits on a virtual host. + - The configurable limits are I(max_connections) and I(max-queues). + +options: + max_connections: + description: + - Max number of concurrent client connections. + - Negative value means "no limit". + - Ignored when the I(state) is C(absent). + type: int + default: -1 + max_queues: + description: + - Max number of queues. + - Negative value means "no limit". + - Ignored when the I(state) is C(absent). + type: int + default: -1 + node: + description: + - Name of the RabbitMQ Erlang node to manage. + type: str + state: + description: + - Specify whether the limits are to be set or cleared. + - If set to C(absent), the limits of both I(max_connections) and I(max-queues) will be cleared. + type: str + default: present + choices: [present, absent] + vhost: + description: + - Name of the virtual host to manage. + type: str + default: / +''' + +EXAMPLES = ''' +# Limit both of the max number of connections and queues on the vhost '/'. +- community.rabbitmq.rabbitmq_vhost_limits: + vhost: / + max_connections: 64 + max_queues: 256 + state: present + +# Limit the max number of connections on the vhost '/'. +# This task implicitly clears the max number of queues limit using default value: -1. +- community.rabbitmq.rabbitmq_vhost_limits: + vhost: / + max_connections: 64 + state: present + +# Clear the limits on the vhost '/'. +- community.rabbitmq.rabbitmq_vhost_limits: + vhost: / + state: absent +''' + +RETURN = ''' # ''' + + +import json +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqVhostLimits(object): + def __init__(self, module): + self._module = module + self._max_connections = module.params['max_connections'] + self._max_queues = module.params['max_queues'] + self._node = module.params['node'] + self._state = module.params['state'] + self._vhost = module.params['vhost'] + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + def _exec(self, args): + cmd = [self._rabbitmqctl, '-q', '-p', self._vhost] + if self._node is not None: + cmd.extend(['-n', self._node]) + rc, out, err = self._module.run_command(cmd + args, check_rc=True) + return dict(rc=rc, out=out.splitlines(), err=err.splitlines()) + + def list(self): + exec_result = self._exec(['list_vhost_limits']) + vhost_limits = exec_result['out'][0] + max_connections = None + max_queues = None + if vhost_limits: + vhost_limits = json.loads(vhost_limits) + if 'max-connections' in vhost_limits: + max_connections = vhost_limits['max-connections'] + if 'max-queues' in vhost_limits: + max_queues = vhost_limits['max-queues'] + return dict( + max_connections=max_connections, + max_queues=max_queues + ) + + def set(self): + if self._module.check_mode: + return + json_str = '{{"max-connections": {0}, "max-queues": {1}}}'.format(self._max_connections, self._max_queues) + self._exec(['set_vhost_limits', json_str]) + + def clear(self): + if self._module.check_mode: + return + self._exec(['clear_vhost_limits']) + + +def main(): + arg_spec = dict( + max_connections=dict(default=-1, type='int'), + max_queues=dict(default=-1, type='int'), + node=dict(default=None, type='str'), + state=dict(default='present', choices=['present', 'absent'], type='str'), + vhost=dict(default='/', type='str') + ) + + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + max_connections = module.params['max_connections'] + max_queues = module.params['max_queues'] + node = module.params['node'] + state = module.params['state'] + vhost = module.params['vhost'] + + module_result = dict(changed=False) + rabbitmq_vhost_limits = RabbitMqVhostLimits(module) + current_status = rabbitmq_vhost_limits.list() + + if state == 'present': + wanted_status = dict( + max_connections=max_connections, + max_queues=max_queues + ) + else: # state == 'absent' + wanted_status = dict( + max_connections=None, + max_queues=None + ) + + if current_status != wanted_status: + module_result['changed'] = True + if state == 'present': + rabbitmq_vhost_limits.set() + else: # state == 'absent' + rabbitmq_vhost_limits.clear() + + module.exit_json(**module_result) + + +if __name__ == '__main__': + main() |