If you want to be a network engineer with programmability skills you need to be comfortable working with APIs. APIs make automation possible in an easy manner, as long as it’s structured and have some documentation. This is opposed to the CLI which is not structured. Instead we have to rely on screen scraping were we have to parse the data using regular expressions.
In this post I will cover the basics of the requests library which is used to interact with HTTP based APIs (also called restful APIs). I will begin covering the basics of the HTTP protocol to help you understand the code examples.
HTTP
The Hypertext Transport Protocol (HTTP) is an open standards based protocol for communication and exchange of data between clients and servers. This is definitely one of the most common application layer protocols used on the internet today, and what’s used on the web. I guess this is not news for you.
HTTP is defined in multiple RFCs depending on HTTP version. Version HTTP/1.1 which is still in heavy used today is defined in RFC 2616. HTTP/2 which is the latest version of the protocol is defined in RFC 7514. The main benefit of HTTP/2 is improved performance.
The standard port number for HTTP is TCP/80 while for TLS secured HTTP it is TCP/443 by default. Make sure you always use HTTPS which is secured by TLS to avoid sending data and passwords in clear-text.
HTTP Methods
We have different HTTP methods that may be used to interact with our webserver. The most common ones are GET, POST, PUT, PATCH and DELETE.
GET is used to retrieve data from a resource.
POST is commonly used to create a resource.
PUT is used to create or modify a resource.
PATCH is used to partially modify an existing resource.
DELETE is used to delete a resource.
Response codes
HTTP response codes are divided into different categories to make it easy to identify what type of response we got. For the details we have to look into the specific code. Here are the different main categories.
CategoryRangeInformational100–199Successful200–299Redirection300–399Client Error400–499Server Error500–599
I won’t cover the details of each but if you like cats I recommend visiting https://http.cat/ that explain the different response codes using pictures. Receiving response codes within the successful category (200-299) is normally what you want.
Image by Tomomi Imura (@girlie_mac)
HTTP headers
HTTP makes use of headers to provide extra information about requests and responses. The structure of the HTTP headers are a set of key value pairs. Some common header keys are:
KeyPurposeExample valueAcceptAccepted format of responseapplication/jsonAuthorizationAuthentication of clientBasic <credentials>Content-LengthSize of response21864Content-TypeFormat of content dataapplication/json
Resource path and Query parameters
A resource is identified as a path after the domain name such as /ipam/ip-addresses/. An HTTP request can usually be filtered using query parameters that are appended to the resource path such as ?family=4&mask_length=24 so the full path including query parameters would be /ipam/ip-addresses/?family=4&mask_length=24.
HTTP Tools
There are a lot of different tools available to create HTTP calls. Very popular ones are
curl (CLI based tool)
Postman (GUI based tool)
And for Python the requests library is very popular which we will now explore.
requests
Finally we will get our hands dirty and get to play with the popular HTTP based library requests. Make sure you have the library installed as it does not come with Python by default. The version I will use in the following examples will be 2.25.1 so if you want that version use pip install requests==2.25.1.
The documentation for the requests library can be found at https://requests.readthedocs.io/en/master/.
We’re going to work with the Netbox API in the following examples. We will use the demo setup which is available at netboxdemo.com. At the time of this writing the login credentials are netbox/netbox. The API uses a token for authentication and you can find the token for the logged in user at https://netboxdemo.com/user/api-tokens/.
More details about the Netbox API is available at https://netbox.readthedocs.io/en/stable/rest-api/overview/. The swagger documentation is available at https://netboxdemo.com/api/docs/.
Fetch data
Enough theory. Let’s fetch all the device types that are currently defined in Netbox.
>>> import requests
>>> token = "72830d67beff4ae178b94d8f781842408df8069d"
>>> device_types_url = https://netboxdemo.com/api/dcim/device-types/
>>> response = requests.get(url=device_types_url, headers={"Authorization": f"Token {token}"})
First we assign the token for the API to a variable named token. Next the API URL for accessing the device types is assigned to the variable device_types_url. Finally we execute an HTTP GET towards the API with the “Authorization” header key and its token defined. This is provided in the headers argument as a dictionary.
Let’s verify the response.
>>> response.ok True
>>> response.status_code 200
>>> response.headers["Content-Type"] 'application/json
>>> from pprint import pprint >>> pprint(response.json())
{
'count': 9,
'next': None,
'previous': None,
'results': [{
'comments': '',
'created': '2021-03-19',
'custom_fields': {},
'device_count': 1,
'display_name': 'Cisco Edge',
'front_image': None,
'id': 8,
'is_full_depth': True,
'last_updated': '2021-03-19T07:56:40.548524Z',
'manufacturer':
{'id': 2,
'name': 'Cisco',
'slug': 'cisco',
'url': 'https://netboxdemo.com/api/dcim/manufacturers/2/'
},
'model': 'Edge',
'part_number': '',
'rear_image': None,
'slug': 'edge',
'subdevice_role': None,
'tags': [],
'u_height': 1,
'url': 'https://netboxdemo.com/api/dcim/device-types/8/'
}, <output omitted>
We start by checking the attribute ok which is True and the status_code attribute which is 200. This is a good indication that our HTTP request was successful. We can also see that the response.headers["Content-Type"] is “application/json”. This indicates that the return data is in JSON format.
We use the pprint (pretty print)function to print the contents in a pretty format. Since the response content is JSON formatted we can use the json() function of the requests library to get structured data back.
Create data
Let’s create a new device type. Looking in the Swagger documentation and filtering on dcim we find that there’s a resource named /dcim/device-types/. Since we would like to create a new device type we are interested in the POST section. There you will find the required parameters and an example that shows the structure.
Let’s add 2960-24TC-L as a device type to our Netbox instance.
We will only provide the required parameters to have the device type created.
>>> model = "2960-24TC-L"
>>> slug = model.lower()
>>> body = {
"manufacturer": 2,
"model": model,
"slug": slug,
}
The reason I use a manufacturer id of 2 is because we found it’s referring to Cisco from the GET request we did in the previous example.
Let’s now POST it to Netbox and inspect the result.
>>> response = requests.post(url=device_types_url,
... headers={"Authorization": f"Token {token}"},
... json=body)
>>> response.ok
True
>>> response.status_code
201
>>> pprint(response.json())
{'comments': '',
'created': '2021-03-19',
'custom_fields': {},
'display_name': 'Cisco 2960-24TC-L',
'front_image': None,
'id': 10,
'is_full_depth': True,
'last_updated': '2021-03-19T13:53:50.485308Z',
'manufacturer': {'id': 2,
'name': 'Cisco',
'slug': 'cisco',
'url': 'https://netboxdemo.com/api/dcim/manufacturers/2/'
},
'model': '2960-24TC-L',
'part_number': '',
'rear_image': None,
'slug': '2960-24tc-l',
'subdevice_role': None,
'tags': [],
'u_height': 1,
'url': 'https://netboxdemo.com/api/dcim/device-types/10/'
}
We can see that we got an OK back and a response code of 201 which means that the resource was created. We can also inspect the details of the created resource by checking the JSON content in the response. This object got a device-type id of 10 which we will use in the next section.
Delete a resource
The only thing you need to delete a single device type is the url of the resource. Since we would like to delete the device type we just created we can use the url in the response we got when creating it.
>>> device_type_url = response.json().get("url")
>>> device_type_url 'https://netboxdemo.com/api/dcim/device-types/10/'
>>> response = requests.delete(url=device_type_url, headers={"Authorization": f"Token {token}"})
>>> response.ok
True
>>> response.status_code
204
>>> response.content
b''
>>> response = requests.get(url=device_type_url, headers={"Authorization": f"Token {token}"})
>>> response.status_code
404
>>> response.ok
False
The response was OK and we got a 204 as the response code which means no content was returned. We also verify it is empty when inspecting the content attribute.
We can confirm that the resource was actually deleted by running the same code again. Since we now receive a 404 “Not found” response code we know for sure it’s not available.
Conclusion
My intention when writing this post was to cover more details of the requests library but still I didn’t want the post to be too long. The requests library is something we will use a lot when interacting with APIs so we will have time to cover more details of it. Just a note, there’s a specific library to use when working with Netbox which is called pynetbox. You should see it as an abstraction layer on top of requests to make it easier for you to work with the Netbox API. Since requests is a general library for HTTP I think it’s more important to be familiar with.
Author: Rickard Körkkö
Comments