loztagain
loztagain

Reputation: 3

Ansible loop built from variable sets

I am pretty new to Ansible as a network engineer and have found it breaking my brain. I've used basic loops in some Ansible playbooks. Now I'm trying something a bit more complex and I'm sure I'm missing something because it feels like it should be simple.

I want to take these variables in a playbook:

  vars:
    smb_tcp_ports:
      - '139'
      - '445'
    smb_udp_ports:
      - '137'
      - '138'
    smb_ips:
      - '172.16.13.130'
      - '172.16.13.0/26'
      - '200X:8X0:fX4a:X053::/64'
      - '200X:8X0:fX4a:X050::130' 

and loop through them so I build a new variable like this:

  vars: 
    smb_allowed_ips_tcp: 
     - { ip: "172.16.13.130", port: ['139','445'] }
     - { ip: "172.16.13.0/26", port: ['139','445'] }
     - { ip: "X001:8X0:fX4a:X053::/64", port: ['139','445'] }
     - { ip: "X001:8X0:fX4a:X050::130", port: ['139','445'] }
    smb_allowed_ips_udp:
     - { ip: "172.16.13.130", port: ['137','138'] }
     - { ip: "172.16.13.0/26", port: ['137','138'] }
     - { ip: "200X:8X0:fX4a:X053::/64", port: ['137','138'] }
     - { ip: "200X:8X0:fX4a:X050::130", port: ['137','138'] }

^^^ the above bit that I want to generate is the bit that I'm struggling with ^^^ Then I can send it to this:

- name: Allow SMB TCP
  ufw:
    rule: allow
    src: '{{ item.0.ip }}'
    port: '{{ item.1 }}'
    proto: tcp
  with_subelements:
    - "{{ smb_allowed_ips_tcp }}"
    - port
  when: "'smbserver' in group_names"

- name: Allow SMB UDP
  ufw:
    rule: allow
    src: '{{ item.0.ip }}'
    port: '{{ item.1 }}'
    proto: udp
  with_subelements:
    - "{{ smb_allowed_ips_udp }}"
    - port
  when: "'smbserver' in group_names"

The question used to have a lot of words here. I deleted it. Thanks Larsks. I hope this is clearer?

I tried set_facts, but there is loads of stuff I don't understand in examples i see, like adding | symbols and writing list, product etc, and I always end up breaking. It also doesnt seem to add as an array, it overwrites.

Answered here: using https://ansibledaily.com/process-complex-variables-with-set_fact-and-with_items/

---

- hosts: myhosts
  gather_facts: true
  become: true
  vars:
    smb_tcp_ports:
      - '139'
      - '445'
    smb_udp_ports:
      - '137'
      - '138'
    smb_ips:
      - '172.16.13.130'
      - '172.16.13.0/26'
      - '200X:8X0:fX4a:X053::/64'
      - '200X:8X0:fX4a:X050::130'
    smb_ips_tcp: {}

  tasks:
  - name: Populate IPs in dict
    set_fact:
      smb_ips_tcp: "{{ smb_ips_tcp | combine({'ip': item}) }}"
    with_items:
      - "{{ smb_ips }}"
    register: smbout

  - name: Populate ports in dict
    set_fact:
      smb_ips_tcp: "{{ item | combine({'port': smb_tcp_ports}) }}"
    with_items:
      - "{{ smbout.results | map(attribute='ansible_facts.smb_ips_tcp') | list  }}"
    register: smbout

  - name: smbout results
    set_fact:
      smb_ips_tcp: "{{ smbout.results | map(attribute='ansible_facts.smb_ips_tcp') | list   }}"

  - name: Allow SMB TCP
    ufw:
      rule: allow
      src: '{{ item.0.ip }}'
      port: '{{ item.1 }}'
      proto: tcp
    with_subelements:
      - "{{ smb_ips_tcp }}"
      - port

I think I had missed the register bit. So when I tried the register facts bit before it kept leaving me with one key value pair. Which was useless. The register though is allowing me to keep all the key values and use them again.

Unsure if this is a duplicate question now.

Upvotes: 0

Views: 623

Answers (1)

larsks
larsks

Reputation: 311238

It sounds like you may have resolved your question, but I thought you might be interested in an alternative implementation. I would probably solve this using the product filter, which produces the cartesian product of two lists. For example, to produce smb_allowed_ips_tcp, I would write:

    - name: create smb_allowed_ips_tcp
      set_fact:
        smb_allowed_ips_tcp: "{{ smb_allowed_ips_tcp + [{'ip': item.0, 'port': item.1}] }}"
      loop: "{{ smb_ips|product(smb_tcp_ports)|list }}"
      vars:
        smb_allowed_ips_tcp: []

This produces a data structure that looks like:

TASK [debug] ******************************************************************************************
ok: [localhost] => {
    "smb_allowed_ips_tcp": [
        {
            "ip": "172.16.13.130",
            "port": "139"
        },
        {
            "ip": "172.16.13.130",
            "port": "445"
        },
        {
            "ip": "172.16.13.0/26",
            "port": "139"
        },
        {
            "ip": "172.16.13.0/26",
            "port": "445"
        },
        {
            "ip": "200X:8X0:fX4a:X053::/64",
            "port": "139"
        },
        {
            "ip": "200X:8X0:fX4a:X053::/64",
            "port": "445"
        },
        {
            "ip": "200X:8X0:fX4a:X050::130",
            "port": "139"
        },
        {
            "ip": "200X:8X0:fX4a:X050::130",
            "port": "445"
        }
    ]
}

We can feed that to the ufw module like this:

    - name: Allow SMB TCP
      ufw:
        rule: allow
        src: '{{ item.ip }}'
        port: '{{ item.port }}'
        proto: tcp
      loop: "{{ smb_allowed_ips_tcp }}"

There are fewer tasks required for this solution, and I think the logic is a little easier to follow.

Upvotes: 1

Related Questions