2015-01-16 15:59:17 +01:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# (c) 2014, Anders Ingemann <aim@secoya.dk>
|
|
|
|
#
|
|
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
|
|
|
|
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:
|
2015-09-17 12:12:54 +02:00
|
|
|
description:
|
|
|
|
- Whether the check should be present or not
|
2015-01-16 15:59:17 +01:00
|
|
|
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:
|
2015-09-17 12:12:54 +02:00
|
|
|
description:
|
|
|
|
- Whether the check is a metric
|
2015-01-16 15:59:17 +01:00
|
|
|
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 low threshhold for flap detection
|
|
|
|
required: false
|
|
|
|
default: null
|
2016-01-21 11:08:25 +01:00
|
|
|
custom:
|
2016-02-02 05:56:58 +01:00
|
|
|
version_added: "2.1"
|
2016-01-21 11:08:25 +01:00
|
|
|
description:
|
2016-01-28 13:42:09 +01:00
|
|
|
- A hash/dictionary of custom parameters for mixing to the configuration.
|
|
|
|
- You can't rewrite others module parameters using this
|
2016-01-21 11:08:25 +01:00
|
|
|
required: false
|
2016-01-28 13:42:09 +01:00
|
|
|
default: {}
|
2016-02-18 03:04:39 +01:00
|
|
|
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
|
2015-01-16 15:59:17 +01:00
|
|
|
requirements: [ ]
|
2015-09-17 12:01:48 +02:00
|
|
|
author: "Anders Ingemann (@andsens)"
|
2015-01-16 15:59:17 +01:00
|
|
|
'''
|
|
|
|
|
|
|
|
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
|
2015-09-17 12:12:54 +02:00
|
|
|
sensu_check: name=check_disk_capacity state=absent
|
2015-01-16 15:59:17 +01:00
|
|
|
'''
|
|
|
|
|
2016-01-11 21:47:21 +01:00
|
|
|
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
|
|
|
|
|
2016-01-28 13:44:57 +01:00
|
|
|
|
2015-01-16 15:59:17 +01:00
|
|
|
def sensu_check(module, path, name, state='present', backup=False):
|
|
|
|
changed = False
|
|
|
|
reasons = []
|
|
|
|
|
2015-09-15 17:40:01 +02:00
|
|
|
stream = None
|
2015-01-16 15:59:17 +01:00
|
|
|
try:
|
2015-06-19 18:13:43 +02:00
|
|
|
try:
|
|
|
|
stream = open(path, 'r')
|
2015-09-15 17:43:26 +02:00
|
|
|
config = json.load(stream)
|
2015-06-19 18:13:43 +02:00
|
|
|
except IOError, e:
|
|
|
|
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)
|
2015-06-19 18:05:50 +02:00
|
|
|
finally:
|
|
|
|
if stream:
|
|
|
|
stream.close()
|
2015-01-16 15:59:17 +01:00
|
|
|
|
|
|
|
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',
|
2016-02-18 03:04:39 +01:00
|
|
|
'source',
|
2015-01-16 15:59:17 +01:00
|
|
|
]
|
|
|
|
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))
|
|
|
|
|
2016-01-21 11:08:25 +01:00
|
|
|
if module.params['custom']:
|
|
|
|
# Convert to json
|
2016-01-28 13:42:09 +01:00
|
|
|
custom_params = module.params['custom']
|
|
|
|
overwrited_fields = set(custom_params.keys()) & set(simple_opts + ['type','subdue','subdue_begin','subdue_end'])
|
2016-01-21 11:08:25 +01:00
|
|
|
if overwrited_fields:
|
2016-01-28 13:42:09 +01:00
|
|
|
msg = 'You can\'t overwriting standard module parameters via "custom". You are trying overwrite: {opt}'.format(opt=list(overwrited_fields))
|
2016-01-21 11:08:25 +01:00
|
|
|
module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
for k,v in custom_params.items():
|
2016-01-28 13:42:09 +01:00
|
|
|
if k in config['checks'][name]:
|
2016-01-21 11:08:25 +01:00
|
|
|
if not config['checks'][name][k] == v:
|
|
|
|
changed = True
|
2016-01-28 13:42:09 +01:00
|
|
|
reasons.append('`custom param {opt}\' was changed'.format(opt=k))
|
2016-01-21 11:08:25 +01:00
|
|
|
else:
|
|
|
|
changed = True
|
2016-01-28 13:42:09 +01:00
|
|
|
reasons.append('`custom param {opt}\' was added'.format(opt=k))
|
2016-01-21 11:08:25 +01:00
|
|
|
check[k] = v
|
|
|
|
simple_opts += custom_params.keys()
|
|
|
|
|
|
|
|
# Remove obsolete custom params
|
2016-01-28 13:42:09 +01:00
|
|
|
for opt in set(config['checks'][name].keys()) - set(simple_opts + ['type','subdue','subdue_begin','subdue_end']):
|
2016-01-21 11:08:25 +01:00
|
|
|
changed = True
|
|
|
|
reasons.append('`custom param {opt}\' was deleted'.format(opt=opt))
|
|
|
|
del check[opt]
|
|
|
|
|
2015-01-16 15:59:17 +01:00
|
|
|
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:
|
2015-06-19 18:13:43 +02:00
|
|
|
try:
|
|
|
|
stream = open(path, 'w')
|
|
|
|
stream.write(json.dumps(config, indent=2) + '\n')
|
|
|
|
except IOError, e:
|
|
|
|
module.fail_json(msg=str(e))
|
2015-06-19 18:05:50 +02:00
|
|
|
finally:
|
|
|
|
if stream:
|
|
|
|
stream.close()
|
2015-01-16 15:59:17 +01:00
|
|
|
|
|
|
|
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'},
|
2016-01-28 13:42:09 +01:00
|
|
|
'custom': {'type': 'dict'},
|
2016-02-18 03:04:39 +01:00
|
|
|
'source': {'type': 'str'},
|
2015-01-16 15:59:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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 *
|
|
|
|
main()
|