I'm trying to make a list with IP addresses of various hosts and then use this list in another task. My question is, how can I pick an IP (I need the public IP) from the output of each host and add it to a list? I need the IPs that do not start with 10..
Later, I need to use this list in another task.
I extract this information by running this playbook:
- hosts: facts
become: true
gather_facts: True
tasks:
- debug:
msg: "The ip: {{ item }}"
with_items: "{{ ansible_all_ipv4_addresses }}"
Later, I need to use this list in another task:
- wait_for:
host: "{{ item[0] }}"
port: "{{ item[1] }}"
state: started
delay: 0
timeout: 2
delegate_to: localhost
become: false
ignore_errors: no
ignore_unreachable: yes
register: result
failed_when: not result.failed
with_nested:
- [ IP LIST HERE]
- [443,80,9200,9300,22,5432,6432]
You can access those values from the hostvars right away, then use a reject filter with a match test in order to reject what you don't want to test for.
Which, in a debug task would gives:
# note: ports list reduced for brevity
- debug:
msg: "I should wait for interface {{ item.0 }}:{{ item.1 }}"
loop: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', ansible_play_hosts)
| map(attribute='value.ansible_all_ipv4_addresses', default=[])
| flatten
| reject('match', '10\..*')
| product(_ports)
}}
loop_control:
label: "{{ item.0 }}"
run_once: true
delegate_to: localhost
vars:
_ports:
- 22
- 80
In my lab, this give:
ok: [ansible-node-1 -> localhost] => (item=172.18.0.3) =>
msg: I should wait for interface 172.18.0.3:22
ok: [ansible-node-1 -> localhost] => (item=172.18.0.3) =>
msg: I should wait for interface 172.18.0.3:80
ok: [ansible-node-1 -> localhost] => (item=172.18.0.4) =>
msg: I should wait for interface 172.18.0.4:22
ok: [ansible-node-1 -> localhost] => (item=172.18.0.4) =>
msg: I should wait for interface 172.18.0.4:80
Try the example below
shell> cat pb.yml
- hosts: all
vars:
ip_list: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_all_ipv4_addresses')|
map('first')|list }}"
ip_list_reject: "{{ ip_list|reject('match', '10\\.')|list }}"
tasks:
- setup:
gather_subset: network
- block:
- debug:
var: ip_list
- debug:
var: ip_list_reject
- wait_for:
host: "{{ item.0 }}"
port: "{{ item.1 }}"
state: started
delay: 0
timeout: 2
delegate_to: localhost
register: result
with_nested:
- "{{ ip_list_reject }}"
- [443, 80, 9200, 9300, 22, 5432, 6432]
run_once: true
I am trying to get the pertinent fields from a vmware module output, I am filtering with json, but the says my variable target_vm_name is undefined. how do I get a variable with only the matching criteria which is filtered by the ip address of the list of hosts I want to iterate through?
code is below:
- name: Loop thru hosts and get IPs
hosts: rpa_test
vars:
ip_addr:
"{{ lookup('dig', ansible_host) }}"
tasks:
- debug:
msg: "System {{ inventory_hostname }} has ip address of {{ ip_addr }}"
- name: get vm info based on ip
community.vmware.vmware_vm_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vault_vcenter_admin_name }}"
password: "{{ vault_vcenter_admin_password }}"
validate_certs: False
delegate_to: localhost
register: vm_info
vars:
target_vm_name: "{{ vm_info.virtual_machines | json_query(query) }}"
query: "[?ip_address=={{ ip_addr }}]"
- debug:
msg: "{{ target_vm_name.virtual_machines.guest_name }}"
vars is not at the right place inside the community.vmware.vmware_vm_info task ,
you should put it in the debug task:
tasks:
- debug:
msg: "System {{ inventory_hostname }} has ip address of {{ ip_addr }}"
- name: get vm info based on ip
community.vmware.vmware_vm_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vault_vcenter_admin_name }}"
password: "{{ vault_vcenter_admin_password }}"
validate_certs: False
delegate_to: localhost
register: vm_info
- debug:
msg: "{{ target_vm_name.virtual_machines.guest_name }}"
vars:
target_vm_name: "{{ vm_info.virtual_machines | json_query(query) }}"
query: "[?ip_address=={{ ip_addr }}]"
I need to create a user on a system depending on it's function (test or production)
Test systems are named with suffix -tXX while prod ones are with -pXX (where XX is two digits numbering).
in variable file I have set:
auser: "{{ 'usertest' if {{ ansible_hostname|lower }} | regex_search('t[0-9]{2}$') else 'userprod' }}"
the error I get is during playbook run is:
fatal: [192.168.1.10]: FAILED! => {"msg": "An unhandled exception occurred while templating '{{ 'usertest' if {{ ansible_hostname|lower }} | regex_search('t[0-9]{2}$') else 'userprod' }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: template error while templating string: expected token ':', got '}'. String: {{ 'usertest' if {{ ansible_hostname|lower }} | regex_search('t[0-9]{2}$') else 'userprod' }}"}
Complete playbook is:
---
- hosts: testgrp
become: yes
vars:
- auser: "{{ 'usertest' if {{ ansible_hostname|lower }} | regex_search('t[0-9]{2}$') else 'userprod' }}"
tasks:
- name: Add groups
group:
name: "{{ item.name }}"
gid: "{{ item.gid }}"
state: present
loop:
- { name: 'group1', gid: '1101' }
- { name: 'group2', gid: '1102' }
- { name: 'group3', gid: '1103' }
- name: Add users
user:
name: "{{ item.name }}"
group: "{{ item.group }}"
groups: "{{ item.groups }}"
state: present
loop:
- { name: "{{ auser }}", group: 'group1', groups: 'group2,group3', uid: '1101' }
For example
- hosts: srv-t01,srv-p01
gather_facts: false
vars:
auser: "{{ inventory_hostname | lower is match('^.*-t[0-9]{2}$') |
ternary('usertest', 'userprod') }}"
tasks:
- debug:
var: auser
gives
ok: [srv-t01] =>
auser: usertest
ok: [srv-p01] =>
auser: userprod
A more robust solution is to select an index, e.g. the play below gives the same result
- hosts: srv-t01,srv-p01
gather_facts: false
vars:
auser_dict:
t: usertest
p: userprod
aindex: "{{ inventory_hostname | lower |
regex_replace('^.*-(.+)[0-9]{2}$', '\\1') }}"
auser: "{{ auser_dict[aindex] }}"
tasks:
- debug:
var: auser
I have a text file that needs to be parsed so i can use it via a REST API with ansible.
10.0.0.0/16 Building-A
10.1.0.0/16 Building-A
10.2.0.0/16 Building-B
10.3.0.0/16 Building-B
I need to convert this text to something like this:
{
"parsed":[
{
"Building-A":[
"10.0.0.0/16",
"10.1.0.0/16"
]
},
{
"Building-B":[
"10.2.0.0/16",
"10.3.0.0/16"
]
}
]
}
Currently i run the following playbook to test the textparsing, without success. The list created is not unique.
- name: Test
hosts: localhost
tasks:
- name: Combine
ansible.builtin.set_fact:
parsed: "{{ (parsed | default([])) | union( [{item.split()[1]: item.split()[0] }] ) }}"
loop: "{{ lookup('file','hostgroups.txt').strip().splitlines() }}"
- name: Debug
ansible.builtin.debug:
var: parsed
ok: [localhost] => {
"parsed": [
{
"Building-A": "10.0.0.0/16"
},
{
"Building-A": "10.1.0.0/16"
},
{
"Building-B": "10.2.0.0/16"
},
{
"Building-B": "10.3.0.0/16"
}
]
}
Thanks for the tip with the helper variables and the groupby filter. Here is the final playbook:
- name: Test
hosts: localhost
# strategy: free
tasks:
- name: Get List
ansible.builtin.set_fact:
parsed_list: "{{ parsed_list | default([]) + [_item] }}"
loop: "{{ lookup('file','hostgroups.txt').strip().splitlines() }}"
vars:
_list: "{{ item.split() }}"
_item: "{{ {'Name': _list[1], 'Subnet': _list[0] } }}"
- name: Debug parsed_list
ansible.builtin.debug:
var: parsed_list
- name: Group parsed_list
ansible.builtin.set_fact.set_fact:
parsed_group: "{{ parsed_group | default([]) + [{_key: _value}] }}"
loop: "{{ parsed_list | groupby('Name') }}"
vars:
_key: "{{ item.0 }}"
_value: "{{ item.1 | map(attribute='Subnet') | list }}"
- name: Debug parsed_group
ansible.builtin.debug:
var: parsed_group
Parse the content, e.g.
- set_fact:
parsed_list: "{{ parsed_list|d([]) + [_item] }}"
loop: "{{ lookup('file','hostgroups.txt').splitlines() }}"
vars:
_array: "{{ item.split() }}"
_item: "{{ {'building': _array.1, 'ip': _array.0} }}"
gives
parsed_list:
- building: Building-A
ip: 10.0.0.0/16
- building: Building-A
ip: 10.1.0.0/16
- building: Building-B
ip: 10.2.0.0/16
- building: Building-B
ip: 10.3.0.0/16
Then, use filter groupby and create the list
- set_fact:
parsed: "{{ parsed|d([]) + [{_key: _val}] }}"
loop: "{{ parsed_list|groupby('building') }}"
vars:
_key: "{{ item.0 }}"
_val: "{{ item.1|map(attribute='ip')|list }}"
gives
parsed:
- Building-A:
- 10.0.0.0/16
- 10.1.0.0/16
- Building-B:
- 10.2.0.0/16
- 10.3.0.0/16
In some cases, a dictionary might be a better structure, e.g.
- set_fact:
parsed_dict: "{{ parsed_dict|d({})|combine({_key: _val}) }}"
loop: "{{ parsed_list|groupby('building') }}"
vars:
_key: "{{ item.0 }}"
_val: "{{ item.1|map(attribute='ip')|list }}"
gives
parsed_dict:
Building-A:
- 10.0.0.0/16
- 10.1.0.0/16
Building-B:
- 10.2.0.0/16
- 10.3.0.0/16
I have as a source a json file that contains a list of blocks and data. from which i would like to extract information to create security rules, using a double loop in ansible.
Below an example from my json file :
[
{
"Name":"Some_name",
"NetworkFlow":[
{
"GroupName":"Test1",
"Type":"Ingress",
"Env":"dev",
"Server":[
"192.168.1.1",
"192.168.1.2",
...
],
"Service":[
{
"Protocol":"TCP",
"Port":"443"
},
{
"Protocol":"UDP",
"Port":"21"
},
....
]
},
....
]
}
]
This is for a generic deployment, and for each "NetworkFlow" section, i have to loop in the list of servers and also in the list of protocols and ports to get a simular parsing like the below:
#rule= Server,Protocol,Port,Type,Env,GroupName
192.168.1.1,TCP,443,Ingress,Dev,Test1
192.168.1.2,TCP,443,Ingress,Dev,Test1
192.168.1.1,UDP,21,Ingress,Dev,Test1
192.168.1.2,UDP,21,Ingress,Dev,Test1
I tried with_nested but it doesn't work, Any idea to deal with that please?
Create a file with the nested loop, for example
shell> cat rules.yml
- debug:
msg: "{{ item.0 }},{{ item.1.Protocol }},{{ item.1.Port }},{{ outer_item.Type }},{{ outer_item.Env }},{{ outer_item.GroupName }}"
with_nested:
- "{{ outer_item.Server }}"
- "{{ outer_item.Service }}"
and include it
- include_tasks: rules.yml
loop: "{{ NetworkFlow }}"
loop_control:
loop_var: outer_item
gives
msg: 192.168.1.1,TCP,443,Ingress,dev,Test1
msg: 192.168.1.1,UDP,21,Ingress,dev,Test1
msg: 192.168.1.2,TCP,443,Ingress,dev,Test1
msg: 192.168.1.2,UDP,21,Ingress,dev,Test1
Q: "... have a list of ports separated by a comma and not just one port."
A: Convert the data. For example
shell> cat rules.yml
- set_fact:
Services: "{{ Services|from_yaml }}"
vars:
Services: |
{% for service in oi.Service %}
{% for port in service.Port.split(',') %}
- Protocol: {{ service.Protocol }}
Port: {{ port }}
{% endfor %}
{% endfor %}
- debug:
msg: "{{ i.0 }},{{ i.1.Protocol }},{{ i.1.Port }},{{ oi.Type }},{{ oi.Env }},{{ oi.GroupName }}"
with_nested:
- "{{ oi.Server }}"
- "{{ Services }}"
loop_control:
loop_var: I
gives
msg: 192.168.1.1,TCP,443,Ingress,dev,Test1
msg: 192.168.1.1,TCP,22,Ingress,dev,Test1
msg: 192.168.1.1,TCP,53,Ingress,dev,Test1
msg: 192.168.1.1,UDP,21,Ingress,dev,Test1
msg: 192.168.1.2,TCP,443,Ingress,dev,Test1
msg: 192.168.1.2,TCP,22,Ingress,dev,Test1
msg: 192.168.1.2,TCP,53,Ingress,dev,Test1
msg: 192.168.1.2,UDP,21,Ingress,dev,Test1