#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2014, Anders Ingemann # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'version': '1.0'} DOCUMENTATION = ''' --- module: sensu_check short_description: Manage Sensu checks version_added: 2.0 description: - Manage the checks that should be run on a machine by I(Sensu). - Most options do not have a default and will not be added to the check definition unless specified. - All defaults except I(path), I(state), I(backup) and I(metric) are not managed by this module, - they are simply specified for your convenience. options: name: description: - The name of the check - This is the key that is used to determine whether a check exists required: true state: description: - Whether the check should be present or not choices: [ 'present', 'absent' ] required: false default: present path: description: - Path to the json file of the check to be added/removed. - Will be created if it does not exist (unless I(state=absent)). - The parent folders need to exist when I(state=present), otherwise an error will be thrown required: false default: /etc/sensu/conf.d/checks.json backup: description: - Create a backup file (if yes), including the timestamp information so - you can get the original file back if you somehow clobbered it incorrectly. choices: [ 'yes', 'no' ] required: false default: no command: description: - Path to the sensu check to run (not required when I(state=absent)) required: true handlers: description: - List of handlers to notify when the check fails required: false default: [] subscribers: description: - List of subscribers/channels this check should run for - See sensu_subscribers to subscribe a machine to a channel required: false default: [] interval: description: - Check interval in seconds required: false default: null timeout: description: - Timeout for the check required: false default: 10 handle: description: - Whether the check should be handled or not choices: [ 'yes', 'no' ] required: false default: yes subdue_begin: description: - When to disable handling of check failures required: false default: null subdue_end: description: - When to enable handling of check failures required: false default: null dependencies: description: - Other checks this check depends on, if dependencies fail, - handling of this check will be disabled required: false default: [] metric: description: - Whether the check is a metric choices: [ 'yes', 'no' ] required: false default: no standalone: description: - Whether the check should be scheduled by the sensu client or server - This option obviates the need for specifying the I(subscribers) option choices: [ 'yes', 'no' ] required: false default: no publish: description: - Whether the check should be scheduled at all. - You can still issue it via the sensu api choices: [ 'yes', 'no' ] required: false default: yes occurrences: description: - Number of event occurrences before the handler should take action required: false default: 1 refresh: description: - Number of seconds handlers should wait before taking second action required: false default: null aggregate: description: - Classifies the check as an aggregate check, - making it available via the aggregate API choices: [ 'yes', 'no' ] required: false default: no low_flap_threshold: description: - The low threshhold for flap detection required: false default: null high_flap_threshold: description: - The high threshhold for flap detection required: false default: null custom: version_added: "2.1" description: - A hash/dictionary of custom parameters for mixing to the configuration. - You can't rewrite others module parameters using this required: false default: {} source: version_added: "2.1" description: - The check source, used to create a JIT Sensu client for an external resource (e.g. a network switch). required: false default: null requirements: [ ] author: "Anders Ingemann (@andsens)" ''' EXAMPLES = ''' # Fetch metrics about the CPU load every 60 seconds, # the sensu server has a handler called 'relay' which forwards stats to graphite - name: get cpu metrics sensu_check: name: cpu_load command: /etc/sensu/plugins/system/cpu-mpstat-metrics.rb metric: yes handlers: relay subscribers: common interval: 60 # Check whether nginx is running - name: check nginx process sensu_check: name: nginx_running command: /etc/sensu/plugins/processes/check-procs.rb -f /var/run/nginx.pid handlers: default subscribers: nginx interval: 60 # Stop monitoring the disk capacity. # Note that the check will still show up in the sensu dashboard, # to remove it completely you need to issue a DELETE request to the sensu api. - name: check disk sensu_check: name: check_disk_capacity state: absent ''' try: import json except ImportError: try: import simplejson as json except ImportError: # Let snippet from module_utils/basic.py return a proper error in this case pass def sensu_check(module, path, name, state='present', backup=False): changed = False reasons = [] stream = None try: try: stream = open(path, 'r') config = json.load(stream) except IOError: e = get_exception() if e.errno is 2: # File not found, non-fatal if state == 'absent': reasons.append('file did not exist and state is `absent\'') return changed, reasons config = {} else: module.fail_json(msg=str(e)) except ValueError: msg = '{path} contains invalid JSON'.format(path=path) module.fail_json(msg=msg) finally: if stream: stream.close() if 'checks' not in config: if state == 'absent': reasons.append('`checks\' section did not exist and state is `absent\'') return changed, reasons config['checks'] = {} changed = True reasons.append('`checks\' section did not exist') if state == 'absent': if name in config['checks']: del config['checks'][name] changed = True reasons.append('check was present and state is `absent\'') if state == 'present': if name not in config['checks']: check = {} config['checks'][name] = check changed = True reasons.append('check was absent and state is `present\'') else: check = config['checks'][name] simple_opts = ['command', 'handlers', 'subscribers', 'interval', 'timeout', 'handle', 'dependencies', 'standalone', 'publish', 'occurrences', 'refresh', 'aggregate', 'low_flap_threshold', 'high_flap_threshold', 'source', ] for opt in simple_opts: if module.params[opt] is not None: if opt not in check or check[opt] != module.params[opt]: check[opt] = module.params[opt] changed = True reasons.append('`{opt}\' did not exist or was different'.format(opt=opt)) else: if opt in check: del check[opt] changed = True reasons.append('`{opt}\' was removed'.format(opt=opt)) if module.params['custom']: # Convert to json custom_params = module.params['custom'] overwrited_fields = set(custom_params.keys()) & set(simple_opts + ['type','subdue','subdue_begin','subdue_end']) if overwrited_fields: msg = 'You can\'t overwriting standard module parameters via "custom". You are trying overwrite: {opt}'.format(opt=list(overwrited_fields)) module.fail_json(msg=msg) for k,v in custom_params.items(): if k in config['checks'][name]: if not config['checks'][name][k] == v: changed = True reasons.append('`custom param {opt}\' was changed'.format(opt=k)) else: changed = True reasons.append('`custom param {opt}\' was added'.format(opt=k)) check[k] = v simple_opts += custom_params.keys() # Remove obsolete custom params for opt in set(config['checks'][name].keys()) - set(simple_opts + ['type','subdue','subdue_begin','subdue_end']): changed = True reasons.append('`custom param {opt}\' was deleted'.format(opt=opt)) del check[opt] if module.params['metric']: if 'type' not in check or check['type'] != 'metric': check['type'] = 'metric' changed = True reasons.append('`type\' was not defined or not `metric\'') if not module.params['metric'] and 'type' in check: del check['type'] changed = True reasons.append('`type\' was defined') if module.params['subdue_begin'] is not None and module.params['subdue_end'] is not None: subdue = {'begin': module.params['subdue_begin'], 'end': module.params['subdue_end'], } if 'subdue' not in check or check['subdue'] != subdue: check['subdue'] = subdue changed = True reasons.append('`subdue\' did not exist or was different') else: if 'subdue' in check: del check['subdue'] changed = True reasons.append('`subdue\' was removed') if changed and not module.check_mode: if backup: module.backup_local(path) try: try: stream = open(path, 'w') stream.write(json.dumps(config, indent=2) + '\n') except IOError: e = get_exception() module.fail_json(msg=str(e)) finally: if stream: stream.close() return changed, reasons def main(): arg_spec = {'name': {'type': 'str', 'required': True}, 'path': {'type': 'str', 'default': '/etc/sensu/conf.d/checks.json'}, 'state': {'type': 'str', 'default': 'present', 'choices': ['present', 'absent']}, 'backup': {'type': 'bool', 'default': 'no'}, 'command': {'type': 'str'}, 'handlers': {'type': 'list'}, 'subscribers': {'type': 'list'}, 'interval': {'type': 'int'}, 'timeout': {'type': 'int'}, 'handle': {'type': 'bool'}, 'subdue_begin': {'type': 'str'}, 'subdue_end': {'type': 'str'}, 'dependencies': {'type': 'list'}, 'metric': {'type': 'bool', 'default': 'no'}, 'standalone': {'type': 'bool'}, 'publish': {'type': 'bool'}, 'occurrences': {'type': 'int'}, 'refresh': {'type': 'int'}, 'aggregate': {'type': 'bool'}, 'low_flap_threshold': {'type': 'int'}, 'high_flap_threshold': {'type': 'int'}, 'custom': {'type': 'dict'}, 'source': {'type': 'str'}, } required_together = [['subdue_begin', 'subdue_end']] module = AnsibleModule(argument_spec=arg_spec, required_together=required_together, supports_check_mode=True) if module.params['state'] != 'absent' and module.params['command'] is None: module.fail_json(msg="missing required arguments: %s" % ",".join(['command'])) path = module.params['path'] name = module.params['name'] state = module.params['state'] backup = module.params['backup'] changed, reasons = sensu_check(module, path, name, state, backup) module.exit_json(path=path, changed=changed, msg='OK', name=name, reasons=reasons) from ansible.module_utils.basic import * from ansible.module_utils.pycompat24 import get_exception if __name__ == '__main__': main()