SituAnGel
SituAnGel

Reputation: 45

How to use nested loops with sequence and a list and how get around of curly braces in Ansible

I have a code to backup config using the ios_config module. I used ios_facts to get the hostname of devices and I want to use it to put the backup file in a similarly named folder and also use it in the file name itself.

In the last task of my code, I need to loop through two items - the sequence from 0 to 1(or how many items are in my inventory) as I need to access the hostname in the results and use it in the backup options, and also loop through my inventory of devices which I extracted from a csv file. I am aware of the rule of double curly braces but I do not know how to get around it.

---
- hosts: localhost
  gather_facts: false

  tasks:
    - name: Block
      block:
          - name: Use CSV

            csv_to_facts:
              src: '{{playbook_dir}}/NEW/Inventory.csv'
              vsheets:
                - INFO:
                    - IP
                    - OS

          - debug:
              msg: '{{item.IP}}'
            loop: '{{INFO}}'

          - name: Create Inventory
            add_host:
              hostname: '{{item.IP}}'
              ansible_network_os: '{{item.OS}}'
              ansible_user: cisco
              ansible_ssh_pass: cisco
              ansible_connection: network_cli
              ansible_become: yes
              ansible_become_method: enable
              groups: group_01
            loop: '{{INFO}}'


          - name: Gather Facts (IOS)
            ios_facts:
            register: ios_facts_loop
            delegate_to: '{{item}}'
            loop: "{{groups['group_01']}}"


          - name: Backup Switch (IOS)
            ios_config:
              backup: yes
              backup_options:
                dir_path:  "tmp/backups/{{ ios_facts_loop.results.{{item[0]}}.ansible_facts.ansible_net_hostname }}"
                filename: "{{ios_facts_loop.results.item{{[0]}}.ansible_facts.ansible_net_hostname}} {{ lookup('pipe','date +%Y-%m-%d@%H:%M:%S')}}"
            register: backup_ios_location
            delegate_to: '{{item[1]}}'
            loop: 
              - with_sequence: "0-{{output|length - 3}}"
              - "{{groups['group_01']}}"

Upvotes: 2

Views: 841

Answers (1)

Zeitounator
Zeitounator

Reputation: 44789

TLDR; for vars notation

You cannot add double curly braces inside double curly braces like in your above code. You current var reference:

ios_facts_loop.results.{{item[0]}}.ansible_facts.ansible_net_hostname

should be turned to

ios_facts_loop.results[item[0]].ansible_facts.ansible_net_hostname
# or equivalent
ios_facts_loop.results[item.0].ansible_facts.ansible_net_hostname

Meanwhile, this will only fix your current syntax error (that you didn't share in your question) as the first element in your loop is a string 'with_sequence: "0-X"' which therefore has no index 0.

Attempt to fix the logic

If I understand correctly, for your last task, you just need to loop over the results of your ios_facts register and delegate the task to the server it was taken from. Luckilly, you should already have all the info you need in ios_facts_loop.results

  • It is a list so you can directly loop over it
  • Each element should contain an item key with the actual item that was used in the previous run at time of register (i.e. one of your groups['group_01'] element).

So I would try to write your last task like this. Disclaimer this is a pure guess as I didn't see your exact datastructure.

- name: Backup Switch (IOS)
  ios_config:
    backup: yes
    backup_options:
      dir_path:  "tmp/backups/{{ item.ansible_facts.ansible_net_hostname }}"
      filename: "{{ item.ansible_facts.ansible_net_hostname}}{{ lookup('pipe','date +%Y-%m-%d@%H:%M:%S')}}"
  register: backup_ios_location
  delegate_to: '{{item.item}}'
  loop: "{{ ios_facts_loop.results }}"

Going further.

I'm not really familiar with the ios_* modules but they should be really close to other stuff I use daily and I think you could really simplify your playbook taking advantage of more ansible feature (e.g. multiple plays in a playbook). I believe the following should actually do the job:

---
- name: Construct inventory from CSV  
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Use CSV
      csv_to_facts:
        src: '{{playbook_dir}}/NEW/Inventory.csv'
        vsheets:
          - INFO:
              - IP
              - OS

    - name: Create Inventory
      add_host:
        hostname: '{{item.IP}}'
        ansible_network_os: '{{item.OS}}'
        ansible_user: cisco
        ansible_ssh_pass: cisco
        ansible_connection: network_cli
        ansible_become: yes
        ansible_become_method: enable
        groups: group_01
      loop: '{{INFO}}'

- name: Backup switches from created inventory
  hosts: group_01
  gather_facts: false

  tasks: 
    - name: Get facts from network os                                                                                            
      ios_facts:                                                                                                  
        gather_subset: all 

    - name: Backup Switch (IOS)
      ios_config:
        backup: yes
        backup_options:
          dir_path:  "tmp/backups/{{ ansible_net_hostname }}"
          filename: "{{ ansible_net_hostname }}{{ lookup('pipe','date +%Y-%m-%d@%H:%M:%S') }}"

More background on dot and brackets notation for vars

You can basically navigate a yaml datastructure with two notation which are equivalent.

  • the dot notation
a_list_var.index_number
a_hasmap_var.keyname
  • the brackets notation
a_list_var[index_number]
a_hashmap_var['key_name']

If we take the following example:

my_servers:
  hostA:
    ips:
      - x.x.x.x
      - y.y.y.y
    env:
      shell: bash
      home: somewhere
  hostB:
    ips:
      - a.a.a.a
      - b.b.b.b
    env:
      shell: sh
      home: elsewhere

The following notation are all strictly equivalent:

# all vars of hostA
hostA_vars: "{{ my_servers.hostA }}"
hostA_vars: "{{ my_server['hostA'] }}"
# first IP of hostB
hostB_ip: "{{ my_servers.hostB.0 }}"
hostB_ip: "{{ my_servers.hostB[0] }}"
hostB_ip: "{{ my_servers['hostB'].0 }}"
hostB_ip: "{{ my_servers['hostB'][0] }}"

As you can see, the dot notation tends to be less verbose and more readable. Meanwhile, you cannot use a variable identifier with the dot notation. So If you want to ave the home env of a variable server you would have to use:

# set a var for server
server: hostA
# all equivalent again
server_home: "{{ my_servers[server].env.home }}"
server_home: "{{ my_servers[server]['env'].home }}"
server_home: "{{ my_servers[server].env['home'] }}"
server_home: "{{ my_servers[server]['env']['home'] }}"

Upvotes: 3

Related Questions