mirror of https://github.com/ansible/ansible.git
Add logicmonitor and logicmonitor_facts modules
parent
0380006e00
commit
9d0eecc873
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,633 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""LogicMonitor Ansible module for managing Collectors, Hosts and Hostgroups
|
||||
Copyright (C) 2015 LogicMonitor
|
||||
|
||||
This program 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.
|
||||
|
||||
This program 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 this program; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA"""
|
||||
|
||||
|
||||
import socket
|
||||
import sys
|
||||
import types
|
||||
import urllib
|
||||
|
||||
HAS_LIB_JSON = True
|
||||
try:
|
||||
import json
|
||||
# Detect the python-json library which is incompatible
|
||||
# Look for simplejson if that's the case
|
||||
try:
|
||||
if (
|
||||
not isinstance(json.loads, types.FunctionType) or
|
||||
not isinstance(json.dumps, types.FunctionType)
|
||||
):
|
||||
raise ImportError
|
||||
except AttributeError:
|
||||
raise ImportError
|
||||
except ImportError:
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
print(
|
||||
'\n{"msg": "Error: ansible requires the stdlib json or ' +
|
||||
'simplejson module, neither was found!", "failed": true}'
|
||||
)
|
||||
HAS_LIB_JSON = False
|
||||
except SyntaxError:
|
||||
print(
|
||||
'\n{"msg": "SyntaxError: probably due to installed simplejson ' +
|
||||
'being for a different python version", "failed": true}'
|
||||
)
|
||||
HAS_LIB_JSON = False
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: logicmonitor_facts
|
||||
short_description: Collect facts about LogicMonitor objects
|
||||
description:
|
||||
- LogicMonitor is a hosted, full-stack, infrastructure monitoring platform.
|
||||
- This module collects facts about hosts abd host groups within your LogicMonitor account.
|
||||
version_added: "2.2"
|
||||
author: Ethan Culler-Mayeno, Jeff Wozniak
|
||||
notes:
|
||||
- You must have an existing LogicMonitor account for this module to function.
|
||||
requirements: ["An existing LogicMonitor account", "Linux"]
|
||||
options:
|
||||
target:
|
||||
description:
|
||||
- The LogicMonitor object you wish to manage.
|
||||
required: true
|
||||
default: null
|
||||
choices: ['host', 'hostgroup']
|
||||
company:
|
||||
description:
|
||||
- The LogicMonitor account company name. If you would log in to your account at "superheroes.logicmonitor.com" you would use "superheroes"
|
||||
required: true
|
||||
default: null
|
||||
user:
|
||||
description:
|
||||
- A LogicMonitor user name. The module will authenticate and perform actions on behalf of this user
|
||||
required: true
|
||||
default: null
|
||||
password:
|
||||
description:
|
||||
- The password for the chosen LogicMonitor User
|
||||
- If an md5 hash is used, the digest flag must be set to true
|
||||
required: true
|
||||
default: null
|
||||
collector:
|
||||
description:
|
||||
- The fully qualified domain name of a collector in your LogicMonitor account.
|
||||
- This is optional for querying a LogicMonitor host when a displayname is specified
|
||||
- This is required for querying a LogicMonitor host when a displayname is not specified
|
||||
required: false
|
||||
default: null
|
||||
hostname:
|
||||
description:
|
||||
- The hostname of a host in your LogicMonitor account, or the desired hostname of a device to add into monitoring.
|
||||
- Required for managing hosts (target=host)
|
||||
required: false
|
||||
default: 'hostname -f'
|
||||
displayname:
|
||||
description:
|
||||
- The display name of a host in your LogicMonitor account or the desired display name of a device to add into monitoring.
|
||||
required: false
|
||||
default: 'hostname -f'
|
||||
fullpath:
|
||||
description:
|
||||
- The fullpath of the hostgroup object you would like to manage
|
||||
- Recommend running on a single ansible host
|
||||
- Required for management of LogicMonitor host groups (target=hostgroup)
|
||||
required: false
|
||||
default: null
|
||||
...
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
#example of querying a list of hosts
|
||||
```
|
||||
---
|
||||
- hosts: hosts
|
||||
user: root
|
||||
vars:
|
||||
company: 'yourcompany'
|
||||
user: 'Luigi'
|
||||
password: 'ImaLuigi,number1!'
|
||||
tasks:
|
||||
- name: query a list of hosts
|
||||
# All tasks should use local_action
|
||||
local_action:
|
||||
logicmonitor_facts:
|
||||
target: host
|
||||
company: '{{ company }}'
|
||||
user: '{{ user }}'
|
||||
password: '{{ password }}'
|
||||
```
|
||||
|
||||
#example of querying a hostgroup
|
||||
```
|
||||
---
|
||||
- hosts: somemachine.superheroes.com
|
||||
user: root
|
||||
vars:
|
||||
company: 'yourcompany'
|
||||
user: 'mario'
|
||||
password: 'itsame.Mario!'
|
||||
tasks:
|
||||
- name: query a host group
|
||||
# All tasks should use local_action
|
||||
local_action:
|
||||
logicmonitor_facts:
|
||||
target: hostgroup
|
||||
fullpath: '/servers/production'
|
||||
company: '{{ company }}'
|
||||
user: '{{ user }}'
|
||||
password: '{{ password }}'
|
||||
```
|
||||
'''
|
||||
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
ansible_facts:
|
||||
description: LogicMonitor properties set for the specified object
|
||||
returned: success
|
||||
type: list of dicts containing name/value pairs
|
||||
example: >
|
||||
{
|
||||
"name": "dc",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"value": "prod"
|
||||
},
|
||||
{
|
||||
"name": "system.categories",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "snmp.community",
|
||||
"value": "********"
|
||||
}
|
||||
...
|
||||
'''
|
||||
|
||||
|
||||
class LogicMonitor(object):
|
||||
|
||||
def __init__(self, module, **params):
|
||||
self.__version__ = "1.0-python"
|
||||
self.module = module
|
||||
self.module.debug("Instantiating LogicMonitor object")
|
||||
|
||||
self.check_mode = False
|
||||
self.company = params["company"]
|
||||
self.user = params["user"]
|
||||
self.password = params["password"]
|
||||
self.fqdn = socket.getfqdn()
|
||||
self.lm_url = "logicmonitor.com/santaba"
|
||||
self.urlopen = open_url # use the ansible provided open_url
|
||||
self.__version__ = self.__version__ + "-ansible-module"
|
||||
|
||||
def rpc(self, action, params):
|
||||
"""Make a call to the LogicMonitor RPC library
|
||||
and return the response"""
|
||||
self.module.debug("Running LogicMonitor.rpc")
|
||||
|
||||
param_str = urllib.urlencode(params)
|
||||
creds = urllib.urlencode(
|
||||
{"c": self.company,
|
||||
"u": self.user,
|
||||
"p": self.password})
|
||||
|
||||
if param_str:
|
||||
param_str = param_str + "&"
|
||||
|
||||
param_str = param_str + creds
|
||||
|
||||
try:
|
||||
url = ("https://" + self.company + "." + self.lm_url +
|
||||
"/rpc/" + action + "?" + param_str)
|
||||
|
||||
# Set custom LogicMonitor header with version
|
||||
headers = {"X-LM-User-Agent": self.__version__}
|
||||
|
||||
# Set headers
|
||||
f = self.urlopen(url, headers=headers)
|
||||
|
||||
raw = f.read()
|
||||
resp = json.loads(raw)
|
||||
if resp["status"] == 403:
|
||||
self.module.debug("Authentication failed.")
|
||||
self.fail(msg="Error: " + resp["errmsg"])
|
||||
else:
|
||||
return raw
|
||||
except IOError:
|
||||
self.fail(msg="Error: Unknown exception making RPC call")
|
||||
|
||||
def get_collectors(self):
|
||||
"""Returns a JSON object containing a list of
|
||||
LogicMonitor collectors"""
|
||||
self.module.debug("Running LogicMonitor.get_collectors...")
|
||||
|
||||
self.module.debug("Making RPC call to 'getAgents'")
|
||||
resp = self.rpc("getAgents", {})
|
||||
resp_json = json.loads(resp)
|
||||
|
||||
if resp_json["status"] is 200:
|
||||
self.module.debug("RPC call succeeded")
|
||||
return resp_json["data"]
|
||||
else:
|
||||
self.fail(msg=resp)
|
||||
|
||||
def get_host_by_hostname(self, hostname, collector):
|
||||
"""Returns a host object for the host matching the
|
||||
specified hostname"""
|
||||
self.module.debug("Running LogicMonitor.get_host_by_hostname...")
|
||||
|
||||
self.module.debug("Looking for hostname " + hostname)
|
||||
self.module.debug("Making RPC call to 'getHosts'")
|
||||
hostlist_json = json.loads(self.rpc("getHosts", {"hostGroupId": 1}))
|
||||
|
||||
if collector:
|
||||
if hostlist_json["status"] == 200:
|
||||
self.module.debug("RPC call succeeded")
|
||||
|
||||
hosts = hostlist_json["data"]["hosts"]
|
||||
|
||||
self.module.debug(
|
||||
"Looking for host matching: hostname " + hostname +
|
||||
" and collector " + str(collector["id"]))
|
||||
|
||||
for host in hosts:
|
||||
if (host["hostName"] == hostname and
|
||||
host["agentId"] == collector["id"]):
|
||||
|
||||
self.module.debug("Host match found")
|
||||
return host
|
||||
self.module.debug("No host match found")
|
||||
return None
|
||||
else:
|
||||
self.module.debug("RPC call failed")
|
||||
self.module.debug(hostlist_json)
|
||||
else:
|
||||
self.module.debug("No collector specified")
|
||||
return None
|
||||
|
||||
def get_host_by_displayname(self, displayname):
|
||||
"""Returns a host object for the host matching the
|
||||
specified display name"""
|
||||
self.module.debug("Running LogicMonitor.get_host_by_displayname...")
|
||||
|
||||
self.module.debug("Looking for displayname " + displayname)
|
||||
self.module.debug("Making RPC call to 'getHost'")
|
||||
host_json = (json.loads(self.rpc("getHost",
|
||||
{"displayName": displayname})))
|
||||
|
||||
if host_json["status"] == 200:
|
||||
self.module.debug("RPC call succeeded")
|
||||
return host_json["data"]
|
||||
else:
|
||||
self.module.debug("RPC call failed")
|
||||
self.module.debug(host_json)
|
||||
return None
|
||||
|
||||
def get_collector_by_description(self, description):
|
||||
"""Returns a JSON collector object for the collector
|
||||
matching the specified FQDN (description)"""
|
||||
self.module.debug(
|
||||
"Running LogicMonitor.get_collector_by_description..."
|
||||
)
|
||||
|
||||
collector_list = self.get_collectors()
|
||||
if collector_list is not None:
|
||||
self.module.debug("Looking for collector with description " +
|
||||
description)
|
||||
for collector in collector_list:
|
||||
if collector["description"] == description:
|
||||
self.module.debug("Collector match found")
|
||||
return collector
|
||||
self.module.debug("No collector match found")
|
||||
return None
|
||||
|
||||
def get_group(self, fullpath):
|
||||
"""Returns a JSON group object for the group matching the
|
||||
specified path"""
|
||||
self.module.debug("Running LogicMonitor.get_group...")
|
||||
|
||||
self.module.debug("Making RPC call to getHostGroups")
|
||||
resp = json.loads(self.rpc("getHostGroups", {}))
|
||||
|
||||
if resp["status"] == 200:
|
||||
self.module.debug("RPC called succeeded")
|
||||
groups = resp["data"]
|
||||
|
||||
self.module.debug("Looking for group matching " + fullpath)
|
||||
for group in groups:
|
||||
if group["fullPath"] == fullpath.lstrip('/'):
|
||||
self.module.debug("Group match found")
|
||||
return group
|
||||
|
||||
self.module.debug("No group match found")
|
||||
return None
|
||||
else:
|
||||
self.module.debug("RPC call failed")
|
||||
self.module.debug(resp)
|
||||
|
||||
return None
|
||||
|
||||
def create_group(self, fullpath):
|
||||
"""Recursively create a path of host groups.
|
||||
Returns the id of the newly created hostgroup"""
|
||||
self.module.debug("Running LogicMonitor.create_group...")
|
||||
|
||||
res = self.get_group(fullpath)
|
||||
if res:
|
||||
self.module.debug("Group " + fullpath + " exists.")
|
||||
return res["id"]
|
||||
|
||||
if fullpath == "/":
|
||||
self.module.debug("Specified group is root. Doing nothing.")
|
||||
return 1
|
||||
else:
|
||||
self.module.debug("Creating group named " + fullpath)
|
||||
self.module.debug("System changed")
|
||||
self.change = True
|
||||
|
||||
if self.check_mode:
|
||||
self.exit(changed=True)
|
||||
|
||||
parentpath, name = fullpath.rsplit('/', 1)
|
||||
parentgroup = self.get_group(parentpath)
|
||||
|
||||
parentid = 1
|
||||
|
||||
if parentpath == "":
|
||||
parentid = 1
|
||||
elif parentgroup:
|
||||
parentid = parentgroup["id"]
|
||||
else:
|
||||
parentid = self.create_group(parentpath)
|
||||
|
||||
h = None
|
||||
|
||||
# Determine if we're creating a group from host or hostgroup class
|
||||
if hasattr(self, '_build_host_group_hash'):
|
||||
h = self._build_host_group_hash(
|
||||
fullpath,
|
||||
self.description,
|
||||
self.properties,
|
||||
self.alertenable)
|
||||
h["name"] = name
|
||||
h["parentId"] = parentid
|
||||
else:
|
||||
h = {"name": name,
|
||||
"parentId": parentid,
|
||||
"alertEnable": True,
|
||||
"description": ""}
|
||||
|
||||
self.module.debug("Making RPC call to 'addHostGroup'")
|
||||
resp = json.loads(
|
||||
self.rpc("addHostGroup", h))
|
||||
|
||||
if resp["status"] == 200:
|
||||
self.module.debug("RPC call succeeded")
|
||||
return resp["data"]["id"]
|
||||
elif resp["errmsg"] == "The record already exists":
|
||||
self.module.debug("The hostgroup already exists")
|
||||
group = self.get_group(fullpath)
|
||||
return group["id"]
|
||||
else:
|
||||
self.module.debug("RPC call failed")
|
||||
self.fail(
|
||||
msg="Error: unable to create new hostgroup \"" + name +
|
||||
"\".\n" + resp["errmsg"])
|
||||
|
||||
def fail(self, msg):
|
||||
self.module.fail_json(msg=msg, changed=self.change)
|
||||
|
||||
def exit(self, changed):
|
||||
self.module.debug("Changed: " + changed)
|
||||
self.module.exit_json(changed=changed)
|
||||
|
||||
def output_info(self, info):
|
||||
self.module.debug("Registering properties as Ansible facts")
|
||||
self.module.exit_json(changed=False, ansible_facts=info)
|
||||
|
||||
|
||||
class Host(LogicMonitor):
|
||||
|
||||
def __init__(self, params, module=None):
|
||||
"""Initializor for the LogicMonitor host object"""
|
||||
self.change = False
|
||||
self.params = params
|
||||
self.collector = None
|
||||
|
||||
LogicMonitor.__init__(self, module, **self.params)
|
||||
self.module.debug("Instantiating Host object")
|
||||
|
||||
if self.params["hostname"]:
|
||||
self.module.debug("Hostname is " + self.params["hostname"])
|
||||
self.hostname = self.params['hostname']
|
||||
else:
|
||||
self.module.debug("No hostname specified. Using " + self.fqdn)
|
||||
self.hostname = self.fqdn
|
||||
|
||||
if self.params["displayname"]:
|
||||
self.module.debug("Display name is " + self.params["displayname"])
|
||||
self.displayname = self.params['displayname']
|
||||
else:
|
||||
self.module.debug("No display name specified. Using " + self.fqdn)
|
||||
self.displayname = self.fqdn
|
||||
|
||||
# Attempt to host information via display name of host name
|
||||
self.module.debug("Attempting to find host by displayname " +
|
||||
self.displayname)
|
||||
info = self.get_host_by_displayname(self.displayname)
|
||||
|
||||
if info is not None:
|
||||
self.module.debug("Host found by displayname")
|
||||
# Used the host information to grab the collector description
|
||||
# if not provided
|
||||
if (not hasattr(self.params, "collector") and
|
||||
"agentDescription" in info):
|
||||
self.module.debug("Setting collector from host response. " +
|
||||
"Collector " + info["agentDescription"])
|
||||
self.params["collector"] = info["agentDescription"]
|
||||
else:
|
||||
self.module.debug("Host not found by displayname")
|
||||
|
||||
# At this point, a valid collector description is required for success
|
||||
# Check that the description exists or fail
|
||||
if self.params["collector"]:
|
||||
self.module.debug("Collector specified is " +
|
||||
self.params["collector"])
|
||||
self.collector = (self.get_collector_by_description(
|
||||
self.params["collector"]))
|
||||
else:
|
||||
self.fail(msg="No collector specified.")
|
||||
|
||||
# If the host wasn't found via displayname, attempt by hostname
|
||||
if info is None:
|
||||
self.module.debug("Attempting to find host by hostname " +
|
||||
self.hostname)
|
||||
info = self.get_host_by_hostname(self.hostname, self.collector)
|
||||
|
||||
self.info = info
|
||||
|
||||
def get_properties(self):
|
||||
"""Returns a hash of the properties
|
||||
associated with this LogicMonitor host"""
|
||||
self.module.debug("Running Host.get_properties...")
|
||||
|
||||
if self.info:
|
||||
self.module.debug("Making RPC call to 'getHostProperties'")
|
||||
properties_json = (json.loads(self.rpc("getHostProperties",
|
||||
{'hostId': self.info["id"],
|
||||
"filterSystemProperties": True})))
|
||||
|
||||
if properties_json["status"] == 200:
|
||||
self.module.debug("RPC call succeeded")
|
||||
return properties_json["data"]
|
||||
else:
|
||||
self.module.debug("Error: there was an issue retrieving the " +
|
||||
"host properties")
|
||||
self.module.debug(properties_json["errmsg"])
|
||||
|
||||
self.fail(msg=properties_json["status"])
|
||||
else:
|
||||
self.module.debug(
|
||||
"Unable to find LogicMonitor host which matches " +
|
||||
self.displayname + " (" + self.hostname + ")"
|
||||
)
|
||||
return None
|
||||
|
||||
def site_facts(self):
|
||||
"""Output current properties information for the Host"""
|
||||
self.module.debug("Running Host.site_facts...")
|
||||
|
||||
if self.info:
|
||||
self.module.debug("Host exists")
|
||||
props = self.get_properties()
|
||||
|
||||
self.output_info(props)
|
||||
else:
|
||||
self.fail(msg="Error: Host doesn't exit.")
|
||||
|
||||
|
||||
class Hostgroup(LogicMonitor):
|
||||
|
||||
def __init__(self, params, module=None):
|
||||
"""Initializor for the LogicMonitor host object"""
|
||||
self.change = False
|
||||
self.params = params
|
||||
|
||||
LogicMonitor.__init__(self, module, **self.params)
|
||||
self.module.debug("Instantiating Hostgroup object")
|
||||
|
||||
self.fullpath = self.params["fullpath"]
|
||||
self.info = self.get_group(self.fullpath)
|
||||
|
||||
def get_properties(self, final=False):
|
||||
"""Returns a hash of the properties
|
||||
associated with this LogicMonitor host"""
|
||||
self.module.debug("Running Hostgroup.get_properties...")
|
||||
|
||||
if self.info:
|
||||
self.module.debug("Group found")
|
||||
|
||||
self.module.debug("Making RPC call to 'getHostGroupProperties'")
|
||||
properties_json = json.loads(self.rpc(
|
||||
"getHostGroupProperties",
|
||||
{'hostGroupId': self.info["id"],
|
||||
"finalResult": final}))
|
||||
|
||||
if properties_json["status"] == 200:
|
||||
self.module.debug("RPC call succeeded")
|
||||
return properties_json["data"]
|
||||
else:
|
||||
self.module.debug("RPC call failed")
|
||||
self.fail(msg=properties_json["status"])
|
||||
else:
|
||||
self.module.debug("Group not found")
|
||||
return None
|
||||
|
||||
def site_facts(self):
|
||||
"""Output current properties information for the Hostgroup"""
|
||||
self.module.debug("Running Hostgroup.site_facts...")
|
||||
|
||||
if self.info:
|
||||
self.module.debug("Group exists")
|
||||
props = self.get_properties(True)
|
||||
|
||||
self.output_info(props)
|
||||
else:
|
||||
self.fail(msg="Error: Group doesn't exit.")
|
||||
|
||||
|
||||
def selector(module):
|
||||
"""Figure out which object and which actions
|
||||
to take given the right parameters"""
|
||||
|
||||
if module.params["target"] == "host":
|
||||
target = Host(module.params, module)
|
||||
target.site_facts()
|
||||
elif module.params["target"] == "hostgroup":
|
||||
# Validate target specific required parameters
|
||||
if module.params["fullpath"] is not None:
|
||||
target = Hostgroup(module.params, module)
|
||||
target.site_facts()
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="Parameter 'fullpath' required for target 'hostgroup'")
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="Error: Unexpected target \"" + module.params["target"] +
|
||||
"\" was specified.")
|
||||
|
||||
|
||||
def main():
|
||||
TARGETS = [
|
||||
"host",
|
||||
"hostgroup"]
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
target=dict(required=True, default=None, choices=TARGETS),
|
||||
company=dict(required=True, default=None),
|
||||
user=dict(required=True, default=None),
|
||||
password=dict(required=True, default=None, no_log=True),
|
||||
|
||||
collector=dict(require=False, default=None),
|
||||
hostname=dict(required=False, default=None),
|
||||
displayname=dict(required=False, default=None),
|
||||
fullpath=dict(required=False, default=None)
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if HAS_LIB_JSON is not True:
|
||||
module.fail_json(msg="Unable to load JSON library")
|
||||
|
||||
selector(module)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue