From 9a86cdcd49afd88575850fc74fae70fbd4585c2c Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 10:07:44 -0700 Subject: [PATCH 01/17] Set version to 2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a06892f..76dab86 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup setup(name='logicmonitor_core', - version='1.0.7.5', + version='2.0.0.0', description='LogicMonitor API python classes', long_description='LogicMonitor is a cloud-based, full stack, IT \ infrastructure monitoring solution that allows you to manage your \ From c90b10bbbdea19a2a748e0af959c72327a1dd7ec Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 13:28:03 -0700 Subject: [PATCH 02/17] Add switch to control which api is used --- logicmonitor_core/Collector.py | 27 ++++++++-------- logicmonitor_core/Datasource.py | 15 ++++----- logicmonitor_core/Host.py | 51 ++++++++++++++++--------------- logicmonitor_core/HostList.py | 6 ++-- logicmonitor_core/Hostgroup.py | 43 +++++++++++++------------- logicmonitor_core/LogicMonitor.py | 43 +++++++++++++------------- 6 files changed, 95 insertions(+), 90 deletions(-) diff --git a/logicmonitor_core/Collector.py b/logicmonitor_core/Collector.py index 5471910..1df397a 100755 --- a/logicmonitor_core/Collector.py +++ b/logicmonitor_core/Collector.py @@ -20,6 +20,7 @@ def __init__(self, params): logging.debug("Instantiating Collector object") self.change = False self.params = params + self.api = self.rpc LogicMonitor.__init__(self, **params) @@ -353,11 +354,11 @@ def sdt(self): start = datetime.utcnow() # Use user UTC offset - logging.debug("Making RPC call to 'getTimeZoneSetting'") - accountresp = json.loads(self.rpc("getTimeZoneSetting", {})) + logging.debug("Making API call to 'getTimeZoneSetting'") + accountresp = json.loads(self.api("getTimeZoneSetting", {})) if accountresp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") offset = accountresp["data"]["offset"] offsetstart = start + timedelta(0, offset) @@ -380,14 +381,14 @@ def sdt(self): "endHour": offsetend.hour, "endMinute": offsetend.minute} - logging.debug("Making RPC call to 'setAgentSDT'") - resp = json.loads(self.rpc("setAgentSDT", h)) + logging.debug("Making API call to 'setAgentSDT'") + resp = json.loads(self.api("setAgentSDT", h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") self.fail(msg=resp["errmsg"]) def site_facts(self): @@ -452,11 +453,11 @@ def _create(self): h = {"autogen": True, "description": self.description} - logging.debug("Making RPC call to 'addAgent'") - create = (json.loads(self.rpc("addAgent", h))) + logging.debug("Making API call to 'addAgent'") + create = (json.loads(self.api("addAgent", h))) if create["status"] is 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") self.info = create["data"] self.id = create["data"]["id"] return create["data"] @@ -488,12 +489,12 @@ def _unregister(self): if self.check_mode: self.exit(changed=True) - logging.debug("Making RPC call to 'deleteAgent'") - delete = json.loads(self.rpc("deleteAgent", + logging.debug("Making API call to 'deleteAgent'") + delete = json.loads(self.api("deleteAgent", {"id": self.id})) if delete["status"] is 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return delete else: # The collector couldn't unregister. Start the service again diff --git a/logicmonitor_core/Datasource.py b/logicmonitor_core/Datasource.py index 84debe6..6263cb8 100644 --- a/logicmonitor_core/Datasource.py +++ b/logicmonitor_core/Datasource.py @@ -14,6 +14,7 @@ def __init__(self, params): logging.debug("Instantiating Datasource object") self.change = False self.params = params + self.api = self.rpc LogicMonitor.__init__(self, **params) @@ -45,11 +46,11 @@ def sdt(self): start = datetime.utcnow() # Use user UTC offset - logging.debug("Making RPC call to 'getTimeZoneSetting'") - accountresp = json.loads(self.rpc("getTimeZoneSetting", {})) + logging.debug("Making API call to 'getTimeZoneSetting'") + accountresp = json.loads(self.api("getTimeZoneSetting", {})) if accountresp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") offset = accountresp["data"]["offset"] offsetstart = start + timedelta(0, offset) @@ -72,12 +73,12 @@ def sdt(self): "endHour": offsetend.hour, "endMinute": offsetend.minute} - logging.debug("Making RPC call to 'setHostDataSourceSDT'") - resp = json.loads(self.rpc("setHostDataSourceSDT", h)) + logging.debug("Making API call to 'setHostDataSourceSDT'") + resp = json.loads(self.api("setHostDataSourceSDT", h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") self.fail(msg=resp["errmsg"]) diff --git a/logicmonitor_core/Host.py b/logicmonitor_core/Host.py index c978a30..0582feb 100755 --- a/logicmonitor_core/Host.py +++ b/logicmonitor_core/Host.py @@ -15,6 +15,7 @@ def __init__(self, params): self.change = False self.params = params self.collector = None + self.api = self.rpc LogicMonitor.__init__(self, **self.params) @@ -89,13 +90,13 @@ def get_properties(self): logging.debug("Running Host.get_properties...") if self.info: - logging.debug("Making RPC call to 'getHostProperties'") - properties_json = (json.loads(self.rpc("getHostProperties", + logging.debug("Making API call to 'getHostProperties'") + properties_json = (json.loads(self.api("getHostProperties", {'hostId': self.info["id"], "filterSystemProperties": True}))) if properties_json["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return properties_json["data"] else: logging.debug("Error: there was an issue retrieving the " + @@ -144,14 +145,14 @@ def add(self): self.properties, self.alertenable) - logging.debug("Making RPC call to 'addHost'") - resp = json.loads(self.rpc("addHost", h)) + logging.debug("Making API call to 'addHost'") + resp = json.loads(self.api("addHost", h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") logging.debug(resp) return resp["errmsg"] elif self.collector is None: @@ -185,13 +186,13 @@ def update(self): h["id"] = self.info["id"] h["opType"] = "replace" - logging.debug("Making RPC call to 'updateHost'") - resp = json.loads(self.rpc("updateHost", h)) + logging.debug("Making API call to 'updateHost'") + resp = json.loads(self.api("updateHost", h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") else: - logging.debug("RPC call failed") + logging.debug("API call failed") self.fail(msg="Error: unable to update the host.") else: logging.debug("Host properties match supplied properties. " + @@ -219,18 +220,18 @@ def remove(self): if self.check_mode: self.exit(changed=True) - logging.debug("Making RPC call to 'deleteHost'") - resp = json.loads(self.rpc("deleteHost", + logging.debug("Making API call to 'deleteHost'") + resp = json.loads(self.api("deleteHost", {"hostId": self.info["id"], "deleteFromSystem": True, "hostGroupId": 1})) if resp["status"] == 200: logging.debug(resp) - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp else: - logging.debug("RPC call failed") + logging.debug("API call failed") logging.debug(resp) self.fail(msg=resp["errmsg"]) @@ -308,11 +309,11 @@ def sdt(self): start = datetime.utcnow() # Use user UTC offset - logging.debug("Making RPC call to 'getTimeZoneSetting'") - accountresp = (json.loads(self.rpc("getTimeZoneSetting", {}))) + logging.debug("Making API call to 'getTimeZoneSetting'") + accountresp = (json.loads(self.api("getTimeZoneSetting", {}))) if accountresp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") offset = accountresp["data"]["offset"] offsetstart = start + timedelta(0, offset) @@ -335,14 +336,14 @@ def sdt(self): "endHour": offsetend.hour, "endMinute": offsetend.minute} - logging.debug("Making RPC call to 'setHostSDT'") - resp = (json.loads(self.rpc("setHostSDT", h))) + logging.debug("Making API call to 'setHostSDT'") + resp = (json.loads(self.api("setHostSDT", h))) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") self.fail(msg=resp["errmsg"]) else: self.fail(msg="Error: Host doesn't exit.") @@ -422,10 +423,10 @@ def _verify_property(self, propname): "propValue0": self.properties[propname]} logging.debug("Making RCP call to 'verifyProperties'") - resp = json.loads(self.rpc('verifyProperties', h)) + resp = json.loads(self.api('verifyProperties', h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"]["match"] else: self.fail( @@ -447,7 +448,7 @@ def _compare_groups(self, hostresp): if path != []: h = {'hostGroupId': path[-1]} - hgresp = json.loads(self.rpc("getHostGroup", h)) + hgresp = json.loads(self.api("getHostGroup", h)) if (hgresp["status"] == 200 and hgresp["data"]["appliesTo"] == ""): diff --git a/logicmonitor_core/HostList.py b/logicmonitor_core/HostList.py index aeb5f92..4398fab 100644 --- a/logicmonitor_core/HostList.py +++ b/logicmonitor_core/HostList.py @@ -34,13 +34,13 @@ def get_hosts(self): associated with this LogicMonitor account""" logging.debug("Running HostList.get_hosts...") - logging.debug("Making RPC call to 'getHosts'") - properties_json = (json.loads(self.rpc("getHosts", + logging.debug("Making API call to 'getHosts'") + properties_json = (json.loads(self.api("getHosts", {"hostGroupId": self.groupId or 1}))) if properties_json["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return properties_json["data"] else: logging.debug("Error: there was an issue retrieving the " + diff --git a/logicmonitor_core/Hostgroup.py b/logicmonitor_core/Hostgroup.py index 9f22f36..cc05137 100755 --- a/logicmonitor_core/Hostgroup.py +++ b/logicmonitor_core/Hostgroup.py @@ -24,6 +24,7 @@ def __init__(self, params): self.starttime = self.params["starttime"] self.duration = self.params["duration"] self.alertenable = self.params["alertenable"] + self.api = self.rpc def create(self): """Wrapper for self.update()""" @@ -38,17 +39,17 @@ def get_properties(self, final=False): if self.info: logging.debug("Group found") - logging.debug("Making RPC call to 'getHostGroupProperties'") - properties_json = json.loads(self.rpc( + logging.debug("Making API call to 'getHostGroupProperties'") + properties_json = json.loads(self.api( "getHostGroupProperties", {'hostGroupId': self.info["id"], "finalResult": final})) if properties_json["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return properties_json["data"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") self.fail(msg=properties_json["status"]) else: logging.debug("Group not found") @@ -113,14 +114,14 @@ def update(self): if self.fullpath != "/": h["id"] = self.info["id"] - logging.debug("Making RPC call to 'updateHostGroup'") - resp = json.loads(self.rpc("updateHostGroup", h)) + logging.debug("Making API call to 'updateHostGroup'") + resp = json.loads(self.api("updateHostGroup", h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") self.fail( msg="Error: Unable to update the " + "host.\n" + resp["errmsg"]) @@ -152,18 +153,18 @@ def remove(self): if self.check_mode: self.exit(changed=True) - logging.debug("Making RPC call to 'deleteHostGroup'") - resp = json.loads(self.rpc("deleteHostGroup", + logging.debug("Making API call to 'deleteHostGroup'") + resp = json.loads(self.api("deleteHostGroup", {"hgId": self.info["id"]})) if resp["status"] == 200: logging.debug(resp) - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp elif resp["errmsg"] == "No such group": logging.debug("Group doesn't exist") else: - logging.debug("RPC call failed") + logging.debug("API call failed") logging.debug(resp) self.fail(msg=resp["errmsg"]) else: @@ -229,11 +230,11 @@ def sdt(self, duration=30, starttime=None): start = datetime.utcnow() # Use user UTC offset - logging.debug("Making RPC call to 'getTimeZoneSetting'") - accountresp = json.loads(self.rpc("getTimeZoneSetting", {})) + logging.debug("Making API call to 'getTimeZoneSetting'") + accountresp = json.loads(self.api("getTimeZoneSetting", {})) if accountresp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") offset = accountresp["data"]["offset"] offsetstart = start + timedelta(0, offset) @@ -256,14 +257,14 @@ def sdt(self, duration=30, starttime=None): "endHour": offsetend.hour, "endMinute": offsetend.minute} - logging.debug("Making RPC call to setHostGroupSDT") - resp = json.loads(self.rpc("setHostGroupSDT", h)) + logging.debug("Making API call to setHostGroupSDT") + resp = json.loads(self.api("setHostGroupSDT", h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") self.fail(msg=resp["errmsg"]) def site_facts(self): @@ -338,10 +339,10 @@ def _verify_property(self, propname): "propValue0": self.properties[propname]} logging.debug("Making RCP call to 'verifyProperties'") - resp = json.loads(self.rpc('verifyProperties', h)) + resp = json.loads(self.api('verifyProperties', h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"]["match"] else: self.fail( diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index f1140f7..0a5cafd 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -21,9 +21,10 @@ def __init__(self, **params): self.fqdn = socket.getfqdn() self.lm_url = "logicmonitor.com/santaba" self.urlopen = urllib2.urlopen + self.api = self.rpc def rpc(self, action, params): - """Make a call to the LogicMonitor RPC library + """Make a call to the LogicMonitor API library and return the response""" logging.debug("Running LogicMonitor.rpc") @@ -56,7 +57,7 @@ def rpc(self, action, params): return raw except IOError as ioe: logging.debug(ioe) - self.fail(msg="Error: Unknown exception making RPC call") + self.fail(msg="Error: Unknown exception making API call") def do(self, action, params): """Make a call to the LogicMonitor @@ -91,12 +92,12 @@ def get_collectors(self): LogicMonitor collectors""" logging.debug("Running LogicMonitor.get_collectors...") - logging.debug("Making RPC call to 'getAgents'") - resp = self.rpc("getAgents", {}) + logging.debug("Making API call to 'getAgents'") + resp = self.api("getAgents", {}) resp_json = json.loads(resp) if resp_json["status"] is 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp_json["data"] else: self.fail(msg=resp) @@ -107,12 +108,12 @@ def get_host_by_hostname(self, hostname, collector): logging.debug("Running LogicMonitor.get_host_by_hostname...") logging.debug("Looking for hostname " + hostname) - logging.debug("Making RPC call to 'getHosts'") - hostlist_json = json.loads(self.rpc("getHosts", {"hostGroupId": 1})) + logging.debug("Making API call to 'getHosts'") + hostlist_json = json.loads(self.api("getHosts", {"hostGroupId": 1})) if collector: if hostlist_json["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") hosts = hostlist_json["data"]["hosts"] @@ -129,7 +130,7 @@ def get_host_by_hostname(self, hostname, collector): logging.debug("No host match found") return None else: - logging.debug("RPC call failed") + logging.debug("API call failed") logging.debug(hostlist_json) else: logging.debug("No collector specified") @@ -141,15 +142,15 @@ def get_host_by_displayname(self, displayname): logging.debug("Running LogicMonitor.get_host_by_displayname...") logging.debug("Looking for displayname " + displayname) - logging.debug("Making RPC call to 'getHost'") - host_json = (json.loads(self.rpc("getHost", + logging.debug("Making API call to 'getHost'") + host_json = (json.loads(self.api("getHost", {"displayName": displayname}))) if host_json["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return host_json["data"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") logging.debug(host_json) return None @@ -174,11 +175,11 @@ def get_group(self, fullpath): specified path""" logging.debug("Running LogicMonitor.get_group...") - logging.debug("Making RPC call to getHostGroups") - resp = json.loads(self.rpc("getHostGroups", {})) + logging.debug("Making API call to getHostGroups") + resp = json.loads(self.api("getHostGroups", {})) if resp["status"] == 200: - logging.debug("RPC called succeeded") + logging.debug("API called succeeded") groups = resp["data"] logging.debug("Looking for group matching " + fullpath) @@ -190,7 +191,7 @@ def get_group(self, fullpath): logging.debug("No group match found") return None else: - logging.debug("RPC call failed") + logging.debug("API call failed") logging.debug(resp) return None @@ -245,19 +246,19 @@ def create_group(self, fullpath): "alertEnable": True, "description": ""} - logging.debug("Making RPC call to 'addHostGroup'") + logging.debug("Making API call to 'addHostGroup'") resp = json.loads( - self.rpc("addHostGroup", h)) + self.api("addHostGroup", h)) if resp["status"] == 200: - logging.debug("RPC call succeeded") + logging.debug("API call succeeded") return resp["data"]["id"] elif resp["errmsg"] == "The record already exists": logging.debug("The hostgroup already exists") group = self.get_group(fullpath) return group["id"] else: - logging.debug("RPC call failed") + logging.debug("API call failed") self.fail( msg="Error: unable to create new hostgroup \"" + name + "\".\n" + resp["errmsg"]) From 559c6f08e44cdd78fa6ad9d8b4c99b443af1bbaa Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 13:28:15 -0700 Subject: [PATCH 03/17] Add rest wrapper --- logicmonitor_core/LogicMonitor.py | 95 ++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 0a5cafd..e23c999 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -1,9 +1,14 @@ #!/usr/bin/python +import base64 +import hashlib +import hmac import json import logging +import requests import socket import sys +import time import urllib import urllib2 @@ -16,8 +21,8 @@ def __init__(self, **params): self.check_mode = False self.company = params["company"] - self.user = params["user"] - self.password = params["password"] + self.access_id = params["access_id"] + self.access_key = params["access_key"] self.fqdn = socket.getfqdn() self.lm_url = "logicmonitor.com/santaba" self.urlopen = urllib2.urlopen @@ -77,7 +82,8 @@ def do(self, action, params): try: # log param string without credentials logging.debug("Attempting to open URL: " + - "https://" + self.company + "." + self.lm_url + + "https://" + self.company + "." + + self.lm_url + "/do/" + action + "?" + param_str) f = self.urlopen( "https://" + self.company + "." + self.lm_url + @@ -87,6 +93,89 @@ def do(self, action, params): logging.debug("Error opening URL. " + ioe) self.fail("Unknown exception opening URL") + def rest(self, method, path, data=None): + '''Make a call to the LogicMonitor server REST API''' + logging.debug("Running LogicMonitor.rest...") + + if ((method == 'DELETE' or + method == 'PATCH' or + method == 'POST') and + data is None): + # TODO better error message + self.fail("No payload specified") + try: + url = ('https://' + self.company + '.' + + self.lm_url + '/rest/' + path) + + logging.debug("Sending " + method + " to: " + url) + auth_header = self.get_auth_header( + method, path, data) + headers = {'Content-Type': 'application/json', + 'Authorization': auth_header} + + resp = None + if method == 'DELETE': + resp = requests.delete(url, headers=headers) + elif method == 'GET': + resp = requests.get(url, headers=headers) + elif method == 'PATCH': + resp = requests.patch(url, + data=data, + headers=headers) + elif method == 'POST': + resp = requests.post(url, + data=data, + headers=headers) + elif method == 'PUT': + resp = requests.put(url, + data=data, + headers=headers) + + if resp.status_code != '200': + self.fail('HTTP response ' + resp.status_code + + ' from API while making ' + method + + ' request to ' + url) + else: + self.debug('Successful API call to ' + url) + return resp + except Exception as e: + self.fail('Unknown error making API request: ' + + e.message) + + def get_auth_header(self, method, path, data): + '''Construct an REST API authentication header''' + logging.debug("Running LogicMonitor.get_auth_header...") + + if self.access_key is None or self.access_id is None: + self.fail('Must specify Access Key and ' + + 'Access ID for authenticating') + + epoch = str(int(time.time() * 1000)) + + # concatenate request details + msg = '' + if data is None: + msg = method + epoch + path + else: + msg = method + epoch + data + path + + # construct signature + signature = base64.b64encode( + hmac.new( + self.access_key, + msg=msg, + digestmod=hashlib.sha256 + ).hexdigest() + ) + + # construct header + auth = ('LMv1 ' + + self.access_id + ':' + + signature + ':' + + epoch) + + return auth + def get_collectors(self): """Returns a JSON object containing a list of LogicMonitor collectors""" From 22b63b34c709e3fa700d6a81742dae3c733705ef Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 13:48:27 -0700 Subject: [PATCH 04/17] Update access variable names --- logicmonitor_core/LogicMonitor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index e23c999..93150ed 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -21,8 +21,8 @@ def __init__(self, **params): self.check_mode = False self.company = params["company"] - self.access_id = params["access_id"] - self.access_key = params["access_key"] + self.accessid = params['accessid'] + self.accesskey = params['accesskey'] self.fqdn = socket.getfqdn() self.lm_url = "logicmonitor.com/santaba" self.urlopen = urllib2.urlopen @@ -146,7 +146,7 @@ def get_auth_header(self, method, path, data): '''Construct an REST API authentication header''' logging.debug("Running LogicMonitor.get_auth_header...") - if self.access_key is None or self.access_id is None: + if self.accesskey is None or self.accessid is None: self.fail('Must specify Access Key and ' + 'Access ID for authenticating') @@ -162,7 +162,7 @@ def get_auth_header(self, method, path, data): # construct signature signature = base64.b64encode( hmac.new( - self.access_key, + self.accesskey, msg=msg, digestmod=hashlib.sha256 ).hexdigest() @@ -170,7 +170,7 @@ def get_auth_header(self, method, path, data): # construct header auth = ('LMv1 ' + - self.access_id + ':' + + self.accessid + ':' + signature + ':' + epoch) From 99e2a302ef08f9738a71faca337fbdf562f771fa Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 14:47:58 -0700 Subject: [PATCH 05/17] Tweaks and fixes --- logicmonitor_core/LogicMonitor.py | 33 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 93150ed..7bbaea0 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -93,7 +93,7 @@ def do(self, action, params): logging.debug("Error opening URL. " + ioe) self.fail("Unknown exception opening URL") - def rest(self, method, path, data=None): + def rest(self, path, method, data=None): '''Make a call to the LogicMonitor server REST API''' logging.debug("Running LogicMonitor.rest...") @@ -105,15 +105,15 @@ def rest(self, method, path, data=None): self.fail("No payload specified") try: url = ('https://' + self.company + '.' + - self.lm_url + '/rest/' + path) + self.lm_url + '/rest' + path) logging.debug("Sending " + method + " to: " + url) auth_header = self.get_auth_header( - method, path, data) + path, method, data) headers = {'Content-Type': 'application/json', 'Authorization': auth_header} - resp = None + resp = '' if method == 'DELETE': resp = requests.delete(url, headers=headers) elif method == 'GET': @@ -130,19 +130,23 @@ def rest(self, method, path, data=None): resp = requests.put(url, data=data, headers=headers) + else: + self.fail('Invalid method ' + + method + 'specified') - if resp.status_code != '200': - self.fail('HTTP response ' + resp.status_code + + if resp.status_code != 200: + self.fail('HTTP response ' + + str(resp.status_code) + ' from API while making ' + method + ' request to ' + url) else: - self.debug('Successful API call to ' + url) - return resp + logging.debug('Successful API call to ' + url) + return resp except Exception as e: self.fail('Unknown error making API request: ' + e.message) - def get_auth_header(self, method, path, data): + def get_auth_header(self, path, method, data): '''Construct an REST API authentication header''' logging.debug("Running LogicMonitor.get_auth_header...") @@ -160,14 +164,11 @@ def get_auth_header(self, method, path, data): msg = method + epoch + data + path # construct signature - signature = base64.b64encode( - hmac.new( - self.accesskey, - msg=msg, - digestmod=hashlib.sha256 - ).hexdigest() - ) + digest = hmac.new(self.accesskey, + msg=msg, + digestmod=hashlib.sha256).hexdigest() + signature = base64.b64encode(digest) # construct header auth = ('LMv1 ' + self.accessid + ':' + From e23dcfa93eb6bbb5e2da5d02b8690821651a4792 Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 15:04:19 -0700 Subject: [PATCH 06/17] Error message updates and quote fixes --- logicmonitor_core/LogicMonitor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 7bbaea0..06d096a 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -101,13 +101,12 @@ def rest(self, path, method, data=None): method == 'PATCH' or method == 'POST') and data is None): - # TODO better error message - self.fail("No payload specified") + self.fail('Message body required for method ' + method) try: url = ('https://' + self.company + '.' + self.lm_url + '/rest' + path) - logging.debug("Sending " + method + " to: " + url) + logging.debug('Sending ' + method + ' to: ' + url) auth_header = self.get_auth_header( path, method, data) headers = {'Content-Type': 'application/json', @@ -143,8 +142,7 @@ def rest(self, path, method, data=None): logging.debug('Successful API call to ' + url) return resp except Exception as e: - self.fail('Unknown error making API request: ' + - e.message) + self.fail('Error making API request: ' + e.message) def get_auth_header(self, path, method, data): '''Construct an REST API authentication header''' From f846510a423d708a05d76a5917b13e7872a909b0 Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 16:13:00 -0700 Subject: [PATCH 07/17] Don't exit on rest failure --- logicmonitor_core/LogicMonitor.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 06d096a..4f35425 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -134,15 +134,18 @@ def rest(self, path, method, data=None): method + 'specified') if resp.status_code != 200: - self.fail('HTTP response ' + - str(resp.status_code) + - ' from API while making ' + method + - ' request to ' + url) + logging.error('HTTP response ' + + str(resp.status_code) + + ' from API while making ' + + method + + ' request to ' + url) else: logging.debug('Successful API call to ' + url) return resp except Exception as e: - self.fail('Error making API request: ' + e.message) + logging.error('Error making API request: ' + + e.message) + return None def get_auth_header(self, path, method, data): '''Construct an REST API authentication header''' From cd883945c104bc79bc5213542dfe318286d94c45 Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 16:41:02 -0700 Subject: [PATCH 08/17] Update api return --- logicmonitor_core/LogicMonitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 4f35425..50589a6 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -141,7 +141,7 @@ def rest(self, path, method, data=None): ' request to ' + url) else: logging.debug('Successful API call to ' + url) - return resp + return resp except Exception as e: logging.error('Error making API request: ' + e.message) From a40e955cca33503e08460969c3ef354ca5a5090e Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 16:42:32 -0700 Subject: [PATCH 09/17] Revert "Update api return" This reverts commit cd883945c104bc79bc5213542dfe318286d94c45. --- logicmonitor_core/LogicMonitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 50589a6..4f35425 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -141,7 +141,7 @@ def rest(self, path, method, data=None): ' request to ' + url) else: logging.debug('Successful API call to ' + url) - return resp + return resp except Exception as e: logging.error('Error making API request: ' + e.message) From 0fa93c4ded44228c930272dab08429d3b4006bf9 Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 17:11:39 -0700 Subject: [PATCH 10/17] Only fail on patch message body --- logicmonitor_core/LogicMonitor.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 4f35425..69ad74d 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -97,10 +97,7 @@ def rest(self, path, method, data=None): '''Make a call to the LogicMonitor server REST API''' logging.debug("Running LogicMonitor.rest...") - if ((method == 'DELETE' or - method == 'PATCH' or - method == 'POST') and - data is None): + if method == 'PATCH' and data is None: self.fail('Message body required for method ' + method) try: url = ('https://' + self.company + '.' + From 244dfc5cd98dd19caa86cd66699c1356aa821fa4 Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 17:13:51 -0700 Subject: [PATCH 11/17] Revert "Only fail on patch message body" This reverts commit 0fa93c4ded44228c930272dab08429d3b4006bf9. --- logicmonitor_core/LogicMonitor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 69ad74d..4f35425 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -97,7 +97,10 @@ def rest(self, path, method, data=None): '''Make a call to the LogicMonitor server REST API''' logging.debug("Running LogicMonitor.rest...") - if method == 'PATCH' and data is None: + if ((method == 'DELETE' or + method == 'PATCH' or + method == 'POST') and + data is None): self.fail('Message body required for method ' + method) try: url = ('https://' + self.company + '.' + From 811fd34bf24274dfd77cbf73f402980cab544b3e Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 17:14:47 -0700 Subject: [PATCH 12/17] Don't fail on message body for DELETE --- logicmonitor_core/LogicMonitor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 4f35425..7ceede9 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -97,8 +97,7 @@ def rest(self, path, method, data=None): '''Make a call to the LogicMonitor server REST API''' logging.debug("Running LogicMonitor.rest...") - if ((method == 'DELETE' or - method == 'PATCH' or + if ((method == 'PATCH' or method == 'POST') and data is None): self.fail('Message body required for method ' + method) From 02b38e8f25eb3503bad26de416c750800f82a8f1 Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Thu, 6 Oct 2016 17:18:24 -0700 Subject: [PATCH 13/17] Convert data to json in rest wrapper --- logicmonitor_core/LogicMonitor.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 7ceede9..712be1f 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -100,7 +100,12 @@ def rest(self, path, method, data=None): if ((method == 'PATCH' or method == 'POST') and data is None): - self.fail('Message body required for method ' + method) + self.fail('Message body required for method ' + + method) + + # convert data to json + if data is not None: + data = json.dumps(data) try: url = ('https://' + self.company + '.' + self.lm_url + '/rest' + path) From 1ff77990228cfd7472757ebb8c0876646729d9c4 Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Fri, 7 Oct 2016 12:09:29 -0700 Subject: [PATCH 14/17] Update log order --- logicmonitor_core/LogicMonitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 712be1f..356a436 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -110,12 +110,12 @@ def rest(self, path, method, data=None): url = ('https://' + self.company + '.' + self.lm_url + '/rest' + path) - logging.debug('Sending ' + method + ' to: ' + url) auth_header = self.get_auth_header( path, method, data) headers = {'Content-Type': 'application/json', 'Authorization': auth_header} + logging.debug('Sending ' + method + ' to: ' + url) resp = '' if method == 'DELETE': resp = requests.delete(url, headers=headers) From 153f0891a1d969d31275473013e8617658881753 Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Fri, 7 Oct 2016 14:43:25 -0700 Subject: [PATCH 15/17] Wrap lines to 80 instead of 64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stupid editor settings… --- logicmonitor_core/LogicMonitor.py | 35 ++++++++++--------------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 356a436..a1cf1ea 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -110,8 +110,7 @@ def rest(self, path, method, data=None): url = ('https://' + self.company + '.' + self.lm_url + '/rest' + path) - auth_header = self.get_auth_header( - path, method, data) + auth_header = self.get_auth_header(path, method, data) headers = {'Content-Type': 'application/json', 'Authorization': auth_header} @@ -122,26 +121,17 @@ def rest(self, path, method, data=None): elif method == 'GET': resp = requests.get(url, headers=headers) elif method == 'PATCH': - resp = requests.patch(url, - data=data, - headers=headers) + resp = requests.patch(url, data=data, headers=headers) elif method == 'POST': - resp = requests.post(url, - data=data, - headers=headers) + resp = requests.post(url, data=data, headers=headers) elif method == 'PUT': - resp = requests.put(url, - data=data, - headers=headers) + resp = requests.put(url, data=data, headers=headers) else: - self.fail('Invalid method ' + - method + 'specified') + self.fail('Invalid method ' + method + 'specified') if resp.status_code != 200: - logging.error('HTTP response ' + - str(resp.status_code) + - ' from API while making ' + - method + + logging.error('HTTP response ' + str(resp.status_code) + + ' from API while making ' + method + ' request to ' + url) else: logging.debug('Successful API call to ' + url) @@ -156,8 +146,8 @@ def get_auth_header(self, path, method, data): logging.debug("Running LogicMonitor.get_auth_header...") if self.accesskey is None or self.accessid is None: - self.fail('Must specify Access Key and ' + - 'Access ID for authenticating') + self.fail('Must specify Access Key and Access ID for ' + + 'authenticating') epoch = str(int(time.time() * 1000)) @@ -172,13 +162,10 @@ def get_auth_header(self, path, method, data): digest = hmac.new(self.accesskey, msg=msg, digestmod=hashlib.sha256).hexdigest() - signature = base64.b64encode(digest) + # construct header - auth = ('LMv1 ' + - self.accessid + ':' + - signature + ':' + - epoch) + auth = ('LMv1 ' + self.accessid + ':' + signature + ':' + epoch) return auth From c04a5b7f7bfa0dfc20d22441236b37ddc137a6ec Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Fri, 7 Oct 2016 14:52:55 -0700 Subject: [PATCH 16/17] More wrap fixes --- logicmonitor_core/LogicMonitor.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index a1cf1ea..194b23d 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -97,18 +97,14 @@ def rest(self, path, method, data=None): '''Make a call to the LogicMonitor server REST API''' logging.debug("Running LogicMonitor.rest...") - if ((method == 'PATCH' or - method == 'POST') and - data is None): - self.fail('Message body required for method ' + - method) + if method == 'PATCH' or method == 'POST' and data is None: + self.fail('Message body required for method ' + method) # convert data to json if data is not None: data = json.dumps(data) try: - url = ('https://' + self.company + '.' + - self.lm_url + '/rest' + path) + url = 'https://' + self.company + '.' + self.lm_url + '/rest' + path auth_header = self.get_auth_header(path, method, data) headers = {'Content-Type': 'application/json', @@ -137,8 +133,7 @@ def rest(self, path, method, data=None): logging.debug('Successful API call to ' + url) return resp except Exception as e: - logging.error('Error making API request: ' - + e.message) + logging.error('Error making API request: ' + e.message) return None def get_auth_header(self, path, method, data): From 8838e0437d2dbee857b7d8f9ff8a6939f42f88ea Mon Sep 17 00:00:00 2001 From: Jeff Wozniak Date: Fri, 7 Oct 2016 16:32:13 -0700 Subject: [PATCH 17/17] Wrap line --- logicmonitor_core/LogicMonitor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logicmonitor_core/LogicMonitor.py b/logicmonitor_core/LogicMonitor.py index 194b23d..04ba656 100755 --- a/logicmonitor_core/LogicMonitor.py +++ b/logicmonitor_core/LogicMonitor.py @@ -104,7 +104,8 @@ def rest(self, path, method, data=None): if data is not None: data = json.dumps(data) try: - url = 'https://' + self.company + '.' + self.lm_url + '/rest' + path + url = 'https://' + self.company + '.' + self.lm_url + ' + /rest' + path auth_header = self.get_auth_header(path, method, data) headers = {'Content-Type': 'application/json',