#!/usr/bin/python # LogicMonitor Ansible module for managing Collectors, Hosts and Hostgroups # Copyright (C) 2015 LogicMonitor # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type RETURN = ''' --- success: description: flag indicating that execution was successful returned: success type: bool sample: True ... ''' DOCUMENTATION = ''' --- module: logicmonitor short_description: Manage your LogicMonitor account through Ansible Playbooks description: - LogicMonitor is a hosted, full-stack, infrastructure monitoring platform. - This module manages hosts, host groups, and collectors within your LogicMonitor account. 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 type of LogicMonitor object you wish to manage. - "Collector: Perform actions on a LogicMonitor collector." - NOTE You should use Ansible service modules such as M(service) or M(supervisorctl) for managing the Collector 'logicmonitor-agent' and 'logicmonitor-watchdog' services. Specifically, you'll probably want to start these services after a Collector add and stop these services before a Collector remove. - "Host: Perform actions on a host device." - "Hostgroup: Perform actions on a LogicMonitor host group." - > NOTE Host and Hostgroup tasks should always be performed via delegate_to: localhost. There are no benefits to running these tasks on the remote host and doing so will typically cause problems. required: true choices: ['collector', 'host', 'datasource', 'hostgroup'] action: description: - The action you wish to perform on target. - "Add: Add an object to your LogicMonitor account." - "Remove: Remove an object from your LogicMonitor account." - "Update: Update properties, description, or groups (target=host) for an object in your LogicMonitor account." - "SDT: Schedule downtime for an object in your LogicMonitor account." required: true choices: ['add', 'remove', 'update', 'sdt'] 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 user: description: - A LogicMonitor user name. The module will authenticate and perform actions on behalf of this user. required: true password: description: - The password of the specified LogicMonitor user required: true collector: description: - The fully qualified domain name of a collector in your LogicMonitor account. - This is required for the creation of a LogicMonitor host (target=host action=add). - This is required for updating, removing or scheduling downtime for hosts if 'displayname' isn't specified (target=host action=update action=remove action=sdt). hostname: description: - The hostname of a host in your LogicMonitor account, or the desired hostname of a device to manage. - Optional for managing hosts (target=host). default: 'hostname -f' displayname: description: - The display name of a host in your LogicMonitor account or the desired display name of a device to manage. - Optional for managing hosts (target=host). default: 'hostname -f' description: description: - The long text description of the object in your LogicMonitor account. - Optional for managing hosts and host groups (target=host or target=hostgroup; action=add or action=update). default: "" properties: description: - A dictionary of properties to set on the LogicMonitor host or host group. - Optional for managing hosts and host groups (target=host or target=hostgroup; action=add or action=update). - This parameter will add or update existing properties in your LogicMonitor account. default: {} groups: description: - A list of groups that the host should be a member of. - Optional for managing hosts (target=host; action=add or action=update). default: [] id: description: - ID of the datasource to target. - Required for management of LogicMonitor datasources (target=datasource). fullpath: description: - The fullpath of the host group object you would like to manage. - Recommend running on a single Ansible host. - Required for management of LogicMonitor host groups (target=hostgroup). alertenable: description: - A boolean flag to turn alerting on or off for an object. - Optional for managing all hosts (action=add or action=update). type: bool default: 'yes' starttime: description: - The time that the Scheduled Down Time (SDT) should begin. - Optional for managing SDT (action=sdt). - Y-m-d H:M default: Now duration: description: - The duration (minutes) of the Scheduled Down Time (SDT). - Optional for putting an object into SDT (action=sdt). default: 30 ... ''' EXAMPLES = ''' # example of adding a new LogicMonitor collector to these devices --- - hosts: collectors remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Deploy/verify LogicMonitor collectors become: yes logicmonitor: target: collector action: add company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' #example of adding a list of hosts into monitoring --- - hosts: hosts remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Deploy LogicMonitor Host # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: host action: add collector: mycompany-Collector company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' groups: /servers/production,/datacenter1 properties: snmp.community: secret dc: 1 type: prod delegate_to: localhost #example of putting a datasource in SDT --- - hosts: localhost remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: SDT a datasource # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: datasource action: sdt id: 123 duration: 3000 starttime: '2017-03-04 05:06' company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' #example of creating a hostgroup --- - hosts: localhost remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Create a host group # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: hostgroup action: add fullpath: /servers/development company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' properties: snmp.community: commstring type: dev #example of putting a list of hosts into SDT --- - hosts: hosts remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: SDT hosts # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: host action: sdt duration: 3000 starttime: '2016-11-10 09:08' company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' collector: mycompany-Collector delegate_to: localhost #example of putting a host group in SDT --- - hosts: localhost remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: SDT a host group # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: hostgroup action: sdt fullpath: /servers/development duration: 3000 starttime: '2017-03-04 05:06' company=: '{{ company }}' user: '{{ user }}' password: '{{ password }}' #example of updating a list of hosts --- - hosts: hosts remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Update a list of hosts # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: host action: update company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' collector: mycompany-Collector groups: /servers/production,/datacenter5 properties: snmp.community: commstring dc: 5 delegate_to: localhost #example of updating a hostgroup --- - hosts: hosts remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Update a host group # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: hostgroup action: update fullpath: /servers/development company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' properties: snmp.community: hg type: dev status: test delegate_to: localhost #example of removing a list of hosts from monitoring --- - hosts: hosts remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Remove LogicMonitor hosts # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: host action: remove company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' collector: mycompany-Collector delegate_to: localhost #example of removing a host group --- - hosts: hosts remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Remove LogicMonitor development servers hostgroup # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: hostgroup action: remove company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' fullpath: /servers/development delegate_to: localhost - name: Remove LogicMonitor servers hostgroup # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: hostgroup action: remove company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' fullpath: /servers delegate_to: localhost - name: Remove LogicMonitor datacenter1 hostgroup # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: hostgroup action: remove company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' fullpath: /datacenter1 delegate_to: localhost - name: Remove LogicMonitor datacenter5 hostgroup # All tasks except for target=collector should use delegate_to: localhost logicmonitor: target: hostgroup action: remove company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' fullpath: /datacenter5 delegate_to: localhost ### example of removing a new LogicMonitor collector to these devices --- - hosts: collectors remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Remove LogicMonitor collectors become: yes logicmonitor: target: collector action: remove company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' #complete example --- - hosts: localhost remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Create a host group logicmonitor: target: hostgroup action: add fullpath: /servers/production/database company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' properties: snmp.community: commstring - name: SDT a host group logicmonitor: target: hostgroup action: sdt fullpath: /servers/production/web duration: 3000 starttime: '2012-03-04 05:06' company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' - hosts: collectors remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: Deploy/verify LogicMonitor collectors logicmonitor: target: collector action: add company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' - name: Place LogicMonitor collectors into 30 minute Scheduled downtime logicmonitor: target: collector action: sdt company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' - name: Deploy LogicMonitor Host logicmonitor: target: host action: add collector: agent1.ethandev.com company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' properties: snmp.community: commstring dc: 1 groups: /servers/production/collectors, /datacenter1 delegate_to: localhost - hosts: database-servers remote_user: '{{ username }}' vars: company: mycompany user: myusername password: mypassword tasks: - name: deploy logicmonitor hosts logicmonitor: target: host action: add collector: monitoring.dev.com company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' properties: snmp.community: commstring type: db dc: 1 groups: /servers/production/database, /datacenter1 delegate_to: localhost - name: schedule 5 hour downtime for 2012-11-10 09:08 logicmonitor: target: host action: sdt duration: 3000 starttime: '2012-11-10 09:08' company: '{{ company }}' user: '{{ user }}' password: '{{ password }}' delegate_to: localhost ''' import datetime import json import os import platform import socket import sys import types from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves.urllib.parse import urlencode from ansible.module_utils.urls import open_url 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 = urlencode(params) creds = 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 as ioe: self.fail(msg="Error: Exception making RPC call to " + "https://" + self.company + "." + self.lm_url + "/rpc/" + action + "\nException" + str(ioe)) def do(self, action, params): """Make a call to the LogicMonitor server \"do\" function""" self.module.debug("Running LogicMonitor.do...") param_str = urlencode(params) creds = (urlencode( {"c": self.company, "u": self.user, "p": self.password})) if param_str: param_str = param_str + "&" param_str = param_str + creds try: self.module.debug("Attempting to open URL: " + "https://" + self.company + "." + self.lm_url + "/do/" + action + "?" + param_str) f = open_url( "https://" + self.company + "." + self.lm_url + "/do/" + action + "?" + param_str) return f.read() except IOError as ioe: self.fail(msg="Error: Exception making RPC call to " + "https://" + self.company + "." + self.lm_url + "/do/" + 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"] == 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 {0}" + 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 {0} exists." + fullpath) 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, failed=True) def exit(self, changed): self.module.debug("Changed: " + changed) self.module.exit_json(changed=changed, success=True) def output_info(self, info): self.module.debug("Registering properties as Ansible facts") self.module.exit_json(changed=False, ansible_facts=info) class Collector(LogicMonitor): def __init__(self, params, module=None): """Initializor for the LogicMonitor Collector object""" self.change = False self.params = params LogicMonitor.__init__(self, module, **params) self.module.debug("Instantiating Collector object") if self.params['description']: self.description = self.params['description'] else: self.description = self.fqdn self.info = self._get() self.installdir = "/usr/local/logicmonitor" self.platform = platform.system() self.is_64bits = sys.maxsize > 2**32 self.duration = self.params['duration'] self.starttime = self.params['starttime'] if self.info is None: self.id = None else: self.id = self.info["id"] def create(self): """Idempotent function to make sure that there is a running collector installed and registered""" self.module.debug("Running Collector.create...") self._create() self.get_installer_binary() self.install() def remove(self): """Idempotent function to make sure that there is not a running collector installed and registered""" self.module.debug("Running Collector.destroy...") self._unreigster() self.uninstall() def get_installer_binary(self): """Download the LogicMonitor collector installer binary""" self.module.debug("Running Collector.get_installer_binary...") arch = 32 if self.is_64bits: self.module.debug("64 bit system") arch = 64 else: self.module.debug("32 bit system") if self.platform == "Linux" and self.id is not None: self.module.debug("Platform is Linux") self.module.debug("Agent ID is " + str(self.id)) installfilepath = (self.installdir + "/logicmonitorsetup" + str(self.id) + "_" + str(arch) + ".bin") self.module.debug("Looking for existing installer at " + installfilepath) if not os.path.isfile(installfilepath): self.module.debug("No previous installer found") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.module.debug("Downloading installer file") # attempt to create the install dir before download self.module.run_command("mkdir " + self.installdir) try: installer = (self.do("logicmonitorsetup", {"id": self.id, "arch": arch})) with open(installfilepath, "w") as write_file: write_file.write(installer) except Exception: self.fail(msg="Unable to open installer file for writing") else: self.module.debug("Collector installer already exists") return installfilepath elif self.id is None: self.fail( msg="Error: There is currently no collector " + "associated with this device. To download " + " the installer, first create a collector " + "for this device.") elif self.platform != "Linux": self.fail( msg="Error: LogicMonitor Collector must be " + "installed on a Linux device.") else: self.fail( msg="Error: Unable to retrieve the installer from the server") def install(self): """Execute the LogicMonitor installer if not already installed""" self.module.debug("Running Collector.install...") if self.platform == "Linux": self.module.debug("Platform is Linux") installer = self.get_installer_binary() if self.info is None: self.module.debug("Retrieving collector information") self.info = self._get() if not os.path.exists(self.installdir + "/agent"): self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.module.debug("Setting installer file permissions") os.chmod(installer, 484) # decimal for 0o744 self.module.debug("Executing installer") ret_code, out, err = self.module.run_command(installer + " -y") if ret_code != 0: self.fail(msg="Error: Unable to install collector: " + err) else: self.module.debug("Collector installed successfully") else: self.module.debug("Collector already installed") else: self.fail( msg="Error: LogicMonitor Collector must be " + "installed on a Linux device") def uninstall(self): """Uninstall LogicMontitor collector from the system""" self.module.debug("Running Collector.uninstall...") uninstallfile = self.installdir + "/agent/bin/uninstall.pl" if os.path.isfile(uninstallfile): self.module.debug("Collector uninstall file exists") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.module.debug("Running collector uninstaller") ret_code, out, err = self.module.run_command(uninstallfile) if ret_code != 0: self.fail( msg="Error: Unable to uninstall collector: " + err) else: self.module.debug("Collector successfully uninstalled") else: if os.path.exists(self.installdir + "/agent"): (self.fail( msg="Unable to uninstall LogicMonitor " + "Collector. Can not find LogicMonitor " + "uninstaller.")) def sdt(self): """Create a scheduled down time (maintenance window) for this host""" self.module.debug("Running Collector.sdt...") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) duration = self.duration starttime = self.starttime offsetstart = starttime if starttime: self.module.debug("Start time specified") start = datetime.datetime.strptime(starttime, '%Y-%m-%d %H:%M') offsetstart = start else: self.module.debug("No start time specified. Using default.") start = datetime.datetime.utcnow() # Use user UTC offset self.module.debug("Making RPC call to 'getTimeZoneSetting'") accountresp = json.loads(self.rpc("getTimeZoneSetting", {})) if accountresp["status"] == 200: self.module.debug("RPC call succeeded") offset = accountresp["data"]["offset"] offsetstart = start + datetime.timedelta(0, offset) else: self.fail(msg="Error: Unable to retrieve timezone offset") offsetend = offsetstart + datetime.timedelta(0, int(duration) * 60) h = {"agentId": self.id, "type": 1, "notifyCC": True, "year": offsetstart.year, "month": offsetstart.month - 1, "day": offsetstart.day, "hour": offsetstart.hour, "minute": offsetstart.minute, "endYear": offsetend.year, "endMonth": offsetend.month - 1, "endDay": offsetend.day, "endHour": offsetend.hour, "endMinute": offsetend.minute} self.module.debug("Making RPC call to 'setAgentSDT'") resp = json.loads(self.rpc("setAgentSDT", h)) if resp["status"] == 200: self.module.debug("RPC call succeeded") return resp["data"] else: self.module.debug("RPC call failed") self.fail(msg=resp["errmsg"]) def site_facts(self): """Output current properties information for the Collector""" self.module.debug("Running Collector.site_facts...") if self.info: self.module.debug("Collector exists") props = self.get_properties(True) self.output_info(props) else: self.fail(msg="Error: Collector doesn't exit.") def _get(self): """Returns a JSON object representing this collector""" self.module.debug("Running Collector._get...") collector_list = self.get_collectors() if collector_list is not None: self.module.debug("Collectors returned") for collector in collector_list: if collector["description"] == self.description: return collector else: self.module.debug("No collectors returned") return None def _create(self): """Create a new collector in the associated LogicMonitor account""" self.module.debug("Running Collector._create...") if self.platform == "Linux": self.module.debug("Platform is Linux") ret = self.info or self._get() if ret is None: self.change = True self.module.debug("System changed") if self.check_mode: self.exit(changed=True) h = {"autogen": True, "description": self.description} self.module.debug("Making RPC call to 'addAgent'") create = (json.loads(self.rpc("addAgent", h))) if create["status"] == 200: self.module.debug("RPC call succeeded") self.info = create["data"] self.id = create["data"]["id"] return create["data"] else: self.fail(msg=create["errmsg"]) else: self.info = ret self.id = ret["id"] return ret else: self.fail( msg="Error: LogicMonitor Collector must be " + "installed on a Linux device.") def _unreigster(self): """Delete this collector from the associated LogicMonitor account""" self.module.debug("Running Collector._unreigster...") if self.info is None: self.module.debug("Retrieving collector information") self.info = self._get() if self.info is not None: self.module.debug("Collector found") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.module.debug("Making RPC call to 'deleteAgent'") delete = json.loads(self.rpc("deleteAgent", {"id": self.id})) if delete["status"] == 200: self.module.debug("RPC call succeeded") return delete else: # The collector couldn't unregister. Start the service again self.module.debug("Error unregistering collecting. " + delete["errmsg"]) self.fail(msg=delete["errmsg"]) else: self.module.debug("Collector not found") return None 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 self.properties = self.params["properties"] self.description = self.params["description"] self.starttime = self.params["starttime"] self.duration = self.params["duration"] self.alertenable = self.params["alertenable"] if self.params["groups"] is not None: self.groups = self._strip_groups(self.params["groups"]) else: self.groups = None def create(self): """Idempotent function to create if missing, update if changed, or skip""" self.module.debug("Running Host.create...") self.update() 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 set_properties(self, propertyhash): """update the host to have the properties contained in the property hash""" self.module.debug("Running Host.set_properties...") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.module.debug("Assigning property hash to host object") self.properties = propertyhash def add(self): """Add this device to monitoring in your LogicMonitor account""" self.module.debug("Running Host.add...") if self.collector and not self.info: self.module.debug("Host not registered. Registering.") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) h = self._build_host_hash( self.hostname, self.displayname, self.collector, self.description, self.groups, self.properties, self.alertenable) self.module.debug("Making RPC call to 'addHost'") resp = json.loads(self.rpc("addHost", h)) if resp["status"] == 200: self.module.debug("RPC call succeeded") return resp["data"] else: self.module.debug("RPC call failed") self.module.debug(resp) return resp["errmsg"] elif self.collector is None: self.fail(msg="Specified collector doesn't exist") else: self.module.debug("Host already registered") def update(self): """This method takes changes made to this host and applies them to the corresponding host in your LogicMonitor account.""" self.module.debug("Running Host.update...") if self.info: self.module.debug("Host already registered") if self.is_changed(): self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) h = (self._build_host_hash( self.hostname, self.displayname, self.collector, self.description, self.groups, self.properties, self.alertenable)) h["id"] = self.info["id"] h["opType"] = "replace" self.module.debug("Making RPC call to 'updateHost'") resp = json.loads(self.rpc("updateHost", h)) if resp["status"] == 200: self.module.debug("RPC call succeeded") else: self.module.debug("RPC call failed") self.fail(msg="Error: unable to update the host.") else: self.module.debug( "Host properties match supplied properties. " + "No changes to make." ) return self.info else: self.module.debug("Host not registered. Registering") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) return self.add() def remove(self): """Remove this host from your LogicMonitor account""" self.module.debug("Running Host.remove...") if self.info: self.module.debug("Host registered") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.module.debug("Making RPC call to 'deleteHost'") resp = json.loads(self.rpc("deleteHost", {"hostId": self.info["id"], "deleteFromSystem": True, "hostGroupId": 1})) if resp["status"] == 200: self.module.debug(resp) self.module.debug("RPC call succeeded") return resp else: self.module.debug("RPC call failed") self.module.debug(resp) self.fail(msg=resp["errmsg"]) else: self.module.debug("Host not registered") def is_changed(self): """Return true if the host doesn't match the LogicMonitor account""" self.module.debug("Running Host.is_changed") ignore = ['system.categories', 'snmp.version'] hostresp = self.get_host_by_displayname(self.displayname) if hostresp is None: hostresp = self.get_host_by_hostname(self.hostname, self.collector) if hostresp: self.module.debug("Comparing simple host properties") if hostresp["alertEnable"] != self.alertenable: return True if hostresp["description"] != self.description: return True if hostresp["displayedAs"] != self.displayname: return True if (self.collector and hasattr(self.collector, "id") and hostresp["agentId"] != self.collector["id"]): return True self.module.debug("Comparing groups.") if self._compare_groups(hostresp) is True: return True propresp = self.get_properties() if propresp: self.module.debug("Comparing properties.") if self._compare_props(propresp, ignore) is True: return True else: self.fail( msg="Error: Unknown error retrieving host properties") return False else: self.fail(msg="Error: Unknown error retrieving host information") def sdt(self): """Create a scheduled down time (maintenance window) for this host""" self.module.debug("Running Host.sdt...") if self.info: self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) duration = self.duration starttime = self.starttime offset = starttime if starttime: self.module.debug("Start time specified") start = datetime.datetime.strptime(starttime, '%Y-%m-%d %H:%M') offsetstart = start else: self.module.debug("No start time specified. Using default.") start = datetime.datetime.utcnow() # Use user UTC offset self.module.debug("Making RPC call to 'getTimeZoneSetting'") accountresp = (json.loads(self.rpc("getTimeZoneSetting", {}))) if accountresp["status"] == 200: self.module.debug("RPC call succeeded") offset = accountresp["data"]["offset"] offsetstart = start + datetime.timedelta(0, offset) else: self.fail( msg="Error: Unable to retrieve timezone offset") offsetend = offsetstart + datetime.timedelta(0, int(duration) * 60) h = {"hostId": self.info["id"], "type": 1, "year": offsetstart.year, "month": offsetstart.month - 1, "day": offsetstart.day, "hour": offsetstart.hour, "minute": offsetstart.minute, "endYear": offsetend.year, "endMonth": offsetend.month - 1, "endDay": offsetend.day, "endHour": offsetend.hour, "endMinute": offsetend.minute} self.module.debug("Making RPC call to 'setHostSDT'") resp = (json.loads(self.rpc("setHostSDT", h))) if resp["status"] == 200: self.module.debug("RPC call succeeded") return resp["data"] else: self.module.debug("RPC call failed") self.fail(msg=resp["errmsg"]) else: self.fail(msg="Error: Host doesn't exit.") 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.") def _build_host_hash(self, hostname, displayname, collector, description, groups, properties, alertenable): """Return a property formatted hash for the creation of a host using the rpc function""" self.module.debug("Running Host._build_host_hash...") h = {} h["hostName"] = hostname h["displayedAs"] = displayname h["alertEnable"] = alertenable if collector: self.module.debug("Collector property exists") h["agentId"] = collector["id"] else: self.fail( msg="Error: No collector found. Unable to build host hash.") if description: h["description"] = description if groups is not None and groups is not []: self.module.debug("Group property exists") groupids = "" for group in groups: groupids = groupids + str(self.create_group(group)) + "," h["hostGroupIds"] = groupids.rstrip(',') if properties is not None and properties is not {}: self.module.debug("Properties hash exists") propnum = 0 for key, value in properties.items(): h["propName" + str(propnum)] = key h["propValue" + str(propnum)] = value propnum = propnum + 1 return h def _verify_property(self, propname): """Check with LogicMonitor server to verify property is unchanged""" self.module.debug("Running Host._verify_property...") if self.info: self.module.debug("Host is registered") if propname not in self.properties: self.module.debug("Property " + propname + " does not exist") return False else: self.module.debug("Property " + propname + " exists") h = {"hostId": self.info["id"], "propName0": propname, "propValue0": self.properties[propname]} self.module.debug("Making RCP call to 'verifyProperties'") resp = json.loads(self.rpc('verifyProperties', h)) if resp["status"] == 200: self.module.debug("RPC call succeeded") return resp["data"]["match"] else: self.fail( msg="Error: unable to get verification " + "from server.\n%s" % resp["errmsg"]) else: self.fail( msg="Error: Host doesn't exist. Unable to verify properties") def _compare_groups(self, hostresp): """Function to compare the host's current groups against provided groups""" self.module.debug("Running Host._compare_groups") g = [] fullpathinids = hostresp["fullPathInIds"] self.module.debug("Building list of groups") for path in fullpathinids: if path != []: h = {'hostGroupId': path[-1]} hgresp = json.loads(self.rpc("getHostGroup", h)) if (hgresp["status"] == 200 and hgresp["data"]["appliesTo"] == ""): g.append(path[-1]) if self.groups is not None: self.module.debug("Comparing group lists") for group in self.groups: groupjson = self.get_group(group) if groupjson is None: self.module.debug("Group mismatch. No result.") return True elif groupjson['id'] not in g: self.module.debug("Group mismatch. ID doesn't exist.") return True else: g.remove(groupjson['id']) if g != []: self.module.debug("Group mismatch. New ID exists.") return True self.module.debug("Groups match") def _compare_props(self, propresp, ignore): """Function to compare the host's current properties against provided properties""" self.module.debug("Running Host._compare_props...") p = {} self.module.debug("Creating list of properties") for prop in propresp: if prop["name"] not in ignore: if ("*******" in prop["value"] and self._verify_property(prop["name"])): p[prop["name"]] = self.properties[prop["name"]] else: p[prop["name"]] = prop["value"] self.module.debug("Comparing properties") # Iterate provided properties and compare to received properties for prop in self.properties: if (prop not in p or p[prop] != self.properties[prop]): self.module.debug("Properties mismatch") return True self.module.debug("Properties match") def _strip_groups(self, groups): """Function to strip whitespace from group list. This function provides the user some flexibility when formatting group arguments """ self.module.debug("Running Host._strip_groups...") return map(lambda x: x.strip(), groups) class Datasource(LogicMonitor): def __init__(self, params, module=None): """Initializor for the LogicMonitor Datasource object""" self.change = False self.params = params LogicMonitor.__init__(self, module, **params) self.module.debug("Instantiating Datasource object") self.id = self.params["id"] self.starttime = self.params["starttime"] self.duration = self.params["duration"] def sdt(self): """Create a scheduled down time (maintenance window) for this host""" self.module.debug("Running Datasource.sdt...") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) duration = self.duration starttime = self.starttime offsetstart = starttime if starttime: self.module.debug("Start time specified") start = datetime.datetime.strptime(starttime, '%Y-%m-%d %H:%M') offsetstart = start else: self.module.debug("No start time specified. Using default.") start = datetime.datetime.utcnow() # Use user UTC offset self.module.debug("Making RPC call to 'getTimeZoneSetting'") accountresp = json.loads(self.rpc("getTimeZoneSetting", {})) if accountresp["status"] == 200: self.module.debug("RPC call succeeded") offset = accountresp["data"]["offset"] offsetstart = start + datetime.timedelta(0, offset) else: self.fail(msg="Error: Unable to retrieve timezone offset") offsetend = offsetstart + datetime.timedelta(0, int(duration) * 60) h = {"hostDataSourceId": self.id, "type": 1, "notifyCC": True, "year": offsetstart.year, "month": offsetstart.month - 1, "day": offsetstart.day, "hour": offsetstart.hour, "minute": offsetstart.minute, "endYear": offsetend.year, "endMonth": offsetend.month - 1, "endDay": offsetend.day, "endHour": offsetend.hour, "endMinute": offsetend.minute} self.module.debug("Making RPC call to 'setHostDataSourceSDT'") resp = json.loads(self.rpc("setHostDataSourceSDT", h)) if resp["status"] == 200: self.module.debug("RPC call succeeded") return resp["data"] else: self.module.debug("RPC call failed") self.fail(msg=resp["errmsg"]) 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) self.properties = self.params["properties"] self.description = self.params["description"] self.starttime = self.params["starttime"] self.duration = self.params["duration"] self.alertenable = self.params["alertenable"] def create(self): """Wrapper for self.update()""" self.module.debug("Running Hostgroup.create...") self.update() 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 set_properties(self, propertyhash): """Update the host to have the properties contained in the property hash""" self.module.debug("Running Hostgroup.set_properties") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.module.debug("Assigning property has to host object") self.properties = propertyhash def add(self): """Idempotent function to ensure that the host group exists in your LogicMonitor account""" self.module.debug("Running Hostgroup.add") if self.info is None: self.module.debug("Group doesn't exist. Creating.") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.create_group(self.fullpath) self.info = self.get_group(self.fullpath) self.module.debug("Group created") return self.info else: self.module.debug("Group already exists") def update(self): """Idempotent function to ensure the host group settings (alertenable, properties, etc) in the LogicMonitor account match the current object.""" self.module.debug("Running Hostgroup.update") if self.info: if self.is_changed(): self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) h = self._build_host_group_hash( self.fullpath, self.description, self.properties, self.alertenable) h["opType"] = "replace" if self.fullpath != "/": h["id"] = self.info["id"] self.module.debug("Making RPC call to 'updateHostGroup'") resp = json.loads(self.rpc("updateHostGroup", h)) if resp["status"] == 200: self.module.debug("RPC call succeeded") return resp["data"] else: self.module.debug("RPC call failed") self.fail(msg="Error: Unable to update the " + "host.\n" + resp["errmsg"]) else: self.module.debug( "Group properties match supplied properties. " + "No changes to make" ) return self.info else: self.module.debug("Group doesn't exist. Creating.") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) return self.add() def remove(self): """Idempotent function to ensure the host group does not exist in your LogicMonitor account""" self.module.debug("Running Hostgroup.remove...") if self.info: self.module.debug("Group exists") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) self.module.debug("Making RPC call to 'deleteHostGroup'") resp = json.loads(self.rpc("deleteHostGroup", {"hgId": self.info["id"]})) if resp["status"] == 200: self.module.debug(resp) self.module.debug("RPC call succeeded") return resp elif resp["errmsg"] == "No such group": self.module.debug("Group doesn't exist") else: self.module.debug("RPC call failed") self.module.debug(resp) self.fail(msg=resp["errmsg"]) else: self.module.debug("Group doesn't exist") def is_changed(self): """Return true if the host doesn't match the LogicMonitor account""" self.module.debug("Running Hostgroup.is_changed...") ignore = [] group = self.get_group(self.fullpath) properties = self.get_properties() if properties is not None and group is not None: self.module.debug("Comparing simple group properties") if (group["alertEnable"] != self.alertenable or group["description"] != self.description): return True p = {} self.module.debug("Creating list of properties") for prop in properties: if prop["name"] not in ignore: if ("*******" in prop["value"] and self._verify_property(prop["name"])): p[prop["name"]] = ( self.properties[prop["name"]]) else: p[prop["name"]] = prop["value"] self.module.debug("Comparing properties") if set(p) != set(self.properties): return True else: self.module.debug("No property information received") return False def sdt(self, duration=30, starttime=None): """Create a scheduled down time (maintenance window) for this host""" self.module.debug("Running Hostgroup.sdt") self.module.debug("System changed") self.change = True if self.check_mode: self.exit(changed=True) duration = self.duration starttime = self.starttime offset = starttime if starttime: self.module.debug("Start time specified") start = datetime.datetime.strptime(starttime, '%Y-%m-%d %H:%M') offsetstart = start else: self.module.debug("No start time specified. Using default.") start = datetime.datetime.utcnow() # Use user UTC offset self.module.debug("Making RPC call to 'getTimeZoneSetting'") accountresp = json.loads(self.rpc("getTimeZoneSetting", {})) if accountresp["status"] == 200: self.module.debug("RPC call succeeded") offset = accountresp["data"]["offset"] offsetstart = start + datetime.timedelta(0, offset) else: self.fail( msg="Error: Unable to retrieve timezone offset") offsetend = offsetstart + datetime.timedelta(0, int(duration) * 60) h = {"hostGroupId": self.info["id"], "type": 1, "year": offsetstart.year, "month": offsetstart.month - 1, "day": offsetstart.day, "hour": offsetstart.hour, "minute": offsetstart.minute, "endYear": offsetend.year, "endMonth": offsetend.month - 1, "endDay": offsetend.day, "endHour": offsetend.hour, "endMinute": offsetend.minute} self.module.debug("Making RPC call to setHostGroupSDT") resp = json.loads(self.rpc("setHostGroupSDT", h)) if resp["status"] == 200: self.module.debug("RPC call succeeded") return resp["data"] else: self.module.debug("RPC call failed") self.fail(msg=resp["errmsg"]) 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 _build_host_group_hash(self, fullpath, description, properties, alertenable): """Return a property formatted hash for the creation of a hostgroup using the rpc function""" self.module.debug("Running Hostgroup._build_host_hash") h = {} h["alertEnable"] = alertenable if fullpath == "/": self.module.debug("Group is root") h["id"] = 1 else: self.module.debug("Determining group path") parentpath, name = fullpath.rsplit('/', 1) parent = self.get_group(parentpath) h["name"] = name if parent: self.module.debug("Parent group " + str(parent["id"]) + " found.") h["parentID"] = parent["id"] else: self.module.debug("No parent group found. Using root.") h["parentID"] = 1 if description: self.module.debug("Description property exists") h["description"] = description if properties != {}: self.module.debug("Properties hash exists") propnum = 0 for key, value in properties.items(): h["propName" + str(propnum)] = key h["propValue" + str(propnum)] = value propnum = propnum + 1 return h def _verify_property(self, propname): """Check with LogicMonitor server to verify property is unchanged""" self.module.debug("Running Hostgroup._verify_property") if self.info: self.module.debug("Group exists") if propname not in self.properties: self.module.debug("Property " + propname + " does not exist") return False else: self.module.debug("Property " + propname + " exists") h = {"hostGroupId": self.info["id"], "propName0": propname, "propValue0": self.properties[propname]} self.module.debug("Making RCP call to 'verifyProperties'") resp = json.loads(self.rpc('verifyProperties', h)) if resp["status"] == 200: self.module.debug("RPC call succeeded") return resp["data"]["match"] else: self.fail( msg="Error: unable to get verification " + "from server.\n%s" % resp["errmsg"]) else: self.fail( msg="Error: Group doesn't exist. Unable to verify properties") def selector(module): """Figure out which object and which actions to take given the right parameters""" if module.params["target"] == "collector": target = Collector(module.params, module) elif module.params["target"] == "host": # Make sure required parameter collector is specified if ((module.params["action"] == "add" or module.params["displayname"] is None) and module.params["collector"] is None): module.fail_json( msg="Parameter 'collector' required.") target = Host(module.params, module) elif module.params["target"] == "datasource": # Validate target specific required parameters if module.params["id"] is not None: # make sure a supported action was specified if module.params["action"] == "sdt": target = Datasource(module.params, module) else: errmsg = ("Error: Unexpected action \"" + module.params["action"] + "\" was specified.") module.fail_json(msg=errmsg) elif module.params["target"] == "hostgroup": # Validate target specific required parameters if module.params["fullpath"] is not None: target = Hostgroup(module.params, module) else: module.fail_json( msg="Parameter 'fullpath' required for target 'hostgroup'") else: module.fail_json( msg="Error: Unexpected target \"" + module.params["target"] + "\" was specified.") if module.params["action"].lower() == "add": action = target.create elif module.params["action"].lower() == "remove": action = target.remove elif module.params["action"].lower() == "sdt": action = target.sdt elif module.params["action"].lower() == "update": action = target.update else: errmsg = ("Error: Unexpected action \"" + module.params["action"] + "\" was specified.") module.fail_json(msg=errmsg) action() module.exit_json(changed=target.change) def main(): TARGETS = [ "collector", "host", "datasource", "hostgroup"] ACTIONS = [ "add", "remove", "sdt", "update"] module = AnsibleModule( argument_spec=dict( target=dict(required=True, default=None, choices=TARGETS), action=dict(required=True, default=None, choices=ACTIONS), company=dict(required=True, default=None), user=dict(required=True, default=None), password=dict(required=True, default=None, no_log=True), collector=dict(required=False, default=None), hostname=dict(required=False, default=None), displayname=dict(required=False, default=None), id=dict(required=False, default=None), description=dict(required=False, default=""), fullpath=dict(required=False, default=None), starttime=dict(required=False, default=None), duration=dict(required=False, default=30, type='int'), properties=dict(required=False, default={}, type="dict"), groups=dict(required=False, default=[], type="list"), alertenable=dict(required=False, default="true", type="bool") ), supports_check_mode=True ) selector(module) if __name__ == "__main__": main()