In Part 1 we introduced Nautobot’s new custom defined Relationships feature. Potential use cases were identified, and the process to configure the relationships using the web user interface was outlined. Nautobot Relationships are supported by the REST API (Application Programming Interface) for standard CRUD (Create, Read, Update, Delete) operations and also by the GraphQL API for read-only operations. This post will outline how to leverage the API, for a use case described in Part 1, to programmatically interact with custom-defined relationships.
For most systems, REST is the de facto API exposed to clients. It is an HTTP-based service that supports stateless client-server interaction for data retrieval of each model in the Nautobot database. For a more detailed overview, check out the Nautobot docs on REST.
HTTP has functions that align to the typical CRUD operations. Nautobot’s RESTful API uses these HTTP functions to interact with the objects in the database. The same operations supported on the GUI are supported on the API. To demonstrate, we’ll create the ‘Circuit IPAddress’ relationship using the API.
The first step when dealing with the API is to consult the endpoint documentation. On the Home page, select the API button in the bottom right corner.
The API button will redirect to <server_url>/api/docs/
which is the Nautobot API endpoint documentation powered by Swagger. To create a Relationship object a POST operation to the /extras/relationships/
endpoint is required. The docs outline that the data
field for the POST method has a number of required fields, highlighted with a red asterisk.
To execute the HTTP POST, we’ll develop a HTTP client using the Python requests
HTTP library, which supports REST and non-REST API communications. Alternatively, the Swagger documentation can be used to test REST API requests directly in the web browser.
When creating the Relationship, a valid slug
field consisting of letters, numbers, underscores or hyphens is required to uniquely identify this new object. In the GUI, this field is automatically generated; but in the API it needs to be manually defined. All other fields should be as described in the previous post.
INFO: Nautobot release 1.2 will introduce auto-generated slug fields for the API.
server_url = "https://demo.nautobot.com"
url = f"{server_url}/api/extras/relationships/"
token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
payload = {
"name": "Circuit IPAddress",
"description": "Custom relationship between circuits and IP addresses",
"slug": "circuit-ipaddress",
"type": "one-to-many",
"source_type": "circuits.circuit",
"source_label": "IP Address",
"destination_type": "ipam.ipaddress",
"destination_label": "Circuit",
}
headers = {"Authorization": f"Token {token}"}
response = requests.request("POST", url, headers=headers, json=payload)
print(json.dumps(response.json(), indent=2))
If the object was created successfully, the HTTP return code of 201 is returned, along with the JSON definition of the object.
{
"id": "2b13240d-cec0-4eb5-a640-ce8f9f47fd9c",
"url": "http://demo.nautobot.com/api/extras/relationships/2b13240d-cec0-4eb5-a640-ce8f9f47fd9c/",
"name": "Circuit IPAddress",
"slug": "circuit-ipaddress",
"description": "Custom relationship between circuits and IP addresses",
"type": "one-to-many",
"source_type": "circuits.circuit",
"source_label": "IP Address",
"source_hidden": false,
"source_filter": null,
"destination_type": "ipam.ipaddress",
"destination_label": "Circuit",
"destination_hidden": false,
"destination_filter": null
}
The Relationship Association is a way to use the new model Relationship. It links instances of the models used in the source_type
and destination_type
. It’s analogous to selecting the remote end of the relationship using a drop-down list on the web GUI.
To create the association using the REST API, the uuid
of each instance in the association is configured in the source_id
and destination_id
. The relationship
field can also be configured with a uuid
or, as in the example, a slug
field can be provided within a dictionary, causing Nautobot to do an object lookup.
The uuid
values can be retrieved with a GET request to the specific model API endpoint. To simplify the example, we’ll use static object ID’s for each instance in the association. A simple loop will iterate over the two IP address objects to associate each instance with the circuit using the new circuit-ipaddress
Relationship.
server_url = "https://demo.nautobot.com"
url = f"{server_url}/api/extras/relationship-associations/"
token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
circuit_id = "8b109292-047d-4140-b711-a9524ebb220c"
ip_addr1_id = "94a902a3-e063-44b6-889c-d3023b901aff"
ip_addr2_id = "8b195702-eca2-4ce3-8d48-68af8d9f5b65"
headers = {"Authorization": f"Token {token}"}
for ip_addr_id in [ip_addr1_id, ip_addr2_id]:
payload = {
"relationship": {
"slug": "circuit-ipaddress",
},
"source_type": "circuits.circuit",
"source_id": circuit_id,
"destination_type": "ipam.ipaddress",
"destination_id": ip_addr_id,
}
response = requests.request("POST", url, headers=headers, json=payload)
print(json.dumps(response.json(), indent=2))
Once again, if successfully completed, the HTTP return code of 201 is returned, along with the JSON definition of the object(s).
{
"id": "830166ef-93af-4eb8-ba56-2fe9e5d15bbe",
"relationship": {
"id": "00a17433-3f59-437a-a413-3f6e58e7d5f0",
"url": "http://demo.nautobot.com/api/extras/relationships/00a17433-3f59-437a-a413-3f6e58e7d5f0/",
"name": "Circuit IPAddress",
"slug": "circuit-ipaddress",
"display": "Circuit IPAddress"
},
"source_type": "circuits.circuit",
"source_id": "8b109292-047d-4140-b711-a9524ebb220c",
"destination_type": "ipam.ipaddress",
"destination_id": "94a902a3-e063-44b6-889c-d3023b901aff"
}
{
"id": "d3d53984-61d5-414a-adaa-eec352baddad",
"relationship": {
"id": "00a17433-3f59-437a-a413-3f6e58e7d5f0",
"url": "http://demo.nautobot.com/api/extras/relationships/00a17433-3f59-437a-a413-3f6e58e7d5f0/",
"name": "Circuit IPAddress",
"slug": "circuit-ipaddress",
"display": "Circuit IPAddress"
},
"source_type": "circuits.circuit",
"source_id": "8b109292-047d-4140-b711-a9524ebb220c",
"destination_type": "ipam.ipaddress",
"destination_id": "8b195702-eca2-4ce3-8d48-68af8d9f5b65"
}
In the majority of the cases when using relationships, the client will have an existing object in context, e.g., a circuit. So the query will be from the perspective of that object. The uuid
of the circuit can be used to query along with the Relationship slug
to get back any relationship information. The circuit was the source_type
of the relationship, so the circuit object uuid
is provided as a value into the source_id
query parameter.
server_url = "http://demo.nautobot.com"
token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
slug = "circuit-ipaddress"
circuit_id = "8b109292-047d-4140-b711-a9524ebb220c"
url = f"{server_url}/api/extras/relationship-associations/?relationship={slug}&source_id={circuit_id}"
headers = {"Authorization": f"Token {token}"}
response = requests.request("GET", url, headers=headers)
print(json.dumps(response.json(), indent=2))
The output return data count
shows two objects, representing the two IP address objects associated with the circuit and their JSON data.
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": "830166ef-93af-4eb8-ba56-2fe9e5d15bbe",
"relationship": {
"id": "00a17433-3f59-437a-a413-3f6e58e7d5f0",
"url": "http://demo.nautobot.com/api/extras/relationships/00a17433-3f59-437a-a413-3f6e58e7d5f0/",
"name": "Circuit IPAddress",
"slug": "circuit-ipaddress",
"display": "Circuit IPAddress"
},
"source_type": "circuits.circuit",
"source_id": "8b109292-047d-4140-b711-a9524ebb220c",
"destination_type": "ipam.ipaddress",
"destination_id": "94a902a3-e063-44b6-889c-d3023b901aff"
},
{
"id": "d3d53984-61d5-414a-adaa-eec352baddad",
"relationship": {
"id": "00a17433-3f59-437a-a413-3f6e58e7d5f0",
"url": "http://demo.nautobot.com/api/extras/relationships/00a17433-3f59-437a-a413-3f6e58e7d5f0/",
"name": "Circuit IPAddress",
"slug": "circuit-ipaddress",
"display": "Circuit IPAddress"
},
"source_type": "circuits.circuit",
"source_id": "8b109292-047d-4140-b711-a9524ebb220c",
"destination_type": "ipam.ipaddress",
"destination_id": "8b195702-eca2-4ce3-8d48-68af8d9f5b65"
}
]
}
GraphQL is a recent technology, designed as a query language for APIs and a server-side application for resolving client queries. Refer to the Resources section, for some excellent material from my colleague Tim Fiola on GraphQL.
In Nautobot, GraphQL supports read-only operations. No updates or modifications can be executed through the GraphQL API. Its power is in complex object data retrieval and the supporting tools for the language, such as GraphiQL, which is an integrated client in Nautobot. GraphiQL can be used to develop queries based on the object model schema to retrieve structured data from the Nautobot GraphQL server. Select the GraphQL button on the bottom-right of the Nautobot Home page.
Nautobot’s GraphQL implementation supports querying custom-defined relationships (and their associations) using the following format, with the hyphens replaced with underscores.
rel_<RELATIONSHIP_SLUG>
Building on the previous example of querying the circuit-ipaddress
Relationship.
rel_circuit_ipaddress
In GraphQL a sample circuit query, filtered with a specific cid
might look as follows.
Notice how the rel_circuit_ipaddress
query object has changed the hyphens to underscores. Also note that attributes available to query within the relationship association depend on the type of object.
The sample query uses the interface
, vrf
and address
fields to show how the Relationship can be used to extract complex association data for the related IP address object. All GraphQL object type fields can be traversed further to retrieve native or custom-defined relationships. This makes it very easy to get all the required data in just one GraphQL query.
An IP address is represented as an IPAddressType
in the GraphQL schema. Schema documentation can be viewed using the Docs
link on the left-hand side of the GraphQL page. The schema for IPAddressType
displays all of the fields available to query.
Accessing the fields of the model within the relationship using GraphQL is in contrast to the REST API, which returns a reference to the instance. The client then has to make another REST API call to the object endpoint to get the related data.
In addition, the GraphQL server response contains only the fields requested in the query. Notice how this is different from a REST response that returns all fields for an endpoint and the client must filter to extract interested fields.
This series focused on how custom-defined relationships can provide a flexible approach to modeling networks using Nautobot as a source of truth. Use cases facilitated the overall concept; and the various different configuration methods highlight Nautobot’s versatility. Relationships are a key feature in the Nautobot system. If you have any specific use cases please let us know.
-Paddy
Share details about yourself & someone from our team will reach out to you ASAP!