Fetching Data From the JSS using API calls & Python

Fetching Data From the JSS using API calls & Python

bb8_thumbsup

Like a loyal droid, your JSS wants to help!


JAMF Software’s Casper Suite provides an Application Programming Interface, or API, to communicate with the JAMF Software Server (JSS) database. This allows an enterprise to customize specific areas of the JSS as needed. This is the first in a series of articles that will discuss these APIs and walk through building Python scripts that take advantage of the data available in the JSS. Future articles will show how to upload data to the JSS, building Extension Attributes in the JSS to react to that data and more.

These articles are based on an internal inventory application that began as an experiment I described in a previous post: Using GIF images in Python GUI scripts. Since that post was written the experiment turned into a cross-platform, automated, text- and GUI-based application that talks to the JSS, campus LDAP as well as two MySQL databases. The core of the application is maintaining the User and Location area of the Inventory pane for each computer in the JSS.

Requesting information from the JSS


There are a number of ways to get computer info from the JSS. The computer’s hostname or JSS ID will work. I elected to use the UUID, or Universally Unique IDentifier because it’s a cross-platform solution. Here are examples for obtaining UUIDs on macOS and Windows:

Getting the Macintosh UUID
uuid_request_raw = subprocess.check_output(["system_profiler", "SPHardwareDataType"])
my_uuid = re.findall('Hardware UUID: (.*)', uuid_request_raw)[0]
print "uuid: %s" % my_uuid
Getting the Windows UUID
uuid_request_raw = subprocess.check_output("wmic CsProduct Get UUID")
uuid_request_raw = uuid_request_raw.split("\r\r\n")[1]
my_uuid = uuid_request_raw.split(" ")[0]

 Making the connection


Once you’ve selected a method of identifying the machine, you will then need to request the data from the JSS. This is done by submitting a specific URL to the JSS. Python has a number of modules available to perform this action. I’ve included examples using the included urllib2 and the very popular Requests module. If you choose the requests method, you will need to make sure the module is installed on any machine you attempt to use your script on.

The code breaks down to the following steps:

  1. Assembling the URL
  2. Submitting the URL
  3. Storing the reply
  4. Creating a JSON object to contain the organized contents of the reply
Assembling the URL
# JSS API credentials
UsernameVar = "your_api_user"
PasswordVar = "your_api_user_password"

# base URL of JSS
jss_url = 'https://your_api_server:8443/JSSResource'
computer_url = jss_url + '/computers/udid/' + local_uuid
urllib2:
request = urllib2.Request(computer_url)
request.add_header('Accept', 'application/json')
request.add_header('Authorization', 'Basic ' + base64.b64encode(UsernameVar + ':' + PasswordVar))

response = urllib2.urlopen(request)
print "Status code from request: %s\n" % response.code
response_json = json.loads(response.read())
requests:
response  = requests.get(computer_url, headers={'Accept': 'application/json'}, auth=(UsernameVar,PasswordVar))
print "Status code from request: %s" % response.status_code
response_json = json.loads(response.text)

 Handling the data


The JSS will reply to your request in a JSON-formatted message. The JavaScript Object Notation is a common, easy to manipulate, human readable data format. The message will be arranged in something like a tree, requiring you to navigate various branches (keys) to find the leaf (data) you require. Fortunately, the json module makes it very easy to find the information you’re looking for.

Once we’ve stored the reply in a json object, we can use the following command to print the entire object. The quantity of data isn’t something that would be useful to an end user, but you as the developer, will need to see it to familiarize yourself with the structure. A computer record from our JSS is nearly 5,000 lines.

print json.dumps(r_json, sort_keys=True, indent=4)

The following code snippet shows direct navigation and processing the extension attributes of a machine to find a specific value. Each extension attribute contains the JSS ID for the EA and the name of the EA, this example utilizes the name. Again, refer to the entire JSON object to build the specific path to the information you need.

General User and Location data
print "JSS assigned ID: %s" % r_json['computer']['general']['id']
print "Asset Tag: %s" % r_json['computer']['general']['asset_tag']
print "User name (uNID): %s" % r_json['computer']['location']['username']
print "Email Address: %s" % r_json['computer']['location']['email_address']
print "Real name: %s" % r_json['computer']['location']['real_name']
print "Phone number: %s" % r_json['computer']['location']['phone']
print "Location: %s" % r_json['computer']['location']['room']
Parsing the Extended Attributes for specific information
jss_purpose_raw = r_json['computer']['extension_attributes']
for ea in jss_purpose_raw:
    if ea['name'] == 'Inventory purpose':
        jss_purpose = ea['value']

print "JSS purpose field: %s" % jss_purpose

More Data from the JSS


The JSS offers much, much more information. For example we can ask the JSS to give us all of the buildings or departments we’ve defined, this information could be used to build a menu offering the user a valid set of choices. This data is located in a different location so we’ll need to change to URL we will call to reflect it.

departments_url = jss_url + '/departments'
urllib2:
request = urllib2.Request(departments_url)
request.add_header('Accept', 'application/json')
request.add_header('Authorization', 'Basic ' + base64.b64encode(UsernameVar + ':' + PasswordVar))

response = urllib2.urlopen(request)
print "Status code from request: %s\n" % response.code
response_json = json.loads(response.read())
Requests:
response  = requests.get(departments_url, headers={'Accept': 'application/json'}, auth=(UsernameVar,PasswordVar))
print "Status code from request: %s\n" % response.status_code
response_json = json.loads(response.text)

 Massaging the data


The JSON object containing the department information needs to be cleaned up as we’re only interested in the names of the departments.

{
    "departments": [
        {
            "id": 8,
            "name": "Administration"
        },
        {
            "id": 12,
            "name": "Collections and Scholarly Communication"
        },
        {
            "id": 11,
            "name": "IT and Digital Library Services"
        },
        {
            "id": 9,
            "name": "Research and User Services"
        },
        {
            "id": 10,
            "name": "Special Collections"
        }
    ]
}

This section examines each branch and stores the name  in the departments  list.

print json.dumps(response_json, sort_keys=True, indent=4)

departments = []
for a_department in response_json['departments']:
    departments.append(a_department.get('name'))

print "%s" % departments

Your API User


You should seriously consider creating a dedicated “API User” with the minimum required privileges. Should this account be compromised, the damage would be minimized. In System Settings:JSS User Accounts & Groups. Creating a new user with a Privilege Set of Auditor is easiest. Then uncheck every box, in the JSS Settings and in every panel down to Casper Imaging. This gives your api user a limited amount of Read privileges.

Screen Shot 2016-06-28 at 10.25.43 AM

 Putting it all together


Here is the complete sample script, if you have the requests module available you can comment out or remove the urlllib2 sections. You will need to supply the correct URL to your JSS and the credentials for your “API User”.

#!/usr/bin/python

import subprocess
import platform
import requests
import urllib2
import base64
import json
import xml.etree.cElementTree as ET
import re

def main():

    current_platform = platform.system()

    if current_platform == 'Darwin':
        local_uuid_raw = subprocess.check_output(["system_profiler", "SPHardwareDataType"])
        local_uuid = re.findall('Hardware UUID: (.*)', local_uuid_raw)[0]
    elif current_platform == 'Windows':
        local_uuid_raw = subprocess.check_output("wmic CsProduct Get UUID")
        local_uuid_raw = local_uuid_raw.split("\r\r\n")[1]
        local_uuid = local_uuid_raw.split(" ")[0]
    else:
        quit()

    # JSS API credentials
    UsernameVar = "your_api_user"
    PasswordVar = "your_api_user_password"

    # base URL of JSS
    jss_url = 'https://your_jss_server:8443/JSSResource'
    computer_url = jss_url + '/computers/udid/' + local_uuid

    print ("Fetching computer record from JSS...")
    print ("")

    #
    # urllib2 method
    request = urllib2.Request(computer_url)
    request.add_header('Accept', 'application/json')
    request.add_header('Authorization', 'Basic ' + base64.b64encode(UsernameVar + ':' + PasswordVar))

    response = urllib2.urlopen(request)
    print "Status code from request: %s" % response.code
    print ("")

    response_json = json.loads(response.read())
    # end of urllib2 method

    #
    # requests method. Try not to use, since we'd need to deploy another module
    # response  = requests.get(computer_url, headers={'Accept': 'application/json'}, auth=(UsernameVar,PasswordVar))
    # print "Status code from request: %s" % response.status_code
    # response_json = json.loads(response.text)
    # end of requests method

    # print full record
    # print json.dumps(response_json, sort_keys=True, indent=4)

    # print appropriate data
    print "JSS assigned ID: %s" % response_json['computer']['general']['id']
    print "Asset Tag: %s" % response_json['computer']['general']['asset_tag']
    print "User name (uNID): %s" % response_json['computer']['location']['username']
    print "Email Address: %s" % response_json['computer']['location']['email_address']
    print "Real name: %s" % response_json['computer']['location']['real_name']
    print "Phone number: %s" % response_json['computer']['location']['phone']
    print "Location: %s" % response_json['computer']['location']['room']

    jss_ea_list = response_json['computer']['extension_attributes']

    for ea in jss_ea_list:
        if ea['name'] == 'Inventory purpose':
            jss_purpose = ea['value']

    print ("JSS purpose field: %s" % jss_purpose)
    print ("")
    print ("")
    print "Fetching Departments from JSS..."
    print ("")

    departments_url = jss_url + '/departments'

    #
    # urllib2 method
    request = urllib2.Request(departments_url)
    request.add_header('Accept', 'application/json')
    request.add_header('Authorization', 'Basic ' + base64.b64encode(UsernameVar + ':' + PasswordVar))

    response = urllib2.urlopen(request)
    print "Status code from request: %s" % response.code
    print ("")
    response_json = json.loads(response.read())
    # end of urllib2 method

    #
    # requests method. Try not to use, since we'd need to deploy another module
    # response  = requests.get(departments_url, headers={'Accept': 'application/json'}, auth=(UsernameVar,PasswordVar))
    # print "Status code from request: %s\n" % response.status_code
    # response_json = json.loads(response.text)
    # end of requests method

    #  print full record
    print json.dumps(response_json, sort_keys=True, indent=4)

    departments = []
    for a_department in response_json['departments']:
        departments.append(a_department.get('name'))

    print "%s" % departments

if __name__ == '__main__':
    main()

 Output of the script


Here’s the output of the sample script running on our JSS. If you haven’t defined any departments, your results will be different.

uuid: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn

Fetching computer record from JSS...

Status code from request: 200

JSS assigned ID: 35
Asset Tag: 123456
User name (uNID): u0000000
Email Address: lyanna.mormont@utah.edu
Real name: Lyanna Mormont
Phone number: (801) 555-1212
Location: 6001 MLIB
JSS purpose field: Staff laptop


Fetching Departments from JSS...

Status code from request: 200

{
    "departments": [
        {
            "id": 8,
            "name": "Administration"
        },
        {
            "id": 12,
            "name": "Collections and Scholarly Communication"
        },
        {
            "id": 11,
            "name": "IT and Digital Library Services"
        },
        {
            "id": 9,
            "name": "Research and User Services"
        },
        {
            "id": 10,
            "name": "Special Collections"
        }
    ]
}
[u'Administration', u'Collections and Scholarly Communication', u'IT and Digital Library Services', u'Research and User Services', u'Special Collections']

Acknowledgements


Without the following resources, my project wouldn’t have progressed as quickly as I did.

http://macbrained.org/the-jss-rest-api-for-everyone/
https://unofficial-jss-api-docs.atlassian.net/wiki

No Comments

Leave a Reply