Skip to content

Automating Nautobot - Lab 05

Building a Site Report with Ansible and Nautobot Data

Task 1 - Use Data from the REST API to Build a Markdown Report with Ansible

Nautobot, being the source of truth for a good part of your device data, can also be used in combination with Ansible and Jinja templates to quickly create all sorts of text reports with dynamic information.

Here you will create a playbook that uses both the REST API and the GraphQL API to retrieve data about a Site and its Devices in order to produce a Site Report.

Step 1-1

In the /home/ntc/ansible/ folder, create a new file named pb_report.yml. Open it in your editor of choice and add the contents below.

---
- name: GET SITE INFORMATION FROM THE REST API
  hosts: localhost
  vars:
    site_slug: "ams01" # FILL IN WITH PROPER SITE VALUE FOR YOUR SYSTEM
    nautobot_host: "https://REPLACE_WITH_NAUTOBOT_IP/"
    nautobot_token: "REPLACE_WITH_NAUTOBOT_TOKEN"

  tasks:
    - name: LOOK UP SITE INFO IN NAUTOBOT
      set_fact:
        site_response: "{{ query('networktocode.nautobot.lookup',
                                  'sites',
                                  api_filter='slug='+site_slug,
                                  api_endpoint=nautobot_host,
                                  token=nautobot_token,
                                  validate_certs=False) }}"

    - debug:
        var: site_response
        verbosity: 1

    - name: SET ONLY THE USEFUL INFORMATION INTO THE VAR
      set_fact:
        site_info: "{{ site_response[0]['value'] }}"

    - debug:
        var: site_info
        verbosity: 1

- name: RENDER THE SITE REPORT
  hosts: localhost
  tasks:
    - name: RENDER THE SITE REPORT
      template:
        src: site_report.j2
        dest: site_report.md

Note: It has placeholders for the Nautobot IP address and its token. Make sure you replace them with the ones provided by your instructor on the day!

Step 1-2

Let's take a moment to understand what the first part of this playbook does:

  • It defines a site_slug which will serve as the input for which Site to create are report for.
  • It also defines the connection parameters for API access. These are specified in the playbook here directly for the sake of simplicity in the lab environment.
  • A query lookup plugin from the Nautobot collection is used to send the query to the Nautobot REST API. This could also very easily be replaced by the generic uri module from Ansible.
    • The only "interesting" parameter is the api_filter which tells the lookup what to query for - in this case, it will get a specific site based on its slug value.
    • The lookup plugin returns a specific key/value data structure which can be viewed when executing the playbook with a -v parameter to trigger the debug module.
  • Right now the debug tasks are there to assist you in understanding what data is retrieved and to link it to the Jinja template later.

Step 1-3

In the /home/ntc/ansible/ folder, create a new folder named templates. In it, create a new file named site_report.j2. Open it in your editor of choice and add the contents below.

ntc@nautobot-1:~/labs/ansible$ mkdir templates
ntc@nautobot-1:~/labs/ansible$ touch templates/site_report.j2
# Site report for {{ site_info['name'] }}

- **Tenant**: {{ site_info['tenant']['name'] }}
- **Status**: {{ site_info['status']['label'] }}
- **Region**: {{ site_info['region']['name'] }}
- **Facility**: {{ site_info['facility'] }}
- **Site Type**: {{ site_info['custom_fields']['site_type'] }}

## Statistics

| Type | Count |
|:-----|:------|
| Devices | {{ site_info['device_count']}} |
| Racks | {{ site_info['rack_count']}} |
| Prefixes | {{ site_info['prefix_count']}} |
| VLANs | {{ site_info['vlan_count']}} |
| Circuits | {{ site_info['circuit_count']}} |
| VMs | {{ site_info['virtualmachine_count']}} |

Note: The template extracts interesting (at least for the report!) data from the site_info fact and formats it with a little Markdown syntax. While the table won't look like much in text format, when the report is rendered as HTML, it will look great!

Step 1-4

Execute the pb_report.yml playbook. Spend some time here and run it with a -v for verbosity - this will cause the debug tasks to fire and show you the data being fed into the Jinja template.

# Normal execution to generate the output report.
ntc@nautobot-1:~/labs/ansible$ ansible-playbook pb_report.yml

# Debugs visible execution to assist in troubleshooting.
ntc@nautobot-1:~/labs/ansible$ ansible-playbook pb_report.yml -v

Step 1-5

Take a look at the freshly generated site_report.md file.

ntc@nautobot-1:~/labs/ansible$ cat site_report.md
# Site report for AMS01

- **Tenant**: Nautobot Airports
- **Status**: Active
- **Region**: Netherlands
- **Facility**: Amsterdam Airport Schiphol
- **Site Type**: POP

## Statistics

| Type | Count |
|:-----|:------|
| Devices | 11 |
| Racks | 8 |
| Prefixes | 39 |
| VLANs | 16 |
| Circuits | 4 |
| VMs | 0 |

If you are working in modern editors like VSCode, you should have a Markdown Preview function that shows you the HTML rendered output. In VSCode you can access it through the Command Palette -> Markdown: Open Preview.

It should look like this:

Task 2 - Use Data from the GraphQL API to Add More Information to the Report

The report looks pretty good so far, but what if you could also have details about the site's devices in the same file? For this second chunk of the report, let's use the GraphQL API.

Sometimes you might need to mix and match - for example the Site counts printed in the report above are only exposed via the REST API call.

Since devices are more complex objects and they also have a lot of relationships with other objects, it's a lot easier to build a GraphQL query to get all the data needed.

Step 2-1

Remember that GraphQL query built at the end of the previous lab? This is where it comes in handy. This is the new play code that you're adding to the pb_report.yml playbook.

Let's take it step by step:

  • The new information here is the GraphQL query - it's added as a play variable. The | is necessary to define a multiline string in YAML.
  • Since the Nautobot collection provides a query_graphql module, it is actually very easy to send the query - the module even registers the returned data in the Ansible host vars for you!
  • Since the top-level object in the query is devices, that's the key under which the data coming from the API will be found. The play is setup with a debug task should you want to see that raw data as provided by the API.
- name: GET DEVICE INFORMATION FROM THE GRAPHQL API
  hosts: localhost
  tags: gql
  vars:
    site_slug: "ams01" # FILL IN WITH PROPER REGION VALUE FOR YOUR SYSTEM
    nautobot_host: "https://REPLACE_WITH_NAUTOBOT_IP/"
    nautobot_token: "REPLACE_WITH_NAUTOBOT_TOKEN"
    nautobot_query: |
      query {
        devices(site: "{{ site_slug }}") {
          name
          status {
            name
          }
          device_type {
            manufacturer {
              name
            }
            part_number
          }
          device_role {
            name
          }
          primary_ip4 {
            interface {
              name
            }
            host
          }
        }
      }

  tasks:
    - name: GET SITE DEVICES INFORMATION FROM NAUTOBOT
      networktocode.nautobot.query_graphql:
        url: "{{ nautobot_host }}"
        token: "{{ nautobot_token }}"
        validate_certs: false
        query: "{{ nautobot_query }}"
        update_hostvars: true

    - debug:
        var: devices
        verbosity: 1

Note: If you want an easier way to check out the data returned by the API, copy the GraphQL query and paste it into the Nautobot GraphiQL web interface! You will need to replace the {{ site_slug }} part with an actual value (like ams01) for it to work though.

Step 2-2

For reference, the full pb_report.yml playbook should now be as follows:

---
- name: GET SITE INFORMATION FROM THE REST API
  hosts: localhost
  vars:
    site_slug: "ams01" # FILL IN WITH PROPER REGION VALUE FOR YOUR SYSTEM
    nautobot_host: "https://REPLACE_WITH_NAUTOBOT_IP/"
    nautobot_token: "REPLACE_WITH_NAUTOBOT_TOKEN"

  tasks:
    - name: LOOK UP SITE INFO IN NAUTOBOT
      set_fact:
        site_response: "{{ query('networktocode.nautobot.lookup',
                                  'sites',
                                  api_filter='slug='+site_slug,
                                  api_endpoint=nautobot_host,
                                  token=nautobot_token,
                                  validate_certs=False) }}"

    - debug:
        var: site_response
        verbosity: 1

    - name: SET ONLY THE USEFUL INFORMATION INTO THE VAR
      set_fact:
        site_info: "{{ site_response[0]['value'] }}"

    - debug:
        var: site_info
        verbosity: 1


- name: GET DEVICE INFORMATION FROM THE GRAPHQL API
  hosts: localhost
  tags: gql
  vars:
    site_slug: "ams01" # FILL IN WITH PROPER REGION VALUE FOR YOUR SYSTEM
    nautobot_host: "https://REPLACE_WITH_NAUTOBOT_IP/"
    nautobot_token: "REPLACE_WITH_NAUTOBOT_TOKEN"
    nautobot_query: |
      query {
        devices(site: "{{ site_slug }}") {
          name
          status {
            name
          }
          device_type {
            manufacturer {
              name
            }
            part_number
          }
          device_role {
            name
          }
          primary_ip4 {
            interface {
              name
            }
            host
          }
        }
      }

  tasks:
    - name: GET SITE DEVICES INFORMATION FROM NAUTOBOT
      networktocode.nautobot.query_graphql:
        url: "{{ nautobot_host }}"
        token: "{{ nautobot_token }}"
        validate_certs: false
        query: "{{ nautobot_query }}"
        update_hostvars: true

    - debug:
        var: devices
        verbosity: 1


- name: RENDER THE SITE REPORT
  hosts: localhost
  tasks:
    - name: RENDER THE SITE REPORT
      template:
        src: site_report.j2
        dest: site_report.md

Note: It has placeholders for the Nautobot IP address and its token. Make sure you replace them with the ones provided by your instructor on the day!

Note: The GraphQL play comes in after the REST play and before the template rendering!

Step 2-3

The Jinja template also needs some additions, update the site_report.j2 file so it looks as follows (adding the ## Devices section at the end):

# Site report for {{ site_info['name'] }}

- **Tenant**: {{ site_info['tenant']['name'] }}
- **Status**: {{ site_info['status']['label'] }}
- **Region**: {{ site_info['region']['name'] }}
- **Facility**: {{ site_info['facility'] }}
- **Site Type**: {{ site_info['custom_fields']['site_type'] }}

## Statistics

| Type | Count |
|:-----|:------|
| Devices | {{ site_info['device_count']}} |
| Racks | {{ site_info['rack_count']}} |
| Prefixes | {{ site_info['prefix_count']}} |
| VLANs | {{ site_info['vlan_count']}} |
| Circuits | {{ site_info['circuit_count']}} |
| VMs | {{ site_info['virtualmachine_count']}} |

## Devices

| Hostname | Role | Vendor | Model | IPv4 | Interface | Status |
|:-----|:-----|:-----|:-----|:-----|:-----|:-----|
{% for device in devices %}
| {{ device['name'] -}}
| {{ device['device_role']['name'] -}}
| {{ device['device_type']['manufacturer']['name'] -}}
| {{ device['device_type']['part_number'] -}}
| {{ device['primary_ip4']['host'] |  default("None") -}}
| {{ device['primary_ip4']['interface']['name'] |  default("None") -}}
| {{ device['status']['name'] }}
{% endfor %}

Step 2-4

Finally, save and run the updated playbook!

# Normal execution to generate the output report.
ntc@nautobot-1:~/labs/ansible$ ansible-playbook pb_report.yml

# Debugs visible execution to assist in troubleshooting.
ntc@nautobot-1:~/labs/ansible$ ansible-playbook pb_report.yml -v

Note: If you ever want to run the playbook for another site without changing it, you can override the site_slug from the command line: ansible-playbook pb_report.yml -e site_slug=atl01.

Step 2-5

The newly generated report should look like this:

ntc@nautobot-1:~/labs/ansible$ cat site_report.md
# Site report for AMS01

- **Tenant**: Nautobot Airports
- **Status**: Active
- **Region**: Netherlands
- **Facility**: Amsterdam Airport Schiphol
- **Site Type**: POP

## Statistics

| Type | Count |
|:-----|:------|
| Devices | 11 |
| Racks | 8 |
| Prefixes | 39 |
| VLANs | 16 |
| Circuits | 4 |
| VMs | 0 |

## Devices

| Hostname | Role | Vendor | Model | IPv4 | Interface | Status |
|:-----|:-----|:-----|:-----|:-----|:-----|:-----|
| ams01-dist-01| distribution| Cisco| WS-C6509-E| None| None| Active
| ams01-edge-01| edge| Arista| DCS-7280CR2-60| 10.11.128.1| Loopback0| Active
| ams01-edge-02| edge| Arista| DCS-7280CR2-60| 10.11.128.2| Loopback0| Active
| ams01-leaf-01| leaf| Arista| DCS-7150S-24| 10.11.128.3| Loopback0| Active
| ams01-leaf-02| leaf| Arista| DCS-7150S-24| 10.11.128.4| Loopback0| Active
| ams01-leaf-03| leaf| Arista| DCS-7150S-24| 10.11.128.5| Loopback0| Active
| ams01-leaf-04| leaf| Arista| DCS-7150S-24| 10.11.128.6| Loopback0| Active
| ams01-leaf-05| leaf| Arista| DCS-7150S-24| 10.11.128.7| Loopback0| Active
| ams01-leaf-06| leaf| Arista| DCS-7150S-24| 10.11.128.8| Loopback0| Active
| ams01-leaf-07| leaf| Arista| DCS-7150S-24| 10.11.128.9| Loopback0| Active
| ams01-leaf-08| leaf| Arista| DCS-7150S-24| 10.11.128.10| Loopback0| Active

Step 2-6

If you are working in modern editors like VSCode, you should have a Markdown Preview function that shows you the HTML rendered output. In VSCode you can access it through the Command Palette -> Markdown: Open Preview.

It should look like this:

Step 2-7

If you cannot get the preview to work, you can also see a final render of the report here on GitHub.