Skip to content

Automating Nautobot - Lab 04

Using Nautobot Modules and Inventory Plugins in Ansible

Task 1 - Update Nautobot Data Using Ansible

Step 1-1

On your lab VM, open a terminal session and go to the /home/ntc/labs folder. Create a new folder called ansible.

ntc@nautobot-1:~$ cd labs/
ntc@nautobot-1:~/labs$ mkdir ansible
ntc@nautobot-1:~/labs$ cd ansible
ntc@nautobot-1:~/labs/ansible$

Step 1-2

In the ansible folder, create a new file named ansible.cfg. Open it in your editor of choice and add the contents below.

Note: To work with Nautobot from Ansible, you'll need the networktocode.nautobot collection and the file below configures Ansible to install collections under a playbook-local path.

[defaults]

# Disable automatic facts gathering.
gathering = explicit

# Do not create retry files when tasks fail. Comment this if you need
# the default behaviour.
retry_files_enabled = False

# For lab usage with password authentication only!
host_key_checking = False

# Playbook-local collections
collections_path = ./collections

Step 1-3

Verify that you have Ansible installed:

ntc@nautobot-1:~/labs/ansible$ ansible --version
ansible [core 2.12.4]
  config file = /home/ntc/labs/ansible/ansible.cfg
  configured module search path = ['/home/ntc/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/ntc/.local/lib/python3.8/site-packages/ansible
  ansible collection location = /home/ntc/labs/ansible/collections
  executable location = /home/ntc/.local/bin/ansible
  python version = 3.8.10 (default, Mar 15 2022, 12:22:08) [GCC 9.4.0]
  jinja version = 2.10.1
  libyaml = True

Step 1-4

The next step is to install the networktocode.nautobot collection for Ansible.

ntc@nautobot-1:~/labs/ansible$ ansible-galaxy collection install networktocode.nautobot:3.4.1
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/download/networktocode-nautobot-3.4.1.tar.gz to /home/ntc/.ansible/tmp/ansible-local-896949cyr_0rd/tmpj7sxztlb/networktocode-nautobot-3.4.1-_4cu4l61
Installing 'networktocode.nautobot:3.4.1' to '/home/ntc/labs/ansible/collections/ansible_collections/networktocode/nautobot'
networktocode.nautobot:3.4.1 was installed successfully
ntc@nautobot-1:~/labs/ansible$

Step 1-5

The Nautobot Ansible collection has documentation available and you can find the full list of plugins it provides here.

Step 1-6

For example, in your first playbook you will use the networktocode.nautobot.site module to manage Site objects. You can view its documentation page online, but also very handy is its documentation via the CLI command ansible-doc.

Note: Documentation is incredibly useful when you're trying to learn how to use a module - it will tell you what parameters it takes, what data it returns, and provide some examples to get you started.

ntc@nautobot-1:~/labs/ansible$ ansible-doc networktocode.nautobot.site
> NETWORKTOCODE.NAUTOBOT.SITE    (/home/ntc/labs/ansible/collections/ansible_collections/networktocode/nautobot/plugins/modules/site.py)

        Creates or removes sites from Nautobot

ADDED IN: version 1.0.0 of networktocode.nautobot

OPTIONS (= is mandatory):

... OUTPUT TRIMMED ...

Step 1-7

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

---
- name: MANAGE NAUTOBOT OBJECTS - CREATE
  hosts: localhost
  tags: create
  vars:
    nautobot_host: "https://REPLACE_WITH_NAUTOBOT_IP/"
    nautobot_token: "REPLACE_WITH_NAUTOBOT_TOKEN"
  tasks:
    - name: CREATE NEW AMS03 SITE
      networktocode.nautobot.site:
        url: "{{ nautobot_host }}"
        token: "{{ nautobot_token }}"
        validate_certs: false
        name: "AMS03"
        status: "Planned"
        region: "Netherlands"
        tenant: "Nautobot Airports"
        facility: "Amsterdam Airport Schiphol"
        state: present

Note: This first task uses the site module to abstract the REST API operation of creating a new Site object in Nautobot. You get to provide some input parameters (such as name, status, region etc.) and the module does all the hard work.

Note: State present is used for create/update operations, while absent is used to delete an object.

Step 1-8

The playbook 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-9

Run the playbook and check out the results in your Nautobot web interface!

ntc@nautobot-1:~/labs/ansible$ ansible-playbook pb_manage_objects.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'

PLAY [MANAGE NAUTOBOT OBJECTS - CREATE] **************************************************

TASK [CREATE NEW AMS03 SITE] *************************************************************
changed: [localhost]

PLAY RECAP *******************************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Note: You can run the playbook again if you want, the module is idempotent so if the Site is configured as per the intent, there is nothing to do.

Step 1-10

Now to make a modification to your site object, add a new play in the same pb_manage_objects.yml playbook:

- name: MANAGE NAUTOBOT OBJECTS - UPDATE
  hosts: localhost
  tags: update
  vars:
    nautobot_host: "https://REPLACE_WITH_NAUTOBOT_IP/"
    nautobot_token: "REPLACE_WITH_NAUTOBOT_TOKEN"
  tasks:
    - name: UPDATE SITE AMS03 WITH CUSTOM FIELD
      networktocode.nautobot.site:
        url: "{{ nautobot_host }}"
        token: "{{ nautobot_token }}"
        validate_certs: false
        name: "AMS03"
        status: "Staging"
        latitude: 52.3
        longitude: 4.765
        custom_fields:
          site_type: "POP"
        state: present

Note: This play is meant to update an existing object, adding some other attributes to it (like latitude and longitude) and setting its Site Type custom field value. Each play is set up with a tag so you can run it independently.

Note: The playbook 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-11

Run the playbook with the update tag only:

ntc@nautobot-1:~/labs/ansible$ ansible-playbook pb_manage_objects.yml -t update
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'

PLAY [MANAGE NAUTOBOT OBJECTS - CREATE] **************************************************

PLAY [MANAGE NAUTOBOT OBJECTS - UPDATE] **************************************************

TASK [UPDATE SITE AMS03 WITH CUSTOM FIELD] ***********************************************
changed: [localhost]

PLAY RECAP *******************************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Step 1-12

Check out the results in your Nautobot web interface - the coordinates have enabled a button link to show you where they are on a map!

Step 1-13

Finally, you want to delete the site object, so add a new play in the same pb_manage_objects.yml playbook - no more parameters this time, as you're telling the API to delete the whole object based on its name:

- name: MANAGE NAUTOBOT OBJECTS - DELETE
  hosts: localhost
  tags: delete
  vars:
    nautobot_host: "https://REPLACE_WITH_NAUTOBOT_IP/"
    nautobot_token: "REPLACE_WITH_NAUTOBOT_TOKEN"
  tasks:
    - name: DELETE SITE AMS03
      networktocode.nautobot.site:
        url: "{{ nautobot_host }}"
        token: "{{ nautobot_token }}"
        validate_certs: false
        name: "AMS03"
        state: absent

Note: The playbook 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-14

Run the playbook with the delete tag only:

ntc@nautobot-1:~/labs/ansible$ ansible-playbook pb_manage_objects.yml -t delete
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'

PLAY [MANAGE NAUTOBOT OBJECTS - CREATE] **************************************************

PLAY [MANAGE NAUTOBOT OBJECTS - UPDATE] **************************************************

PLAY [MANAGE NAUTOBOT OBJECTS - DELETE] **************************************************

TASK [DELETE SITE AMS03] *****************************************************************
changed: [localhost]

PLAY RECAP *******************************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Step 1-15

Visit the Nautobot interface and confirm the site AMS03 is gone. Feel free to execute the three operations as many times as you like, using the tags provided to only get one of the operations at a time.

Task 2 - Use Nautobot as an Inventory Source for Ansible - REST

The Nautobot Ansible collection provides two dynamic inventory plugins for Ansible to leverage - when Nautobot is your inventory source of truth, you definitely want to get your list of devices and their metadata from it!

Here you will explore using the REST API based plugin.

Step 2-1

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

---

plugin: "networktocode.nautobot.inventory"
api_endpoint: "https://REPLACE_WITH_NAUTOBOT_IP/"
validate_certs: false
token: "REPLACE_WITH_NAUTOBOT_TOKEN"

device_query_filters:
  - region: "france" # FILL IN WITH PROPER REGION VALUE FOR YOUR SYSTEM

group_by:
  - "device_roles"
  - "platforms"

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 2-2

Take a moment to read through the inventory plugin parameters:

  • It needs details on how to access the Nautobot REST API, since that's what it uses behind the scenes to get you data.
  • Then, it is good practice to reduce the scope of the data you're getting especially with large inventories!
    • Here, you're telling the plugin to filter the devices by region.
  • Finally, Ansible works well with groups, so you can create groups on the fly using data from Nautobot.
    • Here, devices will be grouped depending on their role, but also by their platform. Devices in Ansible can belong to multiple groups at a time.

Step 2-3

To view the logical structure (hosts and groups) of the dynamic inventory, execute the ansible-inventory command:

ntc@nautobot-1:~/labs/ansible$ ansible-inventory -i nautobot_inventory.yml --graph
@all:
  |--@device_roles_distribution:
  |  |--cdg01-dist-01
  |  |--cdg02-dist-01
  |--@device_roles_edge:
  |  |--cdg01-edge-01
  |  |--cdg01-edge-02
  |  |--cdg02-edge-01
  |  |--cdg02-edge-02
  |--@device_roles_leaf:
  |  |--cdg01-leaf-01
  |  |--cdg01-leaf-02
  |  |--cdg01-leaf-03
  |  |--cdg01-leaf-04
  |  |--cdg01-leaf-05
  |  |--cdg01-leaf-06
  |  |--cdg01-leaf-07
  |  |--cdg01-leaf-08
  |  |--cdg02-leaf-01
  |  |--cdg02-leaf-02
  |  |--cdg02-leaf-03
  |  |--cdg02-leaf-04
  |  |--cdg02-leaf-05
  |  |--cdg02-leaf-06
  |  |--cdg02-leaf-07
  |  |--cdg02-leaf-08
  |--@platforms_arista_eos:
  |  |--cdg01-edge-01
  |  |--cdg01-edge-02
  |  |--cdg01-leaf-01
  |  |--cdg01-leaf-02
  |  |--cdg01-leaf-03
  |  |--cdg01-leaf-04
  |  |--cdg01-leaf-05
  |  |--cdg01-leaf-06
  |  |--cdg01-leaf-07
  |  |--cdg01-leaf-08
  |  |--cdg02-edge-01
  |  |--cdg02-edge-02
  |  |--cdg02-leaf-01
  |  |--cdg02-leaf-02
  |  |--cdg02-leaf-03
  |  |--cdg02-leaf-04
  |  |--cdg02-leaf-05
  |  |--cdg02-leaf-06
  |  |--cdg02-leaf-07
  |  |--cdg02-leaf-08
  |--@platforms_cisco_ios:
  |  |--cdg01-dist-01
  |  |--cdg02-dist-01
  |--@ungrouped:
ntc@nautobot-1:~/labs/ansible$

Note: If you're curious what it is doing and why it's taking a while, run the same command with a -v added to it - you will see all the REST API requests it is sending!

Step 2-4

Next, to view the inventory as a whole, you can run it with --list instead of --graph. That provides you with the full inventory data structure.

Execute the ansible-inventory -i nautobot_inventory.yml --list command. Output is not provided here due to its verbosity - but take a moment to scroll up in your terminal once the command finishes and look at what data is available for a particular device.

Step 2-5

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

---

- name: EXPLORE NAUTOBOT DYNAMIC INVENTORY DATA
  hosts: device_roles_edge
  connection: local
  vars:
    device_info: >
      Host: {{ inventory_hostname }}
      IP: {{ ansible_host }}
      Status: {{ status['label'] }}
      Regions: {{ ", ".join(regions) | title }}
      Model: {{ manufacturers[0] }} {{ device_types[0] }}
      Rack: {{ racks[0] }}
  tasks:
    - debug:
        var: device_info

Step 2-6

Run the playbook by providing it the newly created inventory:

ntc@nautobot-1:~/labs/ansible$ ansible-playbook -i nautobot_inventory.yml pb_rest_inventory.yml

PLAY [EXPLORE NAUTOBOT DYNAMIC INVENTORY DATA] *******************************************

TASK [debug] *****************************************************************************
ok: [cdg01-edge-01] => {
    "device_info": "Host: cdg01-edge-01 IP: 10.8.128.1 Status: Active Regions: France, Europe Model: arista dcs-7280cr2-60 Rack: cdg01-101\n"
}
ok: [cdg01-edge-02] => {
    "device_info": "Host: cdg01-edge-02 IP: 10.8.128.2 Status: Active Regions: France, Europe Model: arista dcs-7280cr2-60 Rack: cdg01-102\n"
}
ok: [cdg02-edge-01] => {
    "device_info": "Host: cdg02-edge-01 IP: 10.28.128.1 Status: Active Regions: France, Europe Model: arista dcs-7280cr2-60 Rack: cdg02-101\n"
}
ok: [cdg02-edge-02] => {
    "device_info": "Host: cdg02-edge-02 IP: 10.28.128.2 Status: Active Regions: France, Europe Model: arista dcs-7280cr2-60 Rack: cdg02-102\n"
}

PLAY RECAP *******************************************************************************
cdg01-edge-01              : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
cdg01-edge-02              : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
cdg02-edge-01              : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
cdg02-edge-02              : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Note: The groups created via the inventory plugin allow you to only run the playbook tasks against a subset of the devices. Feel free to change that around with other groups or even all!

Step 2-7

The inventory does not fetch Configuration Context data by default, since it is a potentially intensive task. Edit the nautobot_inventory.yml file and add the following line at the end:

config_context: true

Step 2-8

Add two more tasks to you pb_rest_inventory.yml playbook to print the context data and extract one particular bit of information about LLDP from it:

    - debug:
        var: config_context
    - debug:
        msg: "LLDP Enabled: {{ config_context[0]['lldp'] }}"

Step 2-9

Save and run the playbook again - the output will be a bit verbose, so take your time to scroll through it and identify the new information:

ntc@nautobot-1:~/labs/ansible$ ansible-playbook -i nautobot_inventory.yml pb_rest_inventory.yml

PLAY [EXPLORE NAUTOBOT DYNAMIC INVENTORY DATA] *******************************************

... OUTPUT TRIMMED ...

TASK [debug] *****************************************************************************
ok: [cdg01-edge-01] => {
    "msg": "LLDP Enabled: True"
}
ok: [cdg01-edge-02] => {
    "msg": "LLDP Enabled: True"
}
ok: [cdg02-edge-01] => {
    "msg": "LLDP Enabled: True"
}
ok: [cdg02-edge-02] => {
    "msg": "LLDP Enabled: True"
}

PLAY RECAP *******************************************************************************
cdg01-edge-01              : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
cdg01-edge-02              : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
cdg02-edge-01              : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
cdg02-edge-02              : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Task 3 - Use Nautobot as an Inventory Source for Ansible - GraphQL

Depending on what sort of related data you need to get from Nautobot for your Ansible inventory, the GraphQL API might be much faster than the REST API. You will now use the GraphQL inventory plugin offered by the collection.

Step 3-1

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

---

plugin: "networktocode.nautobot.gql_inventory"
api_endpoint: "https://REPLACE_WITH_NAUTOBOT_IP/"
validate_certs: false
token: "REPLACE_WITH_NAUTOBOT_TOKEN"

filters:
  region: "france" # FILL IN WITH PROPER REGION VALUE FOR YOUR SYSTEM

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 3-2

Take a moment to read through the inventory plugin parameters:

  • It needs details on how to access the Nautobot GraphQL API, since that's what it uses behind the scenes to get you data.
  • Then, it is good practice to reduce the scope of the data you're getting especially with large inventories!
    • Here, you're telling the plugin to filter the devices by region.

Step 3-3

To view the logical structure (hosts and groups) of the dynamic inventory, execute the ansible-inventory command:

ntc@nautobot-1:~/labs/ansible$ ansible-inventory -i nautobot_inventory_gql.yml --graph
@all:
  |--@ungrouped:
  |  |--cdg01-dist-01
  |  |--cdg01-edge-01
  |  |--cdg01-edge-02
  |  |--cdg01-leaf-01
  |  |--cdg01-leaf-02
  |  |--cdg01-leaf-03
  |  |--cdg01-leaf-04
  |  |--cdg01-leaf-05
  |  |--cdg01-leaf-06
  |  |--cdg01-leaf-07
  |  |--cdg01-leaf-08
  |  |--cdg02-dist-01
  |  |--cdg02-edge-01
  |  |--cdg02-edge-02
  |  |--cdg02-leaf-01
  |  |--cdg02-leaf-02
  |  |--cdg02-leaf-03
  |  |--cdg02-leaf-04
  |  |--cdg02-leaf-05
  |  |--cdg02-leaf-06
  |  |--cdg02-leaf-07
  |  |--cdg02-leaf-08

Step 3-4

To see the full inventory, run the command with --list:

ntc@nautobot-1:~/labs/ansible$ ansible-inventory -i nautobot_inventory_gql.yml --list
{
    "_meta": {
        "hostvars": {
            "cdg01-dist-01": {
                "ansible_host": "cdg01-dist-01",
                "ansible_network_os": "cisco.ios.ios"
            },
            "cdg01-edge-01": {
                "ansible_host": "10.8.128.1",
                "ansible_network_os": "arista.eos.eos"
            },

... OUTPUT TRIMMED ...

Note: What you're going to immediately notice is that the data provided by the plugin is very sparse by default - there's just the hostname and the ansible_network_os!

Step 3-5

Add the following parameters to the inventory file:

  • query adds more requested data to the GraphQL query sent by the plugin.
  • group_by tells the plugin to create Ansible groups based on the device role.
  • additional_variables tells the plugin to add the listed data as inventory variables for each host.
---

plugin: "networktocode.nautobot.gql_inventory"
api_endpoint: "https://REPLACE_WITH_NAUTOBOT_IP/"
validate_certs: false
token: "REPLACE_WITH_NAUTOBOT_TOKEN"

filters:
  region: "france" # FILL IN WITH PROPER REGION VALUE FOR YOUR SYSTEM

query:
  site:
    slug:
  device_role:
    slug:

group_by:
  - "device_role.slug"

additional_variables:
  - "device_role"
  - "site"
  - "status"

Step 3-6

To see the full inventory again, run the command with --list:

ntc@nautobot-1:~/labs/ansible$ ansible-inventory -i nautobot_inventory_gql.yml --list
{
    "_meta": {
        "hostvars": {
            "cdg01-dist-01": {
                "ansible_host": "cdg01-dist-01",
                "ansible_network_os": "cisco.ios.ios",
                "device_role": {
                    "slug": "distribution"
                },
                "site": {
                    "slug": "cdg01"
                },
                "status": {
                    "name": "Active"
                }
            },
... OUTPUT TRIMMED ...

Note: You can now see the device role, the site it belongs to, and its status in the Ansible inventory variables.

Step 3-7

You can use this inventory just like the previous one with any playbook - if you have spare time at the end, take the pb_rest_inventory.yml playbook and adapt it to work with the new data provided by the nautobot_inventory_gql.yml inventory!