As mentioned in my previous post, I have been doing some work with AirWatch’s REST API & at the same time I have been learning Python.
This post is to show how you can access AirWatch’s REST API with Python to return devices information, as well as serve as a template for the scripts I use to access AirWatch’s REST API.
Contents
Requirements
Before we get started there are somethings that we need to setup first.
Authentication
Let’s start with setting up the authentication, for this you’ll need the relevent REST API documentation for your AirWatch console version from ask.air-watch.com.
For my calls I’m using “Basic Authentication”, this requires:
- Username & Password of an admin account created on the AirWatch console with API access.
- An API Key for the calls.
“Basic Authentication” may not be enabled on your AirWatch console, to enable go the to root Organisation Group then: Groups > Groups & Settings > System > Advanced > API > REST > Authentication
The API Key is generated via going to Groups > Groups & Settings > System > Advanced > API > REST > General.
There you can tick “Enable API Access” & add a service. Entering a service name will generate an API Key, which we’ll need for API calls.
NOTE: This was called "Tenant Code" or "aw-tenant-code" previously & in the current (8.2) API documentation & will be referred as such within this post.
Base64
One thing that AirWatch’s documentation seems to skip over is that they want you to base64 encode the username & password for API calls.
Luckily my mate, Francois Levaux-Tiffreau, covered this on his blog here & I have some other examples below:
- First you’ll need to load you OS’s Python interpreter, on OS X this is as simple as typing the word python in a terminal window.
- Next, import the base64 module.
- To encode the string you’ll need to make pass the Username & Password of the admin account in the following form of username:password.
Below is an example of the above (including decoding), using a username & password of “mohan”;
Why Python?
I started a little project that involves a lot of reading of plists, these are XML files & as such the data is structured in the same way as REST API data, after discussing this people advise me to look into using Python for that project.
With some help from the #python channel in the MacAdmins.org Slack I realised just how easy it is with Python to read & iterate over structured data.
In this posts there are also various modules that are built into Python that greatly simplify the whole process, solidifying my decision to use Python for REST API scripting.
Requests Library
Currently I have 3 systems with REST API’s that I wish to script, AirWatch, Box & JSS, as such I wanted to try & standardise my approach across all 3 as much as possible & not use any bespoke libraries.
Whilst Python has many modules built in that greatly assisted me, when it came to REST API scripting the external library “Requests” was recommended & it’s awesome.
As this is an external library, (at least on OS X which is the OS I’m running my API scripts on), we need to run a couple of commands to install the Requests Library on OS X.
These are show below:
sudo easy_install pip
sudo pip install requests
With the Requests Library installed, we can then import it within scripts or the Python interpreter using the “import” command.
The below are quotes taken from the “Requests” site that give a short insight into the library:
Requests is an Apache2 Licensed HTTP library, written in Python, for human beings.
Requests takes all of the work out of Python HTTP/1.1 — making your integration with web services seamless. There’s no need to manually add query strings to your URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, powered by urllib3, which is embedded within Requests.
A folder
One last thing we need is a folder to write logs to &, if wanted, to hold the script.
The account running the script needs to have RW access to the folder.
This is a simple thing, but mentioned to avoid any tripping up.
As mentioned, I’m running these scripts on OS X & have multiple running. So I like one folder per REST API I’m scripting against.
Almost there!
Before we plough into the script, I wanted to break it down some to help make sense of some of it.
Variables
The script declares several variables within it some are generated within the script & others are for you to supply, the below is some detail as to what they do.
For each variable that needs supplying, please enter the relevant values in the ‘ ‘ of the variable.
scriptName
GENERATED: This variable gets the name of the script, as I’m running multiple scripts against the REST API it’s handy to have the logs & notifications for them to be separate & noted as to where they come from.
consoleURL
SUPPLY: The URL of your AirWatch console, with no trailing forward slash.
b64EncodedAuth
SUPPLY: The username & password for the account used to access the REST API, encoded in base64 as show above.
tenantCode
SUPPLY: The API Key as mentioned above.
lookupLimit
SUPPLY: Each REST API will only return a limited set of values, this is often 500. You can override this by supplying a value from 1 – 10000 often. In the script below I have set this to 10000. I personally do not have anywhere near 10000 devices, if you do & need more you may be able to up the limit or may need to call additional pages as with AirWatch’s REST API this is referred to as the page limit.
apiFolder
SUPPLY: The folder to write the log to.
logFileFullPath
GENERATED: This take the variable apiFolder & scriptName & generates a path to create a log. For example: /my/folder/my-script.py.log
mailServer
SUPPLY: Used by then sendEmail function (more on this below) to send an email with the contents of the log file
mailFrom
SUPPLY: The email address for the email to be sent from.
mailTo
SUPPLY: Email address to sent the email too.
logging.basicConfig
GENERATED: This configures the logging for the script, for now leave it as is. This will generate a log in /my/folder/my-script.py.log like the below:
2015-12-14 11:17:58,870 INFO -------- Getting All Device Information -------- 2015-12-14 11:17:58,884 INFO Starting new HTTPS connection (1): cnxx.airwatchportals.com 2015-12-14 11:18:06,692 DEBUG "GET /API/v1/mdm/devices/search?pagesize=10000 HTTP/1.1" 200 1155700 2015-12-14 11:18:12,810 INFO { device details for each device returned via the script } 2015-12-14 11:18:12,893 INFO -------- Retrieved information for %s devices --------
Line 1 of the above is written manually.
Line 2 is via the requests.get command
Line 3 is the result of the requests.get command, with the status of 200. Which means it worked!
Line 4 will return each devices details in JSON.
Line 5 is written manually, but %s is the number of devices that the script returns information for.
This is great for this example, but as you’ll see in future posts this may need to be tweaked to lower the verbosity.
As per the filemode, the log is overwritten with every run of the script.
Script Breakdown
Below is a breakdown of the script itself.
sendMail function
After the variable declaration we have the function called sendMail. This will email the contents of the log file to the supplied recipient (mailTo) via the supplied mail server (mailServer) you specify from the address you supply (mailFrom).
The Subject for the email is in the format of: <scriptname> : <statusMessage>
For example:
AW-API-Template.py: Retrieved information for 921 devices
or
AW-E-ToDelete.py: Get request failed with ('Connection aborted.', gaierror(8, 'nodename nor servname provided, or not known'))
If this is something you do not wish to do, you can comment out the sendEmail lines & the sendEmail function.
Once an email has been sent, the script exits.
Trying to make the API call
Next there is a try block where we try to make the API call.
awTest = requests.get
This uses the various variables to create a GET request for the call with the needed headers. The response comes back as json & we have a timeout set for 30 seconds.
The request we’re making is to get a list of all devices across our AirWatch instance.
awTest.raise_for_status()
Following the request we are raising an exception if the status code comes back as a 4xx, 5xx or if the request times out.
Errors would look like:
2015-12-05 11:00:06,759 -------- Getting All Device Information -------- 2015-12-05 11:00:24,285 Starting new HTTPS connection (1): cnxx.airwatchportals.com 2015-12-05 11:00:29,385 --------Get request failed with ('Connection aborted.', gaierror(8, 'nodename nor servname provided, or not known'))--------
Or:
2015-12-14 12:42:05,553 INFO -------- Getting All Device Information -------- 2015-12-14 12:42:05,562 INFO Starting new HTTPS connection (1): cn.airwatchportals.com 2015-12-14 12:42:05,563 ERROR -------- Get request failed with HTTPSConnectionPool(host='cn.airwatchportals.com', port=443): Max retries exceeded with url: /API/v1/mdm/devices/search?pagesize=10000 (Caused by NewConnectionError('<requests.packages.urllib3.connection.VerifiedHTTPSConnection object at 0x10d7c9810>: Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known',)) --------
deviceDetails = awTest.json()
Next we’re setting the deviceDetails variable to the son returned from GET request.
deviceDetails = deviceDetails[‘Devices’]
This will set the deviceDetails variable to only contain the data from within ‘Devices’ list within the json.
for device in deviceDetails:
For every device returned, log the devices details.
statusMessage
The last few lines will created a message showing how many devices we returned details for, this is written to the log & is also used by the sendMail function as the emails Subject.
This last statusMessage is only shown if we manage to make the GET request & iterate through the device list & will return the number of devices we have found.
If any exceptions are encountered, then their local statusMessage is set & sent to the sendMail function which exists the script once an email has been sent.
The Script
Below is the script itself, as mentioned this script is merely getting device details, & (with the usual caveats) is safe to run against your AirWatch instance.
One thought on “Accessing AirWatch’s REST API with Python”