From 7a9b85c45072c30ae07e909894938debd1fd7f4e Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Fri, 11 Oct 2019 10:45:19 -0400 Subject: [PATCH 01/11] started Cisco support, added Cisco settings to config file. --- Files/shared.py | 11 ++++ Files/warranty.cfg.example | 6 ++ Files/warranty_cisco.py | 118 +++++++++++++++++++++++++++++++++++++ starter.py | 15 ++++- 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 Files/warranty_cisco.py diff --git a/Files/shared.py b/Files/shared.py index 5bb397f..f33fe0a 100644 --- a/Files/shared.py +++ b/Files/shared.py @@ -78,6 +78,17 @@ def __get_discover_cfg(self): 'forcedupdate': forcedupdate } + def __get_cisco_cfg(self): + # Cisco --------------------------------------------- + cisco_url = self.cc.get('cisco', 'url') + cisco_client_id = self.cc.get('cisco', 'client_id') + cisco_client_secret = self.cc.get('cisco', 'client_secret') + return { + 'url': cisco_url, + 'client_id': cisco_client_id, + 'client_secret': cisco_client_secret + } + def __get_dell_cfg(self): # Dell --------------------------------------------- dell_url = self.cc.get('dell', 'url') diff --git a/Files/warranty.cfg.example b/Files/warranty.cfg.example index b4b7f10..78f35ce 100644 --- a/Files/warranty.cfg.example +++ b/Files/warranty.cfg.example @@ -13,6 +13,12 @@ lenovo = True meraki = True forcedupdate = False +[cisco] +# set the client_id and client_secret provided by Cisco +client_id = +client_secret = +url = + [dell] # set api_key as provided by Dell client_id = diff --git a/Files/warranty_cisco.py b/Files/warranty_cisco.py new file mode 100644 index 0000000..4ee155d --- /dev/null +++ b/Files/warranty_cisco.py @@ -0,0 +1,118 @@ +import json +import sys +import time +import random +import requests +from datetime import datetime, timedelta + +from shared import DEBUG, RETRY, ORDER_NO_TYPE, left +from warranty_abstract import WarrantyBase + +try: + requests.packages.urllib3.disable_warnings() +except: + pass + +#https://cloudsso.cisco.com/as/token.oauth2 + + +class Cisco(WarrantyBase, object): + + def __init__(self, params): + super(Cisco, self).__init__() + self.url = params['url'] + self.client_id = params['client_id'] + self.client_secret = params['client_secret'] + self.debug = DEBUG + self.retry = RETRY + self.order_no = ORDER_NO_TYPE + self.d42_rest = params['d42_rest'] + self.common = None + + if self.order_no == 'common': + self.common = self.generate_random_order_no() + + # OAuth 2.0 + self.expires_at = None + self.access_token = None + + # OAth 2.0 + + def get_access_token(self, client_id, client_secret): + access_token_request_url = "https://cloudsso.cisco.com/as/token.oauth2" + + timeout = 10 + + payload = { + 'client_id': client_id, + 'client_secret': client_secret, + 'grant_type': 'client_credentials' + } + + try: + resp = requests.post(access_token_request_url, data=payload, timeout=timeout) + + msg = 'Status code: %s' % str(resp.status_code) + + if str(resp.status_code) == '400' or str(resp.status_code) == '401' or str(resp.status_code) == '404': + print 'HTTP error. Message was: %s' % msg + elif str(resp.status_code) == '500': + print 'HTTP error. Message was: %s' % msg + print 'token access services may be down, try again later...' + print resp.text + else: + # assign access token and expiration to instance variables + result = resp.json() + self.access_token = "Bearer " + str(result['access_token']) + self.expires_at = datetime.utcnow() + timedelta(seconds=int(result['expires_in'])) + if self.debug > 1: + print "Request Token Acquired" + except requests.RequestException as e: + self.error_msg(e) + + def run_warranty_check(self, inline_serials, retry=True): + global full_serials + full_serials = {} + + if self.debug: + print '\t[+] Checking warranty info for "%s"' % inline_serials + timeout = 10 + + # making sure the warranty also gets updated if the serial has been changed by decom lifecycle process + incoming_serials = inline_serials.split(',') + inline_serials = [] + + for d42_serial in incoming_serials: + d42_serial = d42_serial.upper() + if '_' in d42_serial: + full_serials.update({d42_serial.split('_')[0]: d42_serial}) + d42_serial = d42_serial.split('_')[0] + elif '(' in d42_serial: + full_serials.update({d42_serial.split('(')[0]: d42_serial}) + d42_serial = d42_serial.split('(')[0] + else: + full_serials.update({d42_serial: d42_serial}) + inline_serials.append(d42_serial) + inline_serials = ','.join(inline_serials) + + # check to see if the access token is expired, if it is get a new one, else, continue + if self.expires_at is None or self.expires_at is not None and self.expires_at <= datetime.utcnow(): + if self.debug > 1: + print 'attempting to acquire access_token' + + self.get_access_token(self.client_id, self.client_secret) + + if self.access_token is None: + if self.debug > 1: + print 'unable to acquire access_token' + return None + + # get the device information using the requested access token + + + + + def process_result(self, result, purchases): + pass + + diff --git a/starter.py b/starter.py index 23ac23c..f1068cf 100644 --- a/starter.py +++ b/starter.py @@ -2,11 +2,13 @@ import sys from Files.shared import Config, Device42rest +from Files.warranty_cisco import Cisco from Files.warranty_dell import Dell from Files.warranty_hp import Hp from Files.warranty_ibm_lenovo import IbmLenovo from Files.warranty_meraki import Meraki + def get_hardware_by_vendor(name): # Getting the hardware models, so we specifically target the manufacturer systems registered hardware_models = d42_rest.get_hardware_models() @@ -29,7 +31,16 @@ def get_vendor_api(name): current_cfg = cfg.get_config(name) api = None - if vendor == 'dell': + if vendor == 'cisco': + cisco_params = { + 'url': current_cfg['url'], + 'client_id': current_cfg['client_id'], + 'client_secret': current_cfg['client_secret'], + 'd42_rest': d42_rest + } + api = Cisco(cisco_params) + + elif vendor == 'dell': dell_params = { 'url': current_cfg['url'], 'client_id': current_cfg['client_id'], @@ -150,6 +161,8 @@ def loader(name, api, d42): purchases[hasher] = [purchase_id, order_no, line_no, contractid, start, end, discover['forcedupdate']] APPS_ROW = [] + if discover['cisco']: + APPS_ROW.append('cisco') if discover['dell']: APPS_ROW.append('dell') if discover['hp']: From 4ab7e37198c9d9244aeb0306c7aba8cba9aa1612 Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Fri, 11 Oct 2019 14:35:38 -0400 Subject: [PATCH 02/11] finished Cisco warranty sync, don't have a key to test --- Files/warranty.cfg.example | 2 +- Files/warranty_cisco.py | 117 +++++++++++++++++++++++++++++++++++-- README.md | 8 +-- 3 files changed, 115 insertions(+), 12 deletions(-) diff --git a/Files/warranty.cfg.example b/Files/warranty.cfg.example index 78f35ce..4dfe445 100644 --- a/Files/warranty.cfg.example +++ b/Files/warranty.cfg.example @@ -17,7 +17,7 @@ forcedupdate = False # set the client_id and client_secret provided by Cisco client_id = client_secret = -url = +url = https://api.cisco.com/sn2info/v2/coverage/summary/serial_numbers [dell] # set api_key as provided by Dell diff --git a/Files/warranty_cisco.py b/Files/warranty_cisco.py index 4ee155d..549a165 100644 --- a/Files/warranty_cisco.py +++ b/Files/warranty_cisco.py @@ -13,8 +13,6 @@ except: pass -#https://cloudsso.cisco.com/as/token.oauth2 - class Cisco(WarrantyBase, object): @@ -108,11 +106,118 @@ def run_warranty_check(self, inline_serials, retry=True): return None # get the device information using the requested access token + # maximum serials per request is 70 devices with a maximum length of 40 separated by commas + payload = { + 'sr_no': inline_serials + } + headers = { + 'Accept': 'Application/json', + 'Authorization': self.access_token + } - + # request serial number information + try: + resp = requests.get(self.url, params=payload, headers=headers, verify=True, timeout=timeout) + msg = 'Status code: %s' % str(resp.status_code) + if str(resp.status_code) == '401' or str(resp.status_code) == '404': + print '\t[!] HTTP error. Message was: %s' % msg + print '\t[!] waiting for 30 seconds to let the api server calm down' + # suspecting blockage due to to many api calls. Put in a pause of 30 seconds and go on + time.sleep(30) + if retry: + print '\n[!] Retry' + self.run_warranty_check(inline_serials, False) + else: + return None + else: + result = resp.json() + return result + except requests.RequestException as e: + self.error_msg(e) + return None def process_result(self, result, purchases): - pass - - + global full_serials + data = {} + + if 'serial_numbers' in result: + for device in result['serial_numbers']: + try: + if self.order_no == 'common': + order_no = self.common + else: + order_no = self.generate_random_order_no() + + serial = device['parent_sr_no'] + + if "orderable_pid_list" in device: + for orderable_item in device["orderable_pid_list"]: + data.clear() + + data.update({'order_no': order_no}) + data.update({'completed': 'yes'}) + + data.update({'vendor': 'Cisco'}) + data.update({'line_device_serial_nos': full_serials[serial]}) + data.update({'line_type': 'contract'}) + data.update({'line_item_type': 'device'}) + data.update({'line_completed': 'yes'}) + + try: + line_contract_id = orderable_item['service_contract_number'] + except KeyError: + line_contract_id = "Not Available" + + data.update({'line_notes': line_contract_id}) + data.update({'line_contract_id': line_contract_id}) + + try: + warranty_type = orderable_item['warranty_type'] + except KeyError: + warranty_type = "Service" + + data.update({'line_contract_type': warranty_type}) + + if warranty_type == 'Service': + # Skipping the services, only want the warranties + continue + + try: + # max 64 character limit on the line service type field in Device42 (version 13.1.0) + service_line_description = left(orderable_item['service_line_descr'], 64) + data.update({'line_service_type': service_line_description}) + except KeyError: + pass + + start_date = "2019-01-01" + end_date = orderable_item['warranty_end_date'] + data.update({'line_end_date': end_date}) + + # Compare warranty dates by serial, contract_id, start date and end date + # Start date is not provided in request response so the date is simply used for hasher + hasher = serial + line_contract_id + start_date + end_date + + try: + d_purchase_id, d_order_no, d_line_no, d_contractid, d_start, d_end, forcedupdate = \ + purchases[hasher] + + if forcedupdate: + data['purchase_id'] = d_purchase_id + data.pop('order_no') + raise KeyError + + # check for duplicate state + if d_contractid == line_contract_id and d_start == start_date and d_end == end_date: + print '\t[!] Duplicate found. Purchase ' \ + 'for SKU "%s" and "%s" with end date "%s" ' \ + 'order_id: %s and line_no: %s' % ( + serial, line_contract_id, end_date, d_purchase_id, d_line_no) + except KeyError: + raise KeyError + + except KeyError: + self.d42_rest.upload_data(data) + data.clear() + else: + print 'API result did not report devices.' diff --git a/README.md b/README.md index 93ca1a6..bc68a17 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ This script checks warranty status for Dell, HP, IBM, Lenovo and Meraki manufact ## Prerequisites In order for this script to check warranty status of the device, the device must have hardware model and serial number entered in Device42. Dell Warranty Status API key must be acquired as well. -- Device42 Hardware model must have "Dell", "Hewlett Packard", "IBM", "LENOVO" or "Meraki" in it's manufacturer data. -- Device42 Serial number must be set to "Dell", "Hewlett Packard", "IBM", "LENOVO" or "Meraki" device serial number. +- Device42 Hardware model must have "Cisco", "Dell", "Hewlett Packard", "IBM", "LENOVO" or "Meraki" in it's manufacturer data. +- Device42 Serial number must be set to "Cisco", "Dell", "Hewlett Packard", "IBM", "LENOVO" or "Meraki" device serial number. +- Cisco's client id and client secret can be obtained by completing their on-boarding form. Please follow the instructions from here: https://www.cisco.com/c/en/us/support/docs/services/sntc/onboarding_guide.html instructions for enabling the Cisco support APIs can be found here: https://apiconsole.cisco.com/documentation - Dell's client id and client secret can be obtained by filling the on-boarding form. New and existing API users will need to register an account with TechDirect. Please check: http://en.community.dell.com/dell-groups/supportapisgroup/ - HP's API key can be obtained by filling the on-boarding form. Please, follow the instructions from here: https://developers.hp.com/css-enroll - Merakis API key can be obtained by going to the organization > settings page on the Meraki dashboard. Ensure that the enable access to API checkbox is selected then go to your profile to generate the API key. Please check https://developer.cisco.com/meraki/api/#/rest/getting-started/what-can-the-api-be-used-for @@ -21,9 +22,6 @@ In order for this script to check warranty status of the device, the device must - IBM script points to warranty info not related to the SKU, serial given - If a Meraki product has a licence with a renewal required state, the expiration date will be set to the current date -## Change Log -- Please check `CHANGELOG.md` - ## Usage - Set required parameters in warranty.cfg file and run starter.py script: From c64338dce5a5501651f6148234692df8bbe8cb39 Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Mon, 14 Oct 2019 10:49:23 -0400 Subject: [PATCH 03/11] fixed some key errors present in cisco warranty sync and added missing config parameters for cisco --- Files/warranty.cfg.example | 11 +-- Files/warranty_cisco.py | 138 ++++++++++++++++++------------------- 2 files changed, 75 insertions(+), 74 deletions(-) diff --git a/Files/warranty.cfg.example b/Files/warranty.cfg.example index 4dfe445..b3fd986 100644 --- a/Files/warranty.cfg.example +++ b/Files/warranty.cfg.example @@ -6,11 +6,12 @@ url = [discover] # enable/disable discoveries -dell = True -hp = True -ibm = True -lenovo = True -meraki = True +cisco = True +dell = False +hp = False +ibm = False +lenovo = False +meraki = False forcedupdate = False [cisco] diff --git a/Files/warranty_cisco.py b/Files/warranty_cisco.py index 549a165..ab7be73 100644 --- a/Files/warranty_cisco.py +++ b/Files/warranty_cisco.py @@ -5,7 +5,7 @@ import requests from datetime import datetime, timedelta -from shared import DEBUG, RETRY, ORDER_NO_TYPE, left +from shared import DEBUG, RETRY, ORDER_NO_TYPE, left, Device42rest from warranty_abstract import WarrantyBase try: @@ -13,6 +13,9 @@ except: pass +full_serials = {'SAL09232Q0Z': 'SAL09232Q0Z', '32964768': '32964768', 'SWCAT1239A0CJ': 'SWCAT1239A0CJ', + 'FOC0903N5J9': 'FOC0903N5J9', 'INM07501EC3': 'INM07501EC3', } + class Cisco(WarrantyBase, object): @@ -34,8 +37,7 @@ def __init__(self, params): self.expires_at = None self.access_token = None - # OAth 2.0 - + # OAth 2.0 def get_access_token(self, client_id, client_secret): access_token_request_url = "https://cloudsso.cisco.com/as/token.oauth2" @@ -149,72 +151,70 @@ def process_result(self, result, purchases): else: order_no = self.generate_random_order_no() - serial = device['parent_sr_no'] - - if "orderable_pid_list" in device: - for orderable_item in device["orderable_pid_list"]: - data.clear() - - data.update({'order_no': order_no}) - data.update({'completed': 'yes'}) - - data.update({'vendor': 'Cisco'}) - data.update({'line_device_serial_nos': full_serials[serial]}) - data.update({'line_type': 'contract'}) - data.update({'line_item_type': 'device'}) - data.update({'line_completed': 'yes'}) - - try: - line_contract_id = orderable_item['service_contract_number'] - except KeyError: - line_contract_id = "Not Available" - - data.update({'line_notes': line_contract_id}) - data.update({'line_contract_id': line_contract_id}) - - try: - warranty_type = orderable_item['warranty_type'] - except KeyError: - warranty_type = "Service" - - data.update({'line_contract_type': warranty_type}) - - if warranty_type == 'Service': - # Skipping the services, only want the warranties - continue - - try: - # max 64 character limit on the line service type field in Device42 (version 13.1.0) - service_line_description = left(orderable_item['service_line_descr'], 64) - data.update({'line_service_type': service_line_description}) - except KeyError: - pass - - start_date = "2019-01-01" - end_date = orderable_item['warranty_end_date'] - data.update({'line_end_date': end_date}) - - # Compare warranty dates by serial, contract_id, start date and end date - # Start date is not provided in request response so the date is simply used for hasher - hasher = serial + line_contract_id + start_date + end_date - - try: - d_purchase_id, d_order_no, d_line_no, d_contractid, d_start, d_end, forcedupdate = \ - purchases[hasher] - - if forcedupdate: - data['purchase_id'] = d_purchase_id - data.pop('order_no') - raise KeyError - - # check for duplicate state - if d_contractid == line_contract_id and d_start == start_date and d_end == end_date: - print '\t[!] Duplicate found. Purchase ' \ - 'for SKU "%s" and "%s" with end date "%s" ' \ - 'order_id: %s and line_no: %s' % ( - serial, line_contract_id, end_date, d_purchase_id, d_line_no) - except KeyError: - raise KeyError + serial = device['sr_no'] + + data.clear() + + data.update({'order_no': order_no}) + data.update({'completed': 'yes'}) + + data.update({'vendor': 'Cisco'}) + data.update({'line_device_serial_nos': full_serials[serial]}) + data.update({'line_type': 'contract'}) + data.update({'line_item_type': 'device'}) + data.update({'line_completed': 'yes'}) + + try: + line_contract_id = device['service_contract_number'] + except KeyError: + line_contract_id = "Not Available" + + data.update({'line_notes': line_contract_id}) + data.update({'line_contract_id': line_contract_id}) + + try: + warranty_type = device['warranty_type'] + except KeyError: + warranty_type = "Service" + + data.update({'line_contract_type': warranty_type}) + + if warranty_type == 'Service': + # Skipping the services, only want the warranties + continue + + try: + # max 64 character limit on the line service type field in Device42 (version 13.1.0) + service_line_description = left(device['service_line_descr'], 64) + data.update({'line_service_type': service_line_description}) + except KeyError: + pass + + start_date = "2019-01-01" + end_date = device['warranty_end_date'] + data.update({'line_end_date': end_date}) + + # Compare warranty dates by serial, contract_id, start date and end date + # Start date is not provided in request response so the date is simply used for hasher + hasher = serial + line_contract_id + start_date + end_date + + try: + d_purchase_id, d_order_no, d_line_no, d_contractid, d_start, d_end, forcedupdate = \ + purchases[hasher] + + if forcedupdate: + data['purchase_id'] = d_purchase_id + data.pop('order_no') + raise KeyError + + # check for duplicate state + if d_contractid == line_contract_id and d_start == start_date and d_end == end_date: + print '\t[!] Duplicate found. Purchase ' \ + 'for SKU "%s" and "%s" with end date "%s" ' \ + 'order_id: %s and line_no: %s' % ( + serial, line_contract_id, end_date, d_purchase_id, d_line_no) + except KeyError: + raise KeyError except KeyError: self.d42_rest.upload_data(data) From 06ca542004cd3af6ac2337587595f457cb14fa31 Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Mon, 14 Oct 2019 10:52:10 -0400 Subject: [PATCH 04/11] removed test code --- Files/warranty_cisco.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Files/warranty_cisco.py b/Files/warranty_cisco.py index ab7be73..082b64c 100644 --- a/Files/warranty_cisco.py +++ b/Files/warranty_cisco.py @@ -5,7 +5,7 @@ import requests from datetime import datetime, timedelta -from shared import DEBUG, RETRY, ORDER_NO_TYPE, left, Device42rest +from shared import DEBUG, RETRY, ORDER_NO_TYPE, left from warranty_abstract import WarrantyBase try: @@ -13,9 +13,6 @@ except: pass -full_serials = {'SAL09232Q0Z': 'SAL09232Q0Z', '32964768': '32964768', 'SWCAT1239A0CJ': 'SWCAT1239A0CJ', - 'FOC0903N5J9': 'FOC0903N5J9', 'INM07501EC3': 'INM07501EC3', } - class Cisco(WarrantyBase, object): From 8736fb79f1b74173babb59fedbba868419a2387c Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Mon, 14 Oct 2019 11:13:07 -0400 Subject: [PATCH 05/11] added cisco to cfg reader --- Files/shared.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Files/shared.py b/Files/shared.py index f33fe0a..5f3fde7 100644 --- a/Files/shared.py +++ b/Files/shared.py @@ -34,6 +34,8 @@ def get_config(self, source): res = self.__get_d42_cfg() elif source == 'discover': res = self.__get_discover_cfg() + elif source == 'cisco': + res = self.__get_cisco_cfg() elif source == 'dell': res = self.__get_dell_cfg() elif source == 'hp': @@ -63,6 +65,7 @@ def __get_d42_cfg(self): def __get_discover_cfg(self): # Discover ----------------------------------------- + cisco = self.cc.getboolean('discover', 'cisco') dell = self.cc.getboolean('discover', 'dell') hp = self.cc.getboolean('discover', 'hp') ibm = self.cc.getboolean('discover', 'ibm') @@ -70,6 +73,7 @@ def __get_discover_cfg(self): meraki = self.cc.getboolean('discover', 'meraki') forcedupdate = self.cc.getboolean('discover', 'forcedupdate') return { + 'cisco': cisco, 'dell': dell, 'hp': hp, 'ibm': ibm, From 73fd2eee47d8e6e6e3294b2bd0ed5010c73deafa Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Fri, 6 Dec 2019 17:07:16 -0500 Subject: [PATCH 06/11] warranty line items will be added regardless of missing start and end dates. modified hasher so that devices in d42 with no start and end dates are still returned --- Files/warranty.cfg.example | 2 +- Files/warranty_cisco.py | 2 +- Files/warranty_dell.py | 43 ++++++++++++++++++++++++++++++-------- Files/warranty_meraki.py | 7 +++---- starter.py | 7 ++++--- 5 files changed, 43 insertions(+), 18 deletions(-) diff --git a/Files/warranty.cfg.example b/Files/warranty.cfg.example index b3fd986..6ecb74d 100644 --- a/Files/warranty.cfg.example +++ b/Files/warranty.cfg.example @@ -6,7 +6,7 @@ url = [discover] # enable/disable discoveries -cisco = True +cisco = False dell = False hp = False ibm = False diff --git a/Files/warranty_cisco.py b/Files/warranty_cisco.py index 082b64c..48662f9 100644 --- a/Files/warranty_cisco.py +++ b/Files/warranty_cisco.py @@ -187,7 +187,7 @@ def process_result(self, result, purchases): except KeyError: pass - start_date = "2019-01-01" + start_date = "0001-01-01" end_date = device['warranty_end_date'] data.update({'line_end_date': end_date}) diff --git a/Files/warranty_dell.py b/Files/warranty_dell.py index d6d268a..d9dab74 100644 --- a/Files/warranty_dell.py +++ b/Files/warranty_dell.py @@ -3,7 +3,8 @@ import time import random import requests -from datetime import datetime, timedelta +import datetime +from datetime import timedelta, datetime from shared import DEBUG, RETRY, ORDER_NO_TYPE, left from warranty_abstract import WarrantyBase @@ -158,15 +159,25 @@ def process_result(self, result, purchases): # We need check per warranty service item for sub_item in warranties: data.clear() - ship_date = item['shipDate'].split('T')[0] + + try: + if 'shipDate' in item: + ship_date = item['shipDate'].split('T')[0] + else: + ship_date = None + except AttributeError: + ship_date = None + try: product_id = item['ProductId'] except KeyError: product_id = 'notspecified' data.update({'order_no': order_no}) + if ship_date: data.update({'po_date': ship_date}) + data.update({'completed': 'yes'}) data.update({'vendor': 'Dell Inc.'}) @@ -176,6 +187,7 @@ def process_result(self, result, purchases): data.update({'line_completed': 'yes'}) line_contract_id = sub_item['itemNumber'] + data.update({'line_notes': line_contract_id}) data.update({'line_contract_id': line_contract_id}) @@ -183,16 +195,20 @@ def process_result(self, result, purchases): # so notes is now used for identification # Mention this to device42 - service_level_group = sub_item['serviceLevelGroup'] + if 'serviceLevelGroup' in sub_item: + service_level_group = sub_item['serviceLevelGroup'] + else: + service_level_group = None + if service_level_group == -1 or service_level_group == 5 or service_level_group == 8 or service_level_group == 99999: contract_type = 'Warranty' - elif service_level_group == 8 and 'compellent' in product_id: - contract_type = 'Service' elif service_level_group == 11 and 'compellent' in product_id: contract_type = 'Warranty' else: contract_type = 'Service' + data.update({'line_contract_type': contract_type}) + if contract_type == 'Service': # Skipping the services, only want the warranties continue @@ -204,14 +220,23 @@ def process_result(self, result, purchases): except KeyError: pass - start_date = sub_item['startDate'].split('T')[0] - end_date = sub_item['endDate'].split('T')[0] + # start date and end date may be missing from payload so it is only posted when it has a value + # otherwise it is given a max or min date value and only used for hashing + try: + start_date = sub_item['startDate'].split('T')[0] + data.update({'line_start_date': start_date}) + except AttributeError: + start_date = '0001-01-01' - data.update({'line_start_date': start_date}) - data.update({'line_end_date': end_date}) + try: + end_date = sub_item['endDate'].split('T')[0] + data.update({'line_end_date': end_date}) + except AttributeError: + end_date = '9999-12-31' # update or duplicate? Compare warranty dates by serial, contract_id, start date and end date hasher = serial + line_contract_id + start_date + end_date + try: d_purchase_id, d_order_no, d_line_no, d_contractid, d_start, d_end, forcedupdate = purchases[hasher] diff --git a/Files/warranty_meraki.py b/Files/warranty_meraki.py index d85121f..7f94e63 100644 --- a/Files/warranty_meraki.py +++ b/Files/warranty_meraki.py @@ -100,8 +100,7 @@ def process_result(self, result, purchases): all_data.append(copy.deepcopy(data)) - # Easter Egg: start date in hash is the year meraki was founded - hasher = serial_number + "2006-01-01" + license_expiration + hasher = serial_number + "0001-01-01" + license_expiration try: d_purchase_id, d_order_no, d_line_no, d_contractid, d_start, d_end, forcedupdate = purchases[hasher] @@ -112,7 +111,7 @@ def process_result(self, result, purchases): raise KeyError # check for duplicate state - if d_start == "2006-01-01" and d_end == license_expiration: + if d_start == "0001-01-01" and d_end == license_expiration: print '\t[!] Duplicate found. Purchase ' \ 'for SKU "%s" with end date "%s" ' \ 'is already uploaded' % (serial_number, license_expiration) @@ -206,7 +205,7 @@ def get_license_state_all_organizations(self, all_organization_ids, retry, timeo if result['expirationDate'] == "N/A": days_remaining = datetime.utcnow().strftime("%Y-%m-%d") else: - days_remaining = self.meraki_date_parser(result['expirationDate']) # TODO make sure this works + days_remaining = self.meraki_date_parser(result['expirationDate']) all_license_states[organization_id] = days_remaining diff --git a/starter.py b/starter.py index f1068cf..487726d 100644 --- a/starter.py +++ b/starter.py @@ -149,10 +149,11 @@ def loader(name, api, d42): line_no = line_item.get('line_no') devices = line_item.get('devices') contractid = line_item.get('line_notes') - start = line_item.get('line_start_date') - end = line_item.get('line_end_date') + # POs with no start and end dates will now be included and given a hasher key with date min and max + start = line_item.get('line_start_date') if line_item.get('line_start_date') is not None else '0001-01-01' + end = line_item.get('line_end_date') if line_item.get('line_end_date') is not None else '9999-12-31' - if start and end and devices: + if devices: for device in devices: if 'serial_no' in device: serial = device['serial_no'] From e22f994d12171c27c05ab0b2efc93d88db7f2236 Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Tue, 10 Dec 2019 14:15:13 -0500 Subject: [PATCH 07/11] removed service level group as identifier for warranty and service entitlements, now uses service level description. services are now included along side warranties. added changelog back to readme. enabled all warranty jobs in config; the default --- Files/warranty.cfg.example | 8 ++++---- Files/warranty_dell.py | 23 ++++++++--------------- README.md | 2 ++ 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Files/warranty.cfg.example b/Files/warranty.cfg.example index 6ecb74d..446e3d6 100644 --- a/Files/warranty.cfg.example +++ b/Files/warranty.cfg.example @@ -6,10 +6,10 @@ url = [discover] # enable/disable discoveries -cisco = False -dell = False -hp = False -ibm = False +cisco = True +dell = True +hp = True +ibm = True lenovo = False meraki = False forcedupdate = False diff --git a/Files/warranty_dell.py b/Files/warranty_dell.py index d9dab74..d558e9d 100644 --- a/Files/warranty_dell.py +++ b/Files/warranty_dell.py @@ -195,23 +195,16 @@ def process_result(self, result, purchases): # so notes is now used for identification # Mention this to device42 - if 'serviceLevelGroup' in sub_item: - service_level_group = sub_item['serviceLevelGroup'] - else: - service_level_group = None - - if service_level_group == -1 or service_level_group == 5 or service_level_group == 8 or service_level_group == 99999: - contract_type = 'Warranty' - elif service_level_group == 11 and 'compellent' in product_id: - contract_type = 'Warranty' - else: - contract_type = 'Service' + contract_type = 'Service' # default contract type - data.update({'line_contract_type': contract_type}) + if 'serviceLevelDescription' in sub_item: + if sub_item['serviceLevelDescription'] is not None: + if 'Parts' in sub_item['serviceLevelDescription'] or 'Onsite' in sub_item['serviceLevelDescription']: + contract_type = 'Warranty' + else: # no useful information, continue to next entitlement item + continue - if contract_type == 'Service': - # Skipping the services, only want the warranties - continue + data.update({'line_contract_type': contract_type}) try: # There's a max 64 character limit on the line service type field in Device42 (version 13.1.0) diff --git a/README.md b/README.md index bc68a17..bc157a8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ In order for this script to check warranty status of the device, the device must - IBM script points to warranty info not related to the SKU, serial given - If a Meraki product has a licence with a renewal required state, the expiration date will be set to the current date +## Change Log + ## Usage - Set required parameters in warranty.cfg file and run starter.py script: From ca960a3482245fc3ec822976f508906dcb89de86 Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Tue, 10 Dec 2019 14:18:49 -0500 Subject: [PATCH 08/11] missed some vendors in the config file, made sure all of them were set to true; default --- Files/warranty.cfg.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Files/warranty.cfg.example b/Files/warranty.cfg.example index 446e3d6..d3266f8 100644 --- a/Files/warranty.cfg.example +++ b/Files/warranty.cfg.example @@ -10,8 +10,8 @@ cisco = True dell = True hp = True ibm = True -lenovo = False -meraki = False +lenovo = True +meraki = True forcedupdate = False [cisco] From 0a6dc0b978bc33a68e01443b33f3c1f910976cb4 Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Tue, 10 Dec 2019 14:26:08 -0500 Subject: [PATCH 09/11] added conditional for entitlements that are missing service level description fields --- Files/warranty_dell.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Files/warranty_dell.py b/Files/warranty_dell.py index d558e9d..a54ed7a 100644 --- a/Files/warranty_dell.py +++ b/Files/warranty_dell.py @@ -201,8 +201,10 @@ def process_result(self, result, purchases): if sub_item['serviceLevelDescription'] is not None: if 'Parts' in sub_item['serviceLevelDescription'] or 'Onsite' in sub_item['serviceLevelDescription']: contract_type = 'Warranty' - else: # no useful information, continue to next entitlement item + else: # service level description is null, continue to next entitlement item continue + else: # service level description not listed in entitlement, continue to next item + continue data.update({'line_contract_type': contract_type}) From 59785ef672f4fcda9ac91714321c8486e23fe27c Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Fri, 13 Dec 2019 13:04:20 -0500 Subject: [PATCH 10/11] removed adding devices that do not have a start and end date for an entitlment, added log message that includes the SN and the service level description for the entitlement with missing dates --- Files/warranty_dell.py | 23 ++++++++--------------- starter.py | 6 +++--- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/Files/warranty_dell.py b/Files/warranty_dell.py index a54ed7a..db2192f 100644 --- a/Files/warranty_dell.py +++ b/Files/warranty_dell.py @@ -92,13 +92,13 @@ def run_warranty_check(self, inline_serials, retry=True): inline_serials = ','.join(inline_serials) if self.expires_at is None or self.expires_at is not None and self.expires_at <= datetime.utcnow(): - if self.debug > 1: + if self.debug: print 'attempting to acquire access_token' self.get_access_token(self.client_id, self.client_secret) if self.access_token is None: - if self.debug > 1: + if self.debug: print 'unable to acquire access_token' return None @@ -168,11 +168,6 @@ def process_result(self, result, purchases): except AttributeError: ship_date = None - try: - product_id = item['ProductId'] - except KeyError: - product_id = 'notspecified' - data.update({'order_no': order_no}) if ship_date: @@ -215,19 +210,17 @@ def process_result(self, result, purchases): except KeyError: pass - # start date and end date may be missing from payload so it is only posted when it has a value - # otherwise it is given a max or min date value and only used for hashing + # start date and end date may be missing from payload so it is only posted when both have values try: start_date = sub_item['startDate'].split('T')[0] - data.update({'line_start_date': start_date}) - except AttributeError: - start_date = '0001-01-01' - - try: end_date = sub_item['endDate'].split('T')[0] + data.update({'line_start_date': start_date}) data.update({'line_end_date': end_date}) except AttributeError: - end_date = '9999-12-31' + if self.debug: + print('[Alert]: SN:', serial, ': Missing start and end date for a listed entitlement') + print(left(sub_item['serviceLevelDescription'], 64)) + continue # update or duplicate? Compare warranty dates by serial, contract_id, start date and end date hasher = serial + line_contract_id + start_date + end_date diff --git a/starter.py b/starter.py index 487726d..4c3db19 100644 --- a/starter.py +++ b/starter.py @@ -150,10 +150,10 @@ def loader(name, api, d42): devices = line_item.get('devices') contractid = line_item.get('line_notes') # POs with no start and end dates will now be included and given a hasher key with date min and max - start = line_item.get('line_start_date') if line_item.get('line_start_date') is not None else '0001-01-01' - end = line_item.get('line_end_date') if line_item.get('line_end_date') is not None else '9999-12-31' + start = line_item.get('line_start_date') + end = line_item.get('line_end_date') - if devices: + if start and end and devices: for device in devices: if 'serial_no' in device: serial = device['serial_no'] From 69c132c63a0e15a38b5c9525fd4827597fe79367 Mon Sep 17 00:00:00 2001 From: Omar Sanchez Date: Thu, 19 Dec 2019 12:47:57 -0500 Subject: [PATCH 11/11] increased timeout --- Files/warranty_dell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Files/warranty_dell.py b/Files/warranty_dell.py index db2192f..de15fc4 100644 --- a/Files/warranty_dell.py +++ b/Files/warranty_dell.py @@ -38,7 +38,7 @@ def __init__(self, params): def get_access_token(self, client_id, client_secret): access_token_request_url = "https://apigtwb2c.us.dell.com/auth/oauth/v2/token" - timeout = 10 + timeout = 60 payload = { 'client_id': client_id, @@ -72,7 +72,7 @@ def run_warranty_check(self, inline_serials, retry=True): if self.debug: print '\t[+] Checking warranty info for "%s"' % inline_serials - timeout = 10 + timeout = 60 # making sure the warranty also gets updated if the serial has been changed by decom lifecycle process incoming_serials = inline_serials.split(',')