Ansible: Using Facts Modules to do Updates

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Announcements
Please sign in to see details of an important advisory in our Customer Advisories area.
L5 Sessionator

So you're using Ansible, and you can add objects easily. You have your playbooks in order and can have no problems adding new configuration to a firewall. But how do you go about updating existing config without having to re-specify everything from scratch?

 

Let's pretend that we have an address object that we want to delete, but before we can delete this address object, we need to remove it from any address groups it might belong to.

 

Before we begin, it's worth noting that although this example is only focusing on address groups, the logic is the same for other areas such as NAT or security rules. Right now, the proper way to get the Palo Alto Networks Ansible modules is using the Ansible Galaxy role, located here.  As of right now, the most recent version of the role is 2.2.2.

 

So, we start out our playbooks all the same way, by making sure to use this role (note that your hosts specification might be different):

 

- name: Network Playbook
  hosts: fw
  connection: local
  gather_facts: false

  roles:
    - role: PaloAltoNetworks.paloaltonetworks

 

Now, let's define a variable that will have the address object we are looking to remove from all address groups. For simplicity's sake, let's define this directly in our playbook. Let's say the address object we are deleting is named ntp2. So let's set this as a variable named rmadr:

 

  vars:
    rmadr: 'ntp2'

 

After that, we need to get the credentials of the firewall we're going to manage. For me, I have the credentials saved in a YAML file named vars.yml that looks like this:

 

aws_provider:
    ip_address: '127.0.0.1'
    username: 'admin'
    password: 'admin'

 

So, to import that, I'll use the include_vars task to pull that into my playbook like this:

 

  tasks:
  - name: Grab auth creds
    include_vars: 'vars.yml'
    no_log: 'yes'

 

Now that I've done all my setup, let's grab all of my address groups.  This is accomplished by using an appropriate facts module, in this case, panos_object_facts:

 

  - name: Get all address groups and their config
    panos_object_facts:
      provider: '{{ aws_provider }}'
      object_type: 'address-group'
      name_regex: '.*'
    register: ag

 

A couple things you'll note about the above:

  • We specify our credentials using the provider dict pulled in from the include_vars task
  • Since we want all of the objects, we just give a regex that will match all address groups
  • We are saving the results of this facts module to a variable named ag
  • Using the documentation for panos_object_facts, we can see that the list of objects are stored in the objects output

Now that we have all of our address groups, we need to remove the address object from all groups that use it. We can accomplish this by using a combination of Jinja2 templating functions as well as Ansible playbook filters, along with the appropriate PAN-OS module.  Let's take this one step at a time.

 

First things first, we will be using the panos_address_group module, so let's start with that:

 

  - name: Remove address object from all groups
    panos_address_group:
      provider: '{{ aws_provider }}'

 

Since we're looping over a list, we will be using the special variable item as the stand-in for param names, so our task config will look like this so far:

 

      name: '{{ item.name }}'
      description: '{{ item.description }}'
      static_value: '{{ item.static_value }}'
      tag: '{{ item.tag }}'
      commit: false

 

Now let's do the address object removal. The static address objects are all saved as a list in static_value, so we'll use difference() to remove it from the static value. Since rmadr is a string and difference() is expecting a list, we will have just make a single list out of the rmadr variable by putting it in square brackets. So we replace the above static_value line with what ends up looking like this:

 

      static_value: '{{ item.static_value | difference([rmadr]) }}'

 

Next let's specify what we're looping over. Remember, we saved the list of address groups to the variable ag, and the output is objects, so let's specify that next:

 

    loop: '{{ ag.objects }}'

 

Now we need to specify the criteria to update address groups. Some care is needed here as there are two types of address groups: static and dynamic, and dynamic address groups don't have a static_value defined at all. So we need to have two loop criteria here: first is that it is actually a static address group to beging with, and second is that the rmadr value should actually be in the static criteria. The first check is necessary as you will get an error if you try to treat a None as a list, the second criteria is there to help speed up overall playbook execution. So it will look like this:

 

    when:
      - item.static_value
      - rmadr in item.static_value

 

Next, we need to handle the None values properly. There might not be a description and there might not be any administrative tags, so we need to say, "if these things aren't set, then don't specify them in the playbook execution." So we'll use default() to do this and pass in the optional second param as true to say, "default against the trutiness of this value, not just if it's defined or not." Updating the previous lines in this task to take this into account, this is what we end up with:

 

      description: '{{ item.description | default(omit, true) }}'
      tag: '{{ item.tag | default(omit, true) }}'

 

Finally, when looping over things, Ansible will by default flood your screen with the exact criteria that the loop is being executed with for each item. We don't need all that. Let's just display the name of the address group instead:

 

    loop_control:
      label: '{{ item.name }}'

 

Then to finish it all up, we need to actually remove the address object. This is straightforward enough:

 

  - name: Finally remove the address object
    panos_address_object:
      provider: '{{ aws_provider }}'
      name: rmadr
      state: 'absent'
      commit: false

 

The final playbook will look like this:

 

- name: Network Playbook
  hosts: fw
  connection: local
  gather_facts: false

  vars:
    rmadr: 'ntp2'

  roles:
    - role: PaloAltoNetworks.paloaltonetworks

  tasks:
  - name: Grab auth creds
    include_vars: 'vars.yml'
    no_log: 'yes'

  - name: Get all address groups and their config
    panos_object_facts:
      provider: '{{ aws_provider }}'
      object_type: 'address-group'
      name_regex: '.*'
    register: ag

  - name: Remove address object from all groups
    panos_address_group:
      provider: '{{ aws_provider }}'
      name: '{{ item.name }}'
      description: '{{ item.description | default(omit, true) }}'
      static_value: '{{ item.static_value | difference([rmadr]) }}'
      tag: '{{ item.tag | default(omit, true) }}'
      commit: false
    loop: '{{ ag.objects }}'
    loop_control:
      label: '{{ item.name }}'
    when:
      - item.static_value
      - rmadr in item.static_value

  - name: Finally remove the address object
    panos_address_object:
      provider: '{{ aws_provider }}'
      name: rmadr
      state: 'absent'
      commit: false

 

For further reading, please refer to Ansible filtering and Jinja2 templating functions.  Happy templating with Ansible!

5 Comments