• Skip to main content
  • Skip to footer

Andrew Roderos

Networking and Security

  • Blog
  • Resources
    • Book List
    • Freebies
  • About
  • Contact

Palo Alto Ansible Network Automation: Automating VSYS name management

08/12/2024 by Andrew Roderos Leave a Comment

  • Share on Twitter Share on Twitter
  • Share on Facebook Share on Facebook
  • Share on LinkedIn Share on LinkedIn
  • Share on Reddit Share on Reddit
  • Share via Email Share via Email

My Ansible network automation journey began two years ago, but it’s been an off-and-on process due to changing priorities at work. Initially, I created a proof-of-concept Ansible playbook to configure a site-to-site VPN on a Cisco router using a network configuration template. In this blog post, I will share my experience of converting a Palo Alto network configuration template into an Ansible playbook.

This post contains affiliate links, which means I may earn a commission if you click through and make a purchase. This comes at no additional cost to you. Full disclosure here.

Managing expectations

Although I am not an expert in Ansible or network automation, I am excited to share my learning journey as I develop Ansible playbooks to automate manual processes. This post focuses on a specific section of the network configuration template: configuring the virtual system (VSYS) name.

Prerequisites

Here are the prerequisites for you to follow along:

  • Fully functioning Ansible environment.
  • Palo Alto Networks firewall with multi-VSYS capability and license.
  • Completion of the Ansible for PAN-OS getting started tutorial.
  • Basic understanding of PAN’s API.

Objective

The primary objective of the playbook is to verify and set the VSYS name according to user input. After deliberation, we decided to develop two separate playbooks to achieve this goal. Currently, I am focusing on creating separate playbooks for each configuration template section. Eventually, I will create Ansible roles because I see opportunities for “code” reuse.

Gathering VSYS name

This playbook aims to compile a comprehensive list of VSYS names from the target firewall, categorizing them into two distinct lists: available and unavailable VSYS names. The ultimate goal is to verify user-inputted VSYS names against these lists, generating a response whether the name is available for use on the firewall or not.

I have found the Palo Alto Networks Ansible collection somewhat limited in its capabilities. Specifically, I could not find a straightforward way to retrieve a list of VSYS without using XPath. It is becoming evident that I will utilize XPath as the primary method for my other playbooks, which involve checking various configurations.

The playbook

I am sure there are other ways of accomplishing the same thing, but this playbook is what I came up with to achieve the goal, and my initial tests have been successful. However, we will conduct further testing before considering it production-ready to ensure its reliability and effectiveness.

Without further delay, here is the playbook I wrote.

---
- name: Check virtual system names
  hosts: "{{ panfw }}"
  gather_facts: false
  connection: local

  collections:
    - paloaltonetworks.panos

  tasks:
    - paloaltonetworks.panos.panos_facts:
        provider: "{{ palo_provider }}"
        gather_subset: ['config']

    - community.general.xml:
        xmlstring: "{{ ansible_facts['net_config'] }}"
        xpath: "devices/entry[@name='localhost.localdomain']/vsys/entry/display-name"
        content: text
      register: display_names

    - ansible.builtin.set_fact:
        vsys_names: "{{ display_names['matches'] }}"

    - ansible.builtin.set_fact:
        vsys_names_short: []

    - ansible.builtin.set_fact:
        vsys_names_short: "{{ vsys_names_short + [item['display-name']] }}"
      loop: "{{ vsys_names }}"

    - ansible.builtin.set_fact:
        available_vsys_list: "{{ vsys_names_short | select('match', '^vsys[0-9]{1,3}$') | list + vsys_names_short | select('match', '^vs[0-9]{1,3}$') | list  }}"

    - ansible.builtin.set_fact:
        not_available_vsys_list: "{{ vsys_names_short | reject('match', '^vsys[0-9]{1,3}$') | reject('match', '^vs[0-9]{1,3}$') | list }}"

    - block:
        - community.general.xml:
            xmlstring: "{{ ansible_facts['net_config'] }}"
            xpath: "devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys{{ vsysnum }}']/display-name"
            content: text
          register: vsys_display_name

      rescue:
        - ansible.builtin.set_fact:
            vsys_display_name: "vsys{{ vsysnum }}"

        - ansible.builtin.set_fact:
            available_vsys_list: "{{ [vsys_display_name] + available_vsys_list }}"

    - ansible.builtin.set_fact:
        vsys_display_name: "{{ vsys_display_name['matches'] | map(attribute='display-name') | join(', ') }}"
      when: vsys_display_name is defined and 'matches' in vsys_display_name

    - ansible.builtin.debug:
        msg: "The VSYS name of {{ vsysname }} is available in VSYS{{ vsysnum }}"
      when: >
        - (vsys_display_name in available_vsys_list and vsysname not in not_available_vsys_list)

    - ansible.builtin.debug:
        msg: "The VSYS name of {{ vsysname }} is not available in VSYS{{ vsysnum }}"
      when: >
        - (vsys_display_name in available_vsys_list and vsysname in not_available_vsys_list) or
        - (vsys_display_name in not_available_vsys_list and vsysname not in available_vsys_list)

Do you find this content useful?
If so, consider buying me a coffee!

☕️ Buy me a coffee

Breaking it down

Let’s break down each section.

---
- name: Gather virtual system names
  hosts: '{{ panfw }}'
  gather_facts: false
  connection: local

  collections:
    - paloaltonetworks.panos

The playbook starts with specifying the name of the playbook. Then, specifies the host or group of hosts to run the playbook against. Since the {{ panfw }} is a variable, the actual value will be replaced with the real value at runtime. In this case, the user will input the name of the target firewall by specifying it in the ansible-playbook -e command option.

The following line tells Ansible not to gather facts about the hosts before running the playbook. Facts are information about the hosts, such as their operating system, IP addresses, etc.

Want to learn more about Ansible? Ansible for DevOps

In line five, this specifies the connection method to use when communicating with the hosts. In this case, local means that Ansible will run the tasks on the local machine, rather than connecting to the hosts remotely.

Following line five, this tells Ansible to use paloaltonetworks.panos collection, which is a collection of modules and plugins for managing Palo Alto Networks firewalls.

First task

The first task is to gather configuration-related information from the device. In this case, I used the paloaltonetworks.panos.panos_facts module, which is designed to gather factual information about the PAN firewall.

  tasks:
    - paloaltonetworks.panos.panos_facts:
        provider: "{{ palo_provider }}"
        gather_subset: ['config']

Second task

The second task utilizes the community.general.xml module to parse XML data, which complements the earlier task of gathering configuration facts from a PAN firewall. Since the previous task gathers configuration facts from the PAN firewall, the output of facts is in an XML format.

    - community.general.xml:
        xmlstring: "{{ ansible_facts['net_config'] }}"
        xpath: "devices/entry[@name='localhost.localdomain']/vsys/entry/display-name"
        content: text
      register: display_names

Moreover, the task extracts the display-name of all virtual systems (VSYS) within the device’s XML configuration. This information can be vital for identifying or managing that particular VSYS.

Below is the output of the display_names variable. As you can see in the matches section, it displayed all the VSYS names configured in the firewall.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "display_names": {
        "actions": {
            "namespaces": {},
            "state": "present",
            "xpath": "devices/entry[@name='localhost.localdomain']/vsys/entry/display-name"
        },
        "changed": false,
        "count": 74,
        "failed": false,
        "matches": [
            {
                "display-name": "vsys1"
            },
            {
                "display-name": "andrew"
            },
            {
                "display-name": "vs4"
            },
            
		<--- Output ommitted for brevity --->

        ],
        "msg": 74,
        "xmlstring": "<?xml version='1.0' encoding='UTF-8'?>\n<config version=\"10.2.0\" detail-version=\"10.2.8\" urldb=\"paloaltonetworks\">\n  <mgt-config>\n    <users>\n
        
    <--- Output ommitted for brevity --->
    
    }
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
pa5250             : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Third task

This task allows me to assign the extracted display-name values from the XML parsing step to a new variable named vsys_names. This task makes it easier to reference this data in subsequent tasks.

    - ansible.builtin.set_fact:
        vsys_names: "{{ display_names['matches'] }}"

The {{ display_names['matches'] }} is a Jinja2 expression that references the matches key within the display_names variable, which I registered in the previous task. The matches key within the display_names variable contains all elements that match the XPath query I specified in the second task.

Below is the output of the vsys_names variable. As you can see, the display no longer includes the xmlstring section.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "vsys_names": [
        {
            "display-name": "vsys1"
        },
        {
            "display-name": "andrew"
        },
        {
            "display-name": "vs4"
        },
        

<--- Output omitted for brevity --->

    ]
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
pa5250             : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Fourth task

This task initializes an empty list and assigns it to the vsys_names_short variable. Initializing an empty list lets us prepare it for future use within the playbook. It is a common practice to ensure that the variable has a defined starting state.

    - ansible.builtin.set_fact:
        vsys_names_short: []

As you can see, the vsys_names_short has an empty list. The [] signifies an empty list.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "vsys_names_short": []
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
pa5250             : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Fifth task

This task takes each item in the vsys_names list, extracts the display-name attribute, and appends it to the vsys_names_short list. Moreover, I used the loop function to keep updating the list as it goes through each display-name attribute.

    - ansible.builtin.set_fact:
        vsys_names_short: "{{ vsys_names_short + [item['display-name']] }}"
      loop: "{{ vsys_names }}"

As you can see, the vsys_names_short variable now has a list of VSYS names.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "vsys_names_short": [
        "vsys1",
        "andrew",
        "vs4",
        
<--- Output omitted for brevity --->

    ]
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
pa5250             : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Sixth task

This task will filter and create a new list based on two specific patterns: vsys and vs followed by one to three digits. For example, strings like vsys1, vsys20, vsys123, vs1, vs20, and vs123 would match. Then, the two filtered lists are concatenated.

    - ansible.builtin.set_fact:
        available_vsys_list: "{{ vsys_names_short | select('match', '^vsys[0-9]{1,3}$') | list + vsys_names_short | select('match', '^vs[0-9]{1,3}$') | list  }}"

As you can see, the available_vsys_list variable only contains the VSYS names based on the specific pattern defined in our regex syntax.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "available_vsys_list": [
        "vsys1",
        "vsys5",

<--- Output omitted for brevity --->

        "vs4"
    ]
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
pa5250             : ok=7    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Seventh task

This task will filter and create a new list that does not match either vsys and vs followed by one to three digits. Essentially, I am just negating the pattern used in the previous task.

    - ansible.builtin.set_fact:
        not_available_vsys_list: "{{ vsys_names_short | reject('match', '^vsys[0-9]{1,3}$') | reject('match', '^vs[0-9]{1,3}$') | list }}"

As you can see, the not_available_vsys_list variable only contains the VSYS names based on the specific pattern defined in our regex syntax.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "not_available_vsys_list": [
        "andrew"
    ]
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
pa5250             : ok=8    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Eighth task

For simplicity’s sake, I’m consolidating these block and rescue tasks into this section. They are technically three different tasks, but the two under rescue block may or may not get executed.

    - block:
        - community.general.xml:
            xmlstring: "{{ ansible_facts['net_config'] }}"
            xpath: "devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys{{ vsysnum }}']/display-name"
            content: text
          register: vsys_display_name

      rescue:
        - ansible.builtin.set_fact:
            vsys_display_name: "vsys{{ vsysnum }}"

        - ansible.builtin.set_fact:
            available_vsys_list: "{{ [vsys_display_name] + available_vsys_list }}"

In Ansible, the block directive creates a logical group of tasks that execute together. When used with the rescue block, it acts as an error-handling mechanism.

How does it work? All tasks, in this case, just one task, in the main block will attempt to run. If all of them do not encounter an error, then the tasks in the rescue block will not run. If any of the tasks within the block fails, then the tasks in the rescue block will run. You can read more here.

Learn more about Ansible Network Automation

What does this whole block do? The task within the block retrieves the VSYS name associated with a user-input VSYS number, proceeding to the next task if successful. If the VSYS name is blank, it triggers an error, executing tasks within the rescue block to handle the situation. Essentially, it’s a simple retrieval and error-handling mechanism.

When the rescue block is triggered, it sets the vsys_display_name variable to a default value of vsys followed by the user-input VSYS number (e.g., “vsys50”). This variable is then added to the list of available VSYS, effectively creating a fallback option when the original VSYS name retrieval fails.

Error handling in action

The output below reveals that the task was unsuccessful, prompting the rescue block to execute the tasks.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250 vsysnum=2"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})
ok: [pa5250] => (item={'display-name': 'vsys5'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [Display VSYS name of a specific VSYS] **************************************************************************************************************************************************************************************************
fatal: [pa5250]: FAILED! => {"changed": false, "msg": "Xpath devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys2']/display-name does not reference a node!"}

TASK [set vsys_display_name] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [set available_vsys_list] ***************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "msg": [
        {
            "vsys_display_name": "vsys2"
        },
        {
            "available_vsys_list": [
                "vsys2",
                "vsys1",
                "vsys5",

<--- Output omitted for brevity --->

                "vs4"
            ]
        }
    ]
}

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

Ninth task

This task extracts the display-name attribute from the vsys_display_name variable and converts it into a comma-separated string. However, it will only execute if specific conditions are satisfied because of the conditional logic.

    - ansible.builtin.set_fact:
        vsys_display_name: "{{ vsys_display_name['matches'] | map(attribute='display-name') | join(', ') }}"
      when: vsys_display_name is defined and 'matches' in vsys_display_name

Before extraction

Let’s take a look at the vsys_display_name variable before the extraction.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250 vsysnum=1"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [Display VSYS name of a specific VSYS] **************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "msg": [
        {
            "vsys_display_name": {
                "actions": {
                    "namespaces": {},
                    "state": "present",
                    "xpath": "devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/display-name"
                },
                "changed": false,
                "count": 1,
                "failed": false,
                "matches": [
                    {
                        "display-name": "vsys1"
                    }
                ],
                "msg": 1,
                "xmlstring": "<?xml version='1.0' encoding='UTF-8'?>\n<config version=\"10.2.0\" detail-version=\"10.2.8\" urldb=\"paloaltonetworks\">\n  <mgt-config>\n
                
            <--- Output omitted for brevity --->
            
            }
        }
    ]
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
pa5250             : ok=9    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

After extraction

Here’s what it looks like when the task executes.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250 vsysnum=1"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})
ok: [pa5250] => (item={'display-name': 'vsys5'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [Display VSYS name of a specific VSYS] **************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "vsys_display_name": "vsys1"
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
pa5250             : ok=10   changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Missing variable and key

Ansible skipped the task because the eighth task failed to execute, so while vsys_display_name is defined, there was no matches key in the variable.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250 vsysnum=2"

PLAY [Gather virtual system names] ***********************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] ****************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})
ok: [pa5250] => (item={'display-name': 'vsys5'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [Display VSYS name of a specific VSYS] **************************************************************************************************************************************************************************************************
fatal: [pa5250]: FAILED! => {"changed": false, "msg": "Xpath devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys2']/display-name does not reference a node!"}

TASK [set vsys_display_name] *****************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [set available_vsys_list] ***************************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************************************
skipping: [pa5250]

TASK [ansible.builtin.debug] *****************************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "vsys_display_name": "vsys2"
}

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

Last two tasks

The final two tasks will display messages indicating whether the VSYS name is available or unavailable. To accomplish this, a logic mechanism is necessary to determine VSYS name availability. This is where the when conditional statement plays a crucial role, enabling the evaluation of VSYS name availability and triggering the corresponding message display.

    - ansible.builtin.debug:
        msg: "VSYS name of {{ vsysname }} is available to be used in VSYS{{ vsysnum }}"
      when: >
        - (vsys_display_name in available_vsys_list and vsysname not in not_available_vsys_list)

    - ansible.builtin.debug:
        msg: "VSYS name of {{ vsysname }} is not available to be used in VSYS{{ vsysnum }}"
      when: >
        - (vsys_display_name in available_vsys_list and vsysname in not_available_vsys_list) or
        - (vsys_display_name in not_available_vsys_list and vsysname not in available_vsys_list)

The first debug task verifies the availability of the VSYS name and prints a confirmation message. The task will only execute if vsys_display_name is found in available_vsys_list and the vsysname is not found in not_available_vsys_list.

The second debug task confirms the unavailability of the VSYS name and prints a confirmation message. This task has two conditions, at least one of which must be true for it to execute. If neither condition is met, the task is skipped.

Related: Automating IP address validation

The first condition verifies if vsys_display_name is present in the available_vsys_list and vsysname is present in the not_available_vsys_list.

The second condition verifies if vsys_display_name is present in the not_available_vsys_list and vsysname is not present in the available_vsys_list.

Test #1

In this test, I selected a VSYS name that wasn’t present on any of the lists. As expected, VSYS1 was successfully assigned the name vsys1 and was correctly marked as available.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250 vsysnum=1 vsysname=networkjutsu"

PLAY [Gather virtual system names] ************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] *****************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] ******************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [Display VSYS name of a specific VSYS] ***************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] ******************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "msg": "VSYS name of networkjutsu is available to be used in VSYS1"
}

TASK [ansible.builtin.debug] ******************************************************************************************************************************************************************************************************
skipping: [pa5250]

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

Test #2

In this test, I selected a VSYS name not found on any of the lists. However, VSYS3 was assigned the name andrew, which exists in the not_available_vsys_list, resulting in a match of the second debug task.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250 vsysnum=3 vsysname=networkjutsu"

PLAY [Gather virtual system names] ************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] *****************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] ******************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [Display VSYS name of a specific VSYS] ***************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] ******************************************************************************************************************************************************************************************************
skipping: [pa5250]

TASK [ansible.builtin.debug] ******************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "msg": "VSYS name of networkjutsu is not available to be used in VSYS3"
}

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

Test #3

In this test, I selected a VSYS name from the not_available_vsys_list and assigned it to VSYS1, which was named vsys1. As expected, it was correctly marked as unavailable.

$ ansible-playbook pan-check-vsys-name.yml -e "panfw=pa5250 vsysnum=1 vsysname=andrew"

PLAY [Gather virtual system names] ************************************************************************************************************************************************************************************************

TASK [paloaltonetworks.panos.panos_facts] *****************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [community.general.xml] ******************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250] => (item={'display-name': 'vsys1'})
ok: [pa5250] => (item={'display-name': 'andrew'})
ok: [pa5250] => (item={'display-name': 'vs4'})

<--- Output omitted for brevity --->

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [Display VSYS name of a specific VSYS] ***************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.set_fact] ***************************************************************************************************************************************************************************************************
ok: [pa5250]

TASK [ansible.builtin.debug] ******************************************************************************************************************************************************************************************************
skipping: [pa5250]

TASK [ansible.builtin.debug] ******************************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "msg": "VSYS name of andrew is not available to be used in VSYS1"
}

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

Configuring VSYS name

After completing the check, it is time to configure the VSYS name. As you become more familiar with PAN’s API, making changes to the firewall will become increasingly straightforward when you need to use the panos_config_element module.

---

- name: Set virtual system name
  hosts: '{{ panfw }}'
  gather_facts: false
  connection: local

  collections:
    - paloaltonetworks.panos

  tasks:
    - panos_config_element:
        provider: '{{ palo_provider }}'
        xpath: "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys{{ vsysnum }}']/display-name"
        edit: true
        element: '<display-name>{{ vsysname }}</display-name>'
      notify:
        - Commit firewall
        - Gather PAN-OS facts
        - Get virtual system name
        - Extract virtual system name
        - Display virtual system name
        
  handlers:
    - name: Commit firewall
      paloaltonetworks.panos.panos_commit_firewall:
        provider: '{{ palo_provider }}'

    - name: Gather PAN-OS facts
      paloaltonetworks.panos.panos_facts:
        provider: "{{ palo_provider }}"
        gather_subset: ['config']

    - name: Get virtual system name
      community.general.xml:
        xmlstring: "{{ ansible_facts['net_config'] }}"
        xpath: "devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys{{ vsysnum }}']/display-name"
        content: text
      register: vsys_display_name

    - name: Extract virtual system name
      ansible.builtin.set_fact:
        vsys_display_name: "{{ vsys_display_name['matches'] | map(attribute='display-name') | join(', ') }}"

    - name: Display virtual system name
      ansible.builtin.debug:
        msg: "The VSYS name has been set to {{ vsys_display_name }}."

Objective

This playbook is designed to accomplish a straightforward yet crucial task: setting the VSYS name according to user input, committing the changes to ensure the update is saved, and verifying that the VSYS name has been successfully set to the user’s specified value.

Breaking it down

I will skip directly to the task section, as the first section remains unchanged from the previous playbook. The only modification made was renaming the playbook itself.

Task

In the task section, there is only one task and the task uses the panos_config_element module to edit the display name of the specified VSYS. Additionally, I used the notify keyword to trigger the handler. Read about the handler here.

The tasks listed under the handler section will only be executed when a change is made on the target machine. Specifically, five handler names are designated to receive notifications when a task change occurs.

Handler

The first task in the handler section uses the panos_commit_firewall module, which is used to commit the changes to the firewall.

The remaining tasks are identical to the ones previously described in the first playbook, except for the last one.

Seeing it in action

Let’s execute the playbook by assigning an unused VSYS name to an available VSYS.

$ ansible-playbook configure-vsys-name.yml -e "panfw=pa5250 vsysnum=1 vsysname=networkjutsu"

PLAY [Set virtual system name] ****************************************************************************************************************************************************************************************************

TASK [panos_config_element] *******************************************************************************************************************************************************************************************************
changed: [pa5250]

RUNNING HANDLER [Commit firewall] *************************************************************************************************************************************************************************************************
changed: [pa5250]

RUNNING HANDLER [Gather PAN-OS facts] *********************************************************************************************************************************************************************************************
ok: [pa5250]

RUNNING HANDLER [Get virtual system name] *****************************************************************************************************************************************************************************************
ok: [pa5250]

RUNNING HANDLER [Extract virtual system name] *************************************************************************************************************************************************************************************
ok: [pa5250]

RUNNING HANDLER [Display virtual system name] *************************************************************************************************************************************************************************************
ok: [pa5250] => {
    "msg": "The VSYS name has been set to networkjutsu."
}

PLAY RECAP ************************************************************************************************************************************************************************************************************************
pa5250             : ok=6    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Let’s execute the playbook using an existing VSYS name and see the results.

$ ansible-playbook configure-vsys-name.yml -e "panfw=pa5250 vsysnum=1 vsysname=andrew"

PLAY [Set virtual system name] ***************************************************************************************************************************************************************************************************

TASK [panos_config_element] ******************************************************************************************************************************************************************************************************
fatal: [pa5250]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": false, "msg": " display-name 'andrew' is already in use"}

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

Given the failure in the results, it’s crucial to conduct thorough checks. As this is just one part of the PAN configuration template, we’re prioritizing caution and verifying everything before moving forward with configurations.

Let’s execute the playbook with the existing VSYS name and number assignment, which should result in no changes or updates.

$ ansible-playbook configure-vsys-name.yml -e "panfw=pa5250 vsysnum=1 vsysname=vsys1"

PLAY [Set virtual system name] ****************************************************************************************************************************************************************************************************

TASK [panos_config_element] *******************************************************************************************************************************************************************************************************
ok: [pa5250]

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

Final words

Network automation can be both challenging and rewarding. My Ansible journey particularly in automating our PAN configuration templates, has been a testament to that. The playbook I shared in this post is a product of iterative learning and tinkering. Using Ansible to convert manual processes into an automated fashion has been immensely satisfying.

In this post, I discussed the creation and functionality of an Ansible playbook designed to manage VSYS names on a Palo Alto firewall. I hope this guide has provided valuable insights and practical steps for those looking to automate their network configurations.

Resources

I took Kirk Byers’ Ansible Network Automation Premium Package course last year, which was helpful despite not covering PAN specifically. Kirk has also been very helpful in answering my Ansible-related questions. I highly recommend his class. Get the package with community forum access if you can afford it.

Ansible for DevOps: Although I haven’t personally read this book, it’s been highly recommended by many. While it’s not specifically focused on networking, it’s still worth considering. You can also check out the author’s Ansible 101 YouTube series for a comprehensive introduction.

Network Automation Cookbook: I have not read this book cover-to-cover, but I did review some of the playbooks. Unfortunately, the author does not provide detailed explanations for many of them, which was a drawback for me. Nevertheless, the book might inspire you to explore new automation ideas and provide some useful playbooks for your daily operations.

ChatGPT has been quite helpful in writing some of the Jinja2 syntax. In fact, it even assisted me in identifying an issue with one of my tasks.

Disclosure

AndrewRoderos.com is a participant of a few referral programs, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to company websites.

Related posts

Ansible: Accepting user input variables
Palo Alto Ansible Network Automation: Automating VSYS name management

BUY ME COFFEE

Did you find this content useful?
If so, show your appreciation by buying me a coffee!

☕️ Buy me a coffee

  • Share on Twitter Share on Twitter
  • Share on Facebook Share on Facebook
  • Share on LinkedIn Share on LinkedIn
  • Share on Reddit Share on Reddit
  • Share via Email Share via Email

Filed Under: Automation Tagged With: Ansible, Network Automation, Network Security, Palo Alto Networks, PAN-OS, PANOS

About Andrew Roderos

I am a network security engineer with a passion for networking and security. Follow me on Twitter, LinkedIn, and Instagram.

Footer

WANT TO REACH ME?

Let’s talk!

CONTACT ME

Copyright © 2019–2026 · Andrew Roderos · All Rights Reserved · Privacy Policy · Terms of Use