The JSS REST API for Everyone, part 2

I had a really positive response to the original article I posted on the JSS API, and the good people over at Macbrained.org even asked for a copy to be posted up on their site.  It was a lot of fun to write up and I decided to expand upon the first post with items I skipped or did not go into detail about.  I’m also compiling both articles into a single document with a more cohesive order and fully scripted examples of the examples that are covered.

 

JSS User Accounts

If you plan to leverage the JSS API for automation or other tasks, you will want to setup unique user accounts for each script/service/app that needs access.  In version 8.x of the JSS this was a little more straightforward than in version 9.x.  In both cases, the JSS objects that you can interact with have CRUD permissions to enable or disable for the user account you are working with.

CRUD means: Create, Read, Update and Delete.  They are analogous to the API methods we have already covered: POST, GET, PUT and DELETE.

In 8.x, API permissions were separate from the web interface permissions.  You could have API accounts with limited access to certain JSS objects and no access to the web interface.  The 8.x account information API page also only displays the CRUD permissions that applied to that object.

JSS 8x API Settings

In 9.x, the permissions for all JSS objects are unified.  Setting the CRUD permissions for an object grants both API and web interface access for the account.  Any account you create for API access will also be able to log into the JSS web interface.  Another effect is that the full CRUD list is shown for every object.  You will need to rely on the API Documentation to determine what permissions are applicable to the objects you are interacting with.

JSS 9x API Settings

 

Modifying and Building XML using ElementTree (Python)

If your workflow requires modifying JSS objects you will need to drop Bash for, or at least augment it with, another scripting language that has a solid XML parser available.  In my examples here we will be using Python and the ‘xml.etree.ElementTree’ library.

This example will be modifying a static computer group’s memberships.  As I mentioned in our earlier example with advanced computer searches a PUT is not an additive action (or subtractive for that matter).  If you make a PUT request of only one item to a JSS object that contained a list of ten you will end up replacing them them all.  To add to the existing list you must include your new item in a new list already containing the existing items and then submit it.

The basic workflow for updating a JSS object is to GET the original XML, parse out the section(s) we will be updating, insert/remove the XML the elements we want and then PUT this back into the JSS to the same JSS ID.

GET https://myjss.com/JSSResource/computergroups/id/123
    Returns 200 status code and XML of the resource.

<?xml version="1.0" encoding="UTF-8"?>
<computer_group>
    <id>123</id>
    <name>The Fleet</name>
    <is_smart>false</is_smart>
    <site>
        <id>-1</id>
        <name>None</name>
    </site>
    <criteria>
        <size>0</size>
    </criteria>
    <computers>
        <size>3</size>
        <computer>
            <id>1</id>
            <name>USS-Enterprise</name>
            <mac_address>NC:C1:70:1A:C1:1A</mac_address>
            <alt_mac_address/>
            <serial_number>Z00AB1XYZ2QR</serial_number>
        </computer>
        <computer>
            <id>2</id>
            <name>USS-Excelsior</name>
            <mac_address>NC:C2:00:01:1A:2B</mac_address>
            <alt_mac_address/>
            <serial_number>Z00CD2XYZ3QR</serial_number>
        </computer>
        <computer>
            <id>3</id>
            <name>USS-Defiant</name>
            <mac_address>NC:C1:76:41:B2:B3</mac_address>
            <alt_mac_address/>
            <serial_number>Z00EF3XYZ4QR</serial_number>
        </computer>
    </computers>
</computer_group>

The computer group object in the JSS contains a lot of information we won’t need for the update we will be performing. We need to convert the XML string we received in the response into an ElementTree object that we can work with.

computergroup = etree.fromstring(response.read())

To make some of our interactions easier we’re going to make another ElementTree object that is just the ‘computers’ node of the ‘computergroup’ object we just created.

computers = computergroup.find('computers')

To better visualize what that did, here is how the ‘computers’ object would print out:

<computers>
    <computer>
        <id>1</id>
        <name>USS-Enterprise</name>
        <mac_address>NC:C1:70:1A:C1:1A</mac_address>
        <alt_mac_address/>
        <serial_number>Z00AB1XYZ2QR</serial_number>
    </computer>
    <computer>
        <id>2</id>
        <name>USS-Excelsior</name>
        <mac_address>NC:C2:00:01:1A:2B</mac_address>
        <alt_mac_address/>
        <serial_number>Z00CD2XYZ3QR</serial_number>
    </computer>
    <computer>
        <id>3</id>
        <name>USS-Defiant</name>
        <mac_address>NC:C1:76:41:B2:B3</mac_address>
        <alt_mac_address/>
        <serial_number>Z00EF3XYZ4QR</serial_number>
    </computer>
</computers>

As you can see, we are now only interacting with the ‘computers’ node and its children. One of the great things about using the ElmenetTree library is that we can divide up a large XML source into multiple parts, like breaking out the computers above, but all of the changes we make will be reflected in the root object.

For example, if we wanted to find and delete the computer named ‘USS-Enterprise’ we could use the following code on the ‘computers’ object:

for computer in computers.findall('computer'):
    if computer.find('name').text == 'USS-Enterprise':
        computers.remove(computer)

Now if we print the output of the ‘computergroup’ object it will not include any computers named ‘USS-Enterprise’:

<computer_group>
    <id>123</id>
    <name>The Fleet</name>
    <is_smart>false</is_smart>
    <site>
        <id>-1</id>
        <name>None</name>
    </site>
    <criteria>
        <size>0</size>
    </criteria>
    <computers>
        <size>3</size>
        <computer>
            <id>2</id>
            <name>USS-Excelsior</name>
            <mac_address>NC:C2:00:01:1A:2B</mac_address>
            <alt_mac_address/>
            <serial_number>Z00CD2XYZ3QR</serial_number>
        </computer>
        <computer>
            <id>3</id>
            <name>USS-Defiant</name>
            <mac_address>NC:C1:76:41:B2:B3</mac_address>
            <alt_mac_address/>
            <serial_number>Z00EF3XYZ4QR</serial_number>
        </computer>
    </computers>
</computer_group>

We can use the same logic with the other key identifiers in our static group: JSS ID, serial number and MAC address.

Let’s take the truncated membership and PUT it back into the JSS; removing the computer ‘USS-Enterprise’ from the static group. We won’t be using the XML retrieved by our GET for this. Instead, we will use ElementTree to build a new XML object and then copy the computers over.

NewXML = etree.Element('computer_group')
NewXML_computers = etree.SubElement(newXML, 'computers')

We created the root element, ‘computer_group’, in the first line and then created a node, ‘computers’, in the second line. If we print out the ‘NewXML’ object it will look like this:

<computer_group>
    <computers/>
<computer_group>

Now that we have our XML structure we can use a simple for loop to iterate over each computer in the source XML and copy them over. The result will be an XML object containing only the computers we want to update the JSS computer group with.

for computer in computers.iter('computer'):
    NewXML_computers.append(computer)

<computer_group>
    <computers>
        <computer>
            <id>2</id>
            <name>USS-Excelsior</name>
            <mac_address>NC:C2:00:01:1A:2B</mac_address>
            <alt_mac_address/>
            <serial_number>Z00CD2XYZ3QR</serial_number>
        </computer>
        <computer>
            <id>3</id>
            <name>USS-Defiant</name>
            <mac_address>NC:C1:76:41:B2:B3</mac_address>
            <alt_mac_address/>
            <serial_number>Z00EF3XYZ4QR</serial_number>
        </computer>
    </computers>
</computer_group>

Now we can output this into a string and make a PUT request to the JSS. Unless writing to a file on the disk, ElementTree will not include our XML declaration line. As a workaround, we can have the XML declaration read in another string variable and then concatenate it to the output XML string.

xmlDeclaration = '<?xml version="1.0" encoding="UTF-8"?>'
PUTxml = xmlDeclaration + etree.tostring(NewXML)

And here is our final XML:

<?xml version="1.0" encoding="UTF-8"?>
<computer_group>
    <computers>
        <computer>
            <id>2</id>
            <name>USS-Excelsior</name>
            <mac_address>NC:C2:00:01:1A:2B</mac_address>
            <alt_mac_address/>
            <serial_number>Z00CD2XYZ3QR</serial_number>
        </computer>
        <computer>
            <id>3</id>
            <name>USS-Defiant</name>
            <mac_address>NC:C1:76:41:B2:B3</mac_address>
            <alt_mac_address/>
            <serial_number>Z00EF3XYZ4QR</serial_number>
        </computer>
    </computers>
</computer_group>

Now we’re going to make the PUT request and update the static computer group’s membership.

PUT https://myjss.com/JSSResource/computergroups/id/123 PUTxml
    Returns 201 status code and XML with the "<id>" of the updated resource.

This code allowed us to remove a computer from a static group. What if you wanted to add a computer? The concept is the same, but now we’ll build a second XML object to insert into our XML for the PUT. To make this a little more interesting we’ll get the informaion about the computer via the API as well.

GET https://myjss.com/JSSResource/computergroups/id/123
    Returns 200 status code and XML of the resource.computergroup = etree.fromstring(response.read())

computers = computergroup.find('computers')

NewXML = etree.Element('computer_group')
NewXML_computers = etree.SubElement(NewXML, 'computers')

for computer in computers.iter('computer'):
    NewXML_computers.append(computer)

Now we will retrieve the information of the computer to add to the group and parse that into an XML object to add into our ‘NewXML’. The computer object we’re creating is just like the ‘NewXML’ object but with more sub-elements. We are also assigning values to the elements that we’re creating, parsed from the returned JSS computer XML.

GET https://myjss.com/JSSResource/computers/id/5
    Returns 200 status code and XML of the resource.
Mac = etree.fromstring(response.read())
NewMember = etree.Element('computer')
NewMember_id = etree.SubElement(NewMember, 'id')
NewMember_id.text = Mac.find('general/id').text
NewMember_name = etree.SubElement(NewMember, 'name')
NewMember_name.text = Mac.find('general/name').text
NewMember_macadd = etree.SubElement(NewMember, 'mac_address')
NewMember_macadd.text = Mac.find('general/mac_address').text
NewMember_serial = etree.SubElement(NewMember, 'serial_number')
NewMember_serial.text = Mac.find('general/serial_number').text

NewXML_computers.append(NewMember)

PUTxml = xmlDeclaration + etree.tostring(NewXML)

<?xml version="1.0" encoding="UTF-8"?>
<computer_group>
    <computers>
        <computer>
            <id>1</id>
            <name>USS-Enterprise</name>
            <mac_address>NC:C1:70:1A:C1:1A</mac_address>
            <alt_mac_address/>
            <serial_number>Z00AB1XYZ2QR</serial_number>
        </computer>
        <computer>
            <id>2</id>
            <name>USS-Excelsior</name>
            <mac_address>NC:C2:00:01:1A:2B</mac_address>
            <alt_mac_address/>
            <serial_number>Z00CD2XYZ3QR</serial_number>
        </computer>
        <computer>
            <id>3</id>
            <name>USS-Defiant</name>
            <mac_address>NC:C1:76:41:B2:B3</mac_address>
            <alt_mac_address/>
            <serial_number>Z00EF3XYZ4QR</serial_number>
        </computer>
        <computer>
            <id>5</id>
            <name>USS-Constitution</name>
            <mac_address>NC:C1:70:0C:00:00</mac_address>
            <serial_number>Z00FE4XYZ5QR</serial_number>
        </computer>
    </computers>
</computer_group>

PUT https://myjss.com/JSSResource/computergroups/id/123 PUTxml
    Returns 201 status code and XML with the "<id>" of the updated resource.

 

Extension Attributes for Power Admins

While we spent a good amount of time describing how to update a static computer group using the API, I will say this is probably a workflow you should avoid (though you may find situations where maintaining a static group this way is the correct solution).  It makes for a great example, and good practice, but there are better ways to achieve the same goal.

Consider the extension attribute.  An extension attribute is capable of returning or storing additional data beyond the standard inventory report.  You can create extension attributes to be populated by LDAP attribute, pop-up menu, script or simple text input.  These values become a part of a device’s inventory record and can be used for criteria in smart groups and advanced searches.

Let’s look at a possible scenario to apply this to.  We have an extension attribute labeled “App Store” that is displayed with the location information for a mobile device.  There are smart mobile device groups for each country your organization has a VPP account in and the extension attribute is a pop-up menu with the criteria for populating those groups.  Tried to those smart groups are country specific apps and ebooks for redemption.

As a part of the on-boarding process your users may select the App Store they will be receiving these VPP codes from.  Whichever way you wish to present this you can leverage the JSS API to dynamically populate the available options be based upon what is defined by the extension attribute you created:

GET https://myjss.com/JSSResource/mobiledviceextensionattributes/id/1
    Returns 200 status code and XML of the resource.
<mobile_device_extension_attribute>
    <id>1</id>
    <name>App Store</name>
    <description>Used for scoping content from a specific country's App Store.</description>
    <data_type>String</data_type>
    <input_type>
        <type>Pop-up Menu</type>
        <popup_choices>
            <choice>United States</choice>
            <choice>Canada</choice>
            <choice>Great Britain</choice>
            <choice>Germany</choice>
            <choice>Hong Kong</choice>
            <choice>Australia</choice>
        </popup_choices>
    </input_type>
    <inventory_display>User and Location</inventory_display>
</mobile_device_extension_attribute>

Parse out the extension attribute’s ID, name, data type and the section for the ‘popup_choices’.  Make the choices a drop list for your user to select from.  When they go to submit you can take their selection and the other details and pipe them into XML that will update their device’s record.

PUTxml = '''<mobile_device>
    <extension_attributes>
        <extension_attribute>
            <id>1</id>
            <name>Mobile Device Type</name>
            <type>String</type>
            <value>United States</value
        </extension_attribute>
    </extension_attributes>
</mobile_device>'''
PUT https://myjss.com/JSSResource/mobiledevices/id/1 PUTxml
    Returns 201 status code and XML with the "<id>" of the updated resource.

This PUT will update the mobile device’s “Last Inventory Update” timestamp causing the smart groups to recalculate their memberships.  The end result is through an API action you have made tailored content immediately available to a user’s device without waiting for the 24 hour mobile device update cycle.  The same is true for computers.

 

Wrap Up, part 2

I hope to have a neatly formatted copy of the two posts together in PDF format soon.  It was after publishing the first post and chatting with people who had questions that I decided to write a follow-up that went into more advanced territory.  Before I wrap up the PDF I may yet have more to throw in.  If you have any feedback, as before, please reach out to me!  You’ll find I’m pretty active on Twitter.

Advertisements

One thought on “The JSS REST API for Everyone, part 2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s