#!/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 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 ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'version': '1.0'} 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 and host groups within your LogicMonitor account. version_added: "2.2" author: [Ethan Culler-Mayeno (@ethanculler), Jeff Wozniak (@woz5999)] 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.__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 = open_url(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: ioe = get_exception() self.fail(msg="Error: Exception making RPC call to " + "https://" + self.company + "." + self.lm_url + "/rpc/" + action + "\nException" + str(ioe)) 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()