1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2015, Manuel Sousa <manuel.sousa@gmail.com>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10ANSIBLE_METADATA = {
11    'metadata_version': '1.1',
12    'status': ['preview'],
13    'supported_by': 'community'
14}
15
16DOCUMENTATION = '''
17---
18module: rabbitmq_binding
19author: Manuel Sousa (@manuel-sousa)
20version_added: "2.0"
21
22short_description: Manage rabbitMQ bindings
23description:
24  - This module uses rabbitMQ REST APIs to create / delete bindings.
25requirements: [ "requests >= 1.0.0" ]
26options:
27    state:
28      description:
29      - Whether the bindings should be present or absent.
30      choices: [ "present", "absent" ]
31      default: present
32    name:
33      description:
34      - source exchange to create binding on.
35      required: true
36      aliases: [ "src", "source" ]
37    destination:
38      description:
39      - destination exchange or queue for the binding.
40      required: true
41      aliases: [ "dst", "dest" ]
42    destination_type:
43      description:
44      - Either queue or exchange.
45      required: true
46      choices: [ "queue", "exchange" ]
47      aliases: [ "type", "dest_type" ]
48    routing_key:
49      description:
50      - routing key for the binding.
51      default: "#"
52    arguments:
53      description:
54      - extra arguments for exchange. If defined this argument is a key/value dictionary
55      required: false
56      default: {}
57extends_documentation_fragment:
58    - rabbitmq
59'''
60
61EXAMPLES = '''
62# Bind myQueue to directExchange with routing key info
63- rabbitmq_binding:
64    name: directExchange
65    destination: myQueue
66    type: queue
67    routing_key: info
68
69# Bind directExchange to topicExchange with routing key *.info
70- rabbitmq_binding:
71    name: topicExchange
72    destination: topicExchange
73    type: exchange
74    routing_key: '*.info'
75'''
76
77import json
78import traceback
79
80REQUESTS_IMP_ERR = None
81try:
82    import requests
83    HAS_REQUESTS = True
84except ImportError:
85    REQUESTS_IMP_ERR = traceback.format_exc()
86    HAS_REQUESTS = False
87
88from ansible.module_utils.six.moves.urllib import parse as urllib_parse
89from ansible.module_utils.basic import AnsibleModule, missing_required_lib
90from ansible.module_utils.rabbitmq import rabbitmq_argument_spec
91
92
93class RabbitMqBinding(object):
94    def __init__(self, module):
95        """
96        :param module:
97        """
98        self.module = module
99        self.name = self.module.params['name']
100        self.login_user = self.module.params['login_user']
101        self.login_password = self.module.params['login_password']
102        self.login_host = self.module.params['login_host']
103        self.login_port = self.module.params['login_port']
104        self.login_protocol = self.module.params['login_protocol']
105        self.vhost = self.module.params['vhost']
106        self.destination = self.module.params['destination']
107        self.destination_type = 'q' if self.module.params['destination_type'] == 'queue' else 'e'
108        self.routing_key = self.module.params['routing_key']
109        self.arguments = self.module.params['arguments']
110        self.verify = self.module.params['ca_cert']
111        self.cert = self.module.params['client_cert']
112        self.key = self.module.params['client_key']
113        self.props = urllib_parse.quote(self.routing_key) if self.routing_key != '' else '~'
114        self.base_url = '{0}://{1}:{2}/api/bindings'.format(self.login_protocol,
115                                                            self.login_host,
116                                                            self.login_port)
117        self.url = '{0}/{1}/e/{2}/{3}/{4}/{5}'.format(self.base_url,
118                                                      urllib_parse.quote(self.vhost, safe=''),
119                                                      urllib_parse.quote(self.name, safe=''),
120                                                      self.destination_type,
121                                                      urllib_parse.quote(self.destination, safe=''),
122                                                      self.props)
123        self.result = {
124            'changed': False,
125            'name': self.module.params['name'],
126        }
127        self.authentication = (
128            self.login_user,
129            self.login_password
130        )
131        self.request = requests
132        self.http_check_states = {
133            200: True,
134            404: False,
135        }
136        self.http_actionable_states = {
137            201: True,
138            204: True,
139        }
140        self.api_result = self.request.get(self.url, auth=self.authentication, verify=self.verify, cert=(self.cert, self.key))
141
142    def run(self):
143        """
144        :return:
145        """
146        self.check_presence()
147        self.check_mode()
148        self.action_mode()
149
150    def check_presence(self):
151        """
152        :return:
153        """
154        if self.check_should_throw_fail():
155            self.fail()
156
157    def change_required(self):
158        """
159        :return:
160        """
161        if self.module.params['state'] == 'present':
162            if not self.is_present():
163                return True
164        elif self.module.params['state'] == 'absent':
165            if self.is_present():
166                return True
167        return False
168
169    def is_present(self):
170        """
171        :return:
172        """
173        return self.http_check_states.get(self.api_result.status_code, False)
174
175    def check_mode(self):
176        """
177        :return:
178        """
179        if self.module.check_mode:
180            result = self.result
181            result['changed'] = self.change_required()
182            result['details'] = self.api_result.json() if self.is_present() else self.api_result.text
183            result['arguments'] = self.module.params['arguments']
184            self.module.exit_json(**result)
185
186    def check_reply_is_correct(self):
187        """
188        :return:
189        """
190        if self.api_result.status_code in self.http_check_states:
191            return True
192        return False
193
194    def check_should_throw_fail(self):
195        """
196        :return:
197        """
198        if not self.is_present():
199            if not self.check_reply_is_correct():
200                return True
201        return False
202
203    def action_mode(self):
204        """
205        :return:
206        """
207        result = self.result
208        if self.change_required():
209            if self.module.params['state'] == 'present':
210                self.create()
211            if self.module.params['state'] == 'absent':
212                self.remove()
213            if self.action_should_throw_fail():
214                self.fail()
215            result['changed'] = True
216            result['destination'] = self.module.params['destination']
217            self.module.exit_json(**result)
218        else:
219            result['changed'] = False
220            self.module.exit_json(**result)
221
222    def action_reply_is_correct(self):
223        """
224        :return:
225        """
226        if self.api_result.status_code in self.http_actionable_states:
227            return True
228        return False
229
230    def action_should_throw_fail(self):
231        """
232        :return:
233        """
234        if not self.action_reply_is_correct():
235            return True
236        return False
237
238    def create(self):
239        """
240        :return:
241        """
242        self.url = '{0}/{1}/e/{2}/{3}/{4}'.format(self.base_url,
243                                                  urllib_parse.quote(self.vhost, safe=''),
244                                                  urllib_parse.quote(self.name, safe=''),
245                                                  self.destination_type,
246                                                  urllib_parse.quote(self.destination, safe=''))
247        self.api_result = self.request.post(self.url,
248                                            auth=self.authentication,
249                                            verify=self.verify,
250                                            cert=(self.cert, self.key),
251                                            headers={"content-type": "application/json"},
252                                            data=json.dumps({
253                                                'routing_key': self.routing_key,
254                                                'arguments': self.arguments
255                                            }))
256
257    def remove(self):
258        """
259        :return:
260        """
261        self.api_result = self.request.delete(self.url, auth=self.authentication, verify=self.verify, cert=(self.cert, self.key))
262
263    def fail(self):
264        """
265        :return:
266        """
267        self.module.fail_json(
268            msg="Unexpected reply from API",
269            status=self.api_result.status_code,
270            details=self.api_result.text
271        )
272
273
274def main():
275
276    argument_spec = rabbitmq_argument_spec()
277    argument_spec.update(
278        dict(
279            state=dict(default='present', choices=['present', 'absent'], type='str'),
280            name=dict(required=True, aliases=["src", "source"], type='str'),
281            destination=dict(required=True, aliases=["dst", "dest"], type='str'),
282            destination_type=dict(required=True, aliases=["type", "dest_type"], choices=["queue", "exchange"],
283                                  type='str'),
284            routing_key=dict(default='#', type='str'),
285            arguments=dict(default=dict(), type='dict')
286        )
287    )
288    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
289
290    if not HAS_REQUESTS:
291        module.fail_json(msg=missing_required_lib("requests"), exception=REQUESTS_IMP_ERR)
292
293    RabbitMqBinding(module).run()
294
295
296if __name__ == '__main__':
297    main()
298