Skip to content

Commit

Permalink
Merge pull request #27 from device42/D42-13677
Browse files Browse the repository at this point in the history
D42 13677 - Dell API v5 Support
  • Loading branch information
cscaglioned42 committed Dec 6, 2019
2 parents 11cf601 + 815c40e commit d581d96
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 117 deletions.
6 changes: 4 additions & 2 deletions Files/shared.py
Expand Up @@ -81,10 +81,12 @@ def __get_discover_cfg(self):
def __get_dell_cfg(self):
# Dell ---------------------------------------------
dell_url = self.cc.get('dell', 'url')
dell_api_key = self.cc.get('dell', 'api_key')
dell_client_id = self.cc.get('dell', 'client_id')
dell_client_secret = self.cc.get('dell', 'client_secret')
return {
'url': dell_url,
'api_key': dell_api_key
'client_id': dell_client_id,
'client_secret': dell_client_secret
}

def __get_hp_cfg(self):
Expand Down
5 changes: 3 additions & 2 deletions Files/warranty.cfg.example
Expand Up @@ -15,8 +15,9 @@ forcedupdate = False

[dell]
# set api_key as provided by Dell
api_key =
url = https://sandbox.api.dell.com/support/assetinfo/v4/getassetwarranty
client_id =
client_secret =
url = https://apigtwb2c.us.dell.com/PROD/sbil/eapi/v5/asset-entitlements

[hp]
# set api_key as provided by HP
Expand Down
261 changes: 151 additions & 110 deletions Files/warranty_dell.py
@@ -1,7 +1,9 @@
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
Expand All @@ -16,7 +18,8 @@ class Dell(WarrantyBase, object):
def __init__(self, params):
super(Dell, self).__init__()
self.url = params['url']
self.api_key = params['api_key']
self.client_id = params['client_id']
self.client_secret = params['client_secret']
self.debug = DEBUG
self.retry = RETRY
self.order_no = ORDER_NO_TYPE
Expand All @@ -26,6 +29,42 @@ def __init__(self, params):
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://apigtwb2c.us.dell.com/auth/oauth/v2/token"

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 = {}
Expand All @@ -51,10 +90,29 @@ def run_warranty_check(self, inline_serials, retry=True):
inline_serials.append(d42_serial)
inline_serials = ','.join(inline_serials)

payload = {'id': inline_serials, 'apikey': self.api_key, 'accept': 'Application/json'}
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

payload = {
'servicetags': inline_serials,
'Method': 'GET',
}

headers = {
'Accept': 'Application/json',
'Authorization': self.access_token
}

try:
resp = requests.get(self.url, params=payload, verify=True, timeout=timeout)
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
Expand All @@ -77,114 +135,97 @@ def process_result(self, result, purchases):
global full_serials
data = {}

if 'AssetWarrantyResponse' in result:
for item in result['AssetWarrantyResponse']:
try:
warranties = item['AssetEntitlementData']
asset = item['AssetHeaderData']
product = item['ProductHeaderData']
except IndexError:
if self.debug:
try:
msg = str(result['InvalidFormatAssets']['BadAssets'])
if msg:
print '\t\t[-] Error: Bad asset: %s' % msg
except Exception as e:
print e
for item in result:
try:
warranties = item['entitlements']
except IndexError:
if self.debug:
try:
msg = str(result['InvalidFormatAssets']['BadAssets'])
if msg:
print '\t\t[-] Error: Bad asset: %s' % msg
except Exception as e:
print e

else:
if self.order_no == 'common':
order_no = self.common
else:
if self.order_no == 'vendor':
order_no = asset['OrderNumber']
elif self.order_no == 'common':
order_no = self.common
else:
order_no = self.generate_random_order_no()

serial = asset['ServiceTag']
customernumber = asset['CustomerNumber']
country = asset['CountryLookupCode']

'''
For future implementation of registering the purchase date as a lifecycle event
Add a lifecycle event for the system
data.update({'date':ship_date})
data.update({'type':'Purchased'})
data.update({'serial_no':serial})
d42.upload_lifecycle(data)
data.clear()
'''
order_no = self.generate_random_order_no()

# We need check per warranty service item
for sub_item in warranties:
serial = item['serviceTag']

# We need check per warranty service item
for sub_item in warranties:
data.clear()
ship_date = item['shipDate'].split('T')[0]
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.'})
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'})

line_contract_id = sub_item['itemNumber']
data.update({'line_notes': line_contract_id})
data.update({'line_contract_id': line_contract_id})

# Using notes as well as the Device42 API doesn't give back the line_contract_id,
# so notes is now used for identification
# Mention this to device42

service_level_group = sub_item['serviceLevelGroup']
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

try:
# There's a max 64 character limit on the line service type field in Device42 (version 13.1.0)
service_level_description = left(sub_item['serviceLevelDescription'], 64)
data.update({'line_service_type': service_level_description})
except KeyError:
pass

start_date = sub_item['startDate'].split('T')[0]
end_date = sub_item['endDate'].split('T')[0]

data.update({'line_start_date': start_date})
data.update({'line_end_date': end_date})

# 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]

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:
self.d42_rest.upload_data(data)
data.clear()
ship_date = asset['ShipDate'].split('T')[0]
try:
product_id = product['ProductId']
except:
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.'})
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'})

line_contract_id = sub_item['ItemNumber']
data.update({'line_notes': line_contract_id})
data.update({'line_contract_id': line_contract_id})

# Using notes as well as the Device42 API doesn't give back the line_contract_id,
# so notes is now used for identification
# Mention this to device42

service_level_group = sub_item['ServiceLevelGroup']
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

try:
# There's a max 64 character limit on the line service type field in Device42 (version 13.1.0)
service_level_description = left(sub_item['ServiceLevelDescription'], 64)
data.update({'line_service_type': service_level_description})
except:
pass

start_date = sub_item['StartDate'].split('T')[0]
end_date = sub_item['EndDate'].split('T')[0]

data.update({'line_start_date': start_date})
data.update({'line_end_date': end_date})

# 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]

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:
self.d42_rest.upload_data(data)
data.clear()
7 changes: 5 additions & 2 deletions README.md
Expand Up @@ -10,7 +10,7 @@ This script checks warranty status for Dell, HP, IBM, Lenovo and Meraki manufact
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.
- Dell's API key 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/
- 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
## Plans
Expand Down Expand Up @@ -42,4 +42,7 @@ In order for this script to check warranty status of the device, the device must
## Compatibility
* requests module required
* Script runs on Linux and Windows
* Python 2.7
* Python 2.7

## Updates
10/10/19 - Updated Dell warranty sync to use version 5 of their API (OAuth2.0), Version 4 EOL is scheduled for 12/15/19, Please update before this date
3 changes: 2 additions & 1 deletion starter.py
Expand Up @@ -32,7 +32,8 @@ def get_vendor_api(name):
if vendor == 'dell':
dell_params = {
'url': current_cfg['url'],
'api_key': current_cfg['api_key'],
'client_id': current_cfg['client_id'],
'client_secret': current_cfg['client_secret'],
'd42_rest': d42_rest
}
api = Dell(dell_params)
Expand Down

0 comments on commit d581d96

Please sign in to comment.