1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3# Copyright: Ansible Project
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6from __future__ import absolute_import, division, print_function
7__metaclass__ = type
8
9
10DOCUMENTATION = '''
11---
12module: rax_mon_check
13short_description: Create or delete a Rackspace Cloud Monitoring check for an
14                   existing entity.
15description:
16- Create or delete a Rackspace Cloud Monitoring check associated with an
17  existing rax_mon_entity. A check is a specific test or measurement that is
18  performed, possibly from different monitoring zones, on the systems you
19  monitor. Rackspace monitoring module flow | rax_mon_entity ->
20  *rax_mon_check* -> rax_mon_notification -> rax_mon_notification_plan ->
21  rax_mon_alarm
22options:
23  state:
24    type: str
25    description:
26    - Ensure that a check with this C(label) exists or does not exist.
27    choices: ["present", "absent"]
28    default: present
29  entity_id:
30    type: str
31    description:
32    - ID of the rax_mon_entity to target with this check.
33    required: true
34  label:
35    type: str
36    description:
37    - Defines a label for this check, between 1 and 64 characters long.
38    required: true
39  check_type:
40    type: str
41    description:
42    - The type of check to create. C(remote.) checks may be created on any
43      rax_mon_entity. C(agent.) checks may only be created on rax_mon_entities
44      that have a non-null C(agent_id).
45    - |
46        Choices for this option are:
47        - C(remote.dns)
48        - C(remote.ftp-banner)
49        - C(remote.http)
50        - C(remote.imap-banner)
51        - C(remote.mssql-banner)
52        - C(remote.mysql-banner)
53        - C(remote.ping)
54        - C(remote.pop3-banner)
55        - C(remote.postgresql-banner)
56        - C(remote.smtp-banner)
57        - C(remote.smtp)
58        - C(remote.ssh)
59        - C(remote.tcp)
60        - C(remote.telnet-banner)
61        - C(agent.filesystem)
62        - C(agent.memory)
63        - C(agent.load_average)
64        - C(agent.cpu)
65        - C(agent.disk)
66        - C(agent.network)
67        - C(agent.plugin)
68    required: true
69  monitoring_zones_poll:
70    type: str
71    description:
72    - Comma-separated list of the names of the monitoring zones the check should
73      run from. Available monitoring zones include mzdfw, mzhkg, mziad, mzlon,
74      mzord and mzsyd. Required for remote.* checks; prohibited for agent.* checks.
75  target_hostname:
76    type: str
77    description:
78    - One of `target_hostname` and `target_alias` is required for remote.* checks,
79      but prohibited for agent.* checks. The hostname this check should target.
80      Must be a valid IPv4, IPv6, or FQDN.
81  target_alias:
82    type: str
83    description:
84    - One of `target_alias` and `target_hostname` is required for remote.* checks,
85      but prohibited for agent.* checks. Use the corresponding key in the entity's
86      `ip_addresses` hash to resolve an IP address to target.
87  details:
88    type: dict
89    description:
90    - Additional details specific to the check type. Must be a hash of strings
91      between 1 and 255 characters long, or an array or object containing 0 to
92      256 items.
93  disabled:
94    description:
95    - If "yes", ensure the check is created, but don't actually use it yet.
96    type: bool
97    default: false
98  metadata:
99    type: dict
100    description:
101    - Hash of arbitrary key-value pairs to accompany this check if it fires.
102      Keys and values must be strings between 1 and 255 characters long.
103  period:
104    type: int
105    description:
106    - The number of seconds between each time the check is performed. Must be
107      greater than the minimum period set on your account.
108  timeout:
109    type: int
110    description:
111    - The number of seconds this check will wait when attempting to collect
112      results. Must be less than the period.
113author: Ash Wilson (@smashwilson)
114extends_documentation_fragment:
115- community.general.rackspace.openstack
116
117'''
118
119EXAMPLES = '''
120- name: Create a monitoring check
121  gather_facts: False
122  hosts: local
123  connection: local
124  tasks:
125  - name: Associate a check with an existing entity.
126    community.general.rax_mon_check:
127      credentials: ~/.rax_pub
128      state: present
129      entity_id: "{{ the_entity['entity']['id'] }}"
130      label: the_check
131      check_type: remote.ping
132      monitoring_zones_poll: mziad,mzord,mzdfw
133      details:
134        count: 10
135      meta:
136        hurf: durf
137    register: the_check
138'''
139
140try:
141    import pyrax
142    HAS_PYRAX = True
143except ImportError:
144    HAS_PYRAX = False
145
146from ansible.module_utils.basic import AnsibleModule
147from ansible_collections.community.general.plugins.module_utils.rax import rax_argument_spec, rax_required_together, setup_rax_module
148
149
150def cloud_check(module, state, entity_id, label, check_type,
151                monitoring_zones_poll, target_hostname, target_alias, details,
152                disabled, metadata, period, timeout):
153
154    # Coerce attributes.
155
156    if monitoring_zones_poll and not isinstance(monitoring_zones_poll, list):
157        monitoring_zones_poll = [monitoring_zones_poll]
158
159    if period:
160        period = int(period)
161
162    if timeout:
163        timeout = int(timeout)
164
165    changed = False
166    check = None
167
168    cm = pyrax.cloud_monitoring
169    if not cm:
170        module.fail_json(msg='Failed to instantiate client. This typically '
171                             'indicates an invalid region or an incorrectly '
172                             'capitalized region name.')
173
174    entity = cm.get_entity(entity_id)
175    if not entity:
176        module.fail_json(msg='Failed to instantiate entity. "%s" may not be'
177                             ' a valid entity id.' % entity_id)
178
179    existing = [e for e in entity.list_checks() if e.label == label]
180
181    if existing:
182        check = existing[0]
183
184    if state == 'present':
185        if len(existing) > 1:
186            module.fail_json(msg='%s existing checks have a label of %s.' %
187                                 (len(existing), label))
188
189        should_delete = False
190        should_create = False
191        should_update = False
192
193        if check:
194            # Details may include keys set to default values that are not
195            # included in the initial creation.
196            #
197            # Only force a recreation of the check if one of the *specified*
198            # keys is missing or has a different value.
199            if details:
200                for (key, value) in details.items():
201                    if key not in check.details:
202                        should_delete = should_create = True
203                    elif value != check.details[key]:
204                        should_delete = should_create = True
205
206            should_update = label != check.label or \
207                (target_hostname and target_hostname != check.target_hostname) or \
208                (target_alias and target_alias != check.target_alias) or \
209                (disabled != check.disabled) or \
210                (metadata and metadata != check.metadata) or \
211                (period and period != check.period) or \
212                (timeout and timeout != check.timeout) or \
213                (monitoring_zones_poll and monitoring_zones_poll != check.monitoring_zones_poll)
214
215            if should_update and not should_delete:
216                check.update(label=label,
217                             disabled=disabled,
218                             metadata=metadata,
219                             monitoring_zones_poll=monitoring_zones_poll,
220                             timeout=timeout,
221                             period=period,
222                             target_alias=target_alias,
223                             target_hostname=target_hostname)
224                changed = True
225        else:
226            # The check doesn't exist yet.
227            should_create = True
228
229        if should_delete:
230            check.delete()
231
232        if should_create:
233            check = cm.create_check(entity,
234                                    label=label,
235                                    check_type=check_type,
236                                    target_hostname=target_hostname,
237                                    target_alias=target_alias,
238                                    monitoring_zones_poll=monitoring_zones_poll,
239                                    details=details,
240                                    disabled=disabled,
241                                    metadata=metadata,
242                                    period=period,
243                                    timeout=timeout)
244            changed = True
245    elif state == 'absent':
246        if check:
247            check.delete()
248            changed = True
249    else:
250        module.fail_json(msg='state must be either present or absent.')
251
252    if check:
253        check_dict = {
254            "id": check.id,
255            "label": check.label,
256            "type": check.type,
257            "target_hostname": check.target_hostname,
258            "target_alias": check.target_alias,
259            "monitoring_zones_poll": check.monitoring_zones_poll,
260            "details": check.details,
261            "disabled": check.disabled,
262            "metadata": check.metadata,
263            "period": check.period,
264            "timeout": check.timeout
265        }
266        module.exit_json(changed=changed, check=check_dict)
267    else:
268        module.exit_json(changed=changed)
269
270
271def main():
272    argument_spec = rax_argument_spec()
273    argument_spec.update(
274        dict(
275            entity_id=dict(required=True),
276            label=dict(required=True),
277            check_type=dict(required=True),
278            monitoring_zones_poll=dict(),
279            target_hostname=dict(),
280            target_alias=dict(),
281            details=dict(type='dict', default={}),
282            disabled=dict(type='bool', default=False),
283            metadata=dict(type='dict', default={}),
284            period=dict(type='int'),
285            timeout=dict(type='int'),
286            state=dict(default='present', choices=['present', 'absent'])
287        )
288    )
289
290    module = AnsibleModule(
291        argument_spec=argument_spec,
292        required_together=rax_required_together()
293    )
294
295    if not HAS_PYRAX:
296        module.fail_json(msg='pyrax is required for this module')
297
298    entity_id = module.params.get('entity_id')
299    label = module.params.get('label')
300    check_type = module.params.get('check_type')
301    monitoring_zones_poll = module.params.get('monitoring_zones_poll')
302    target_hostname = module.params.get('target_hostname')
303    target_alias = module.params.get('target_alias')
304    details = module.params.get('details')
305    disabled = module.boolean(module.params.get('disabled'))
306    metadata = module.params.get('metadata')
307    period = module.params.get('period')
308    timeout = module.params.get('timeout')
309
310    state = module.params.get('state')
311
312    setup_rax_module(module, pyrax)
313
314    cloud_check(module, state, entity_id, label, check_type,
315                monitoring_zones_poll, target_hostname, target_alias, details,
316                disabled, metadata, period, timeout)
317
318
319if __name__ == '__main__':
320    main()
321