I have 2 csv files as
CSV1:
ABC, NIC.Slot.1-1-1:, A6:C3:4F:8E:D5:30
DEF, NIC.Slot.1-2-1:, B2:D5:8C:5A:43:60
CSV2:
XYZ, ethernet15:3, ABC_DPDK2, b2:d5:8c:5a:43:60
UVW, ethernet15:2, ABC_DPDK1, a6:c3:4f:8e:d5:30
I want another CSV file which will compare the MAC of both the files and update rest in output csv file
Below is the desired output
CSV3:
ABC, UVW, NIC.Slot.1-1-1:, A6:C3:4F:8E:D5:30, ethernet15:2, ABC_DPDK1
DEF, XYZ, NIC.Slot.1-2-1:, B2:D5:8C:5A:43:60, ethernet15:3, ABC_DPDK2
is there anyway i can do it via ansible
Given the files
shell> cat net1.csv
ABC, NIC.Slot.1-1-1:, A6:C3:4F:8E:D5:30
DEF, NIC.Slot.1-2-1:, B2:D5:8C:5A:43:60
shell> cat net2.csv
XYZ, ethernet15:3, ABC_DPDK2, b2:d5:8c:5a:43:60
UVW, ethernet15:2, ABC_DPDK1, a6:c3:4f:8e:d5:30
Put the declarations below as appropriate
fields: [name1, name2, nic, MAC, dev, label]
csv_files:
- {file: net1.csv, fields: [name1, nic, mac]}
- {file: net2.csv, fields: [name2, dev, label, mac]}
_list: "{{ csv.results|map(attribute='list')|flatten }}"
_MAC: "{{ _list|map(attribute='mac')|map('trim')|map('upper')|
map('community.general.dict_kv', 'MAC')|list }}"
_groups: "{{ _list|zip(_MAC)|map('combine')|groupby('MAC') }}"
_values: "{{ _groups|map(attribute=1)|map('combine')|list }}"
Read the files, for example
- community.general.read_csv:
path: "{{ playbook_dir }}/{{ item.file }}"
fieldnames: "{{ item.fields }}"
skipinitialspace: true
register: csv
loop: "{{ csv_files }}"
gives
_list:
- mac: A6:C3:4F:8E:D5:30
name1: ABC
nic: 'NIC.Slot.1-1-1:'
- mac: B2:D5:8C:5A:43:60
name1: DEF
nic: 'NIC.Slot.1-2-1:'
- dev: ethernet15:3
label: ABC_DPDK2
mac: 'b2:d5:8c:5a:43:60 '
name2: XYZ
- dev: ethernet15:2
label: ABC_DPDK1
mac: a6:c3:4f:8e:d5:30
name2: UVW
_values:
- MAC: A6:C3:4F:8E:D5:30
dev: ethernet15:2
label: ABC_DPDK1
mac: a6:c3:4f:8e:d5:30
name1: ABC
name2: UVW
nic: 'NIC.Slot.1-1-1:'
- MAC: B2:D5:8C:5A:43:60
dev: ethernet15:3
label: ABC_DPDK2
mac: 'b2:d5:8c:5a:43:60 '
name1: DEF
name2: XYZ
nic: 'NIC.Slot.1-2-1:'
Create the CSV file
- ansible.builtin.copy:
dest: net3.csv
content: |-
{% for line in _values %}
{{ fields|map('extract', line)|join(', ') }}
{% endfor %}
gives
shell> cat ~/net3.csv
ABC, UVW, NIC.Slot.1-1-1:, A6:C3:4F:8E:D5:30, ethernet15:2, ABC_DPDK1
DEF, XYZ, NIC.Slot.1-2-1:, B2:D5:8C:5A:43:60, ethernet15:3, ABC_DPDK2
Example of a complete playbook
- hosts: localhost
vars:
fields: [name1, name2, nic, MAC, dev, label]
csv_files:
- {file: net1.csv, fields: [name1, nic, mac]}
- {file: net2.csv, fields: [name2, dev, label, mac]}
_list: "{{ csv.results|map(attribute='list')|flatten }}"
_MAC: "{{ _list|map(attribute='mac')|map('trim')|map('upper')|
map('community.general.dict_kv', 'MAC')|list }}"
_groups: "{{ _list|zip(_MAC)|map('combine')|groupby('MAC') }}"
_values: "{{ _groups|map(attribute=1)|map('combine')|list }}"
tasks:
- community.general.read_csv:
path: "{{ playbook_dir }}/{{ item.file }}"
fieldnames: "{{ item.fields }}"
skipinitialspace: true
register: csv
loop: "{{ csv_files }}"
- ansible.builtin.copy:
dest: net3.csv
content: |-
{% for line in _values %}
{{ fields|map('extract', line)|join(', ') }}
{% endfor %}
I have as a source a json file that contains a list of NetworkFlow keys, and 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":"prod",
"Server":[
"192.168.1.1",
"192.168.1.2"
],
"Service":[
{
"Protocol":"TCP",
"Port":"443,22,53"
},
{
"Protocol":"UDP",
"Port":"21"
}
]
},
{
"GroupName":"Test2",
"Type":"Egress",
"Env":"dev",
"Server":[
"192.168.1.3",
"192.168.1.4"
],
"Service":[
{
"Protocol":"UDP",
"Port":"9996,9997"
}
]
}
]
}
so firstly i have to loop for each NetworkFlow section, and inside each one, i have to loop in the list of servers and also in the list of services (protocols and ports) to get a simular parsing like the below:
#rule= Server,Protocol,Port,Type,Env,GroupName
msg: 192.168.1.1,TCP,443,Ingress,prod,Test1
msg: 192.168.1.1,TCP,22,Ingress,prod,Test1
msg: 192.168.1.1,TCP,53,Ingress,prod,Test1
msg: 192.168.1.1,UDP,21,Ingress,prod,Test1
msg: 192.168.1.2,TCP,443,Ingress,prod,Test1
msg: 192.168.1.2,TCP,22,Ingress,prod,Test1
msg: 192.168.1.2,TCP,53,Ingress,prod,Test1
msg: 192.168.1.2,UDP,21,Ingress,prod,Test1
msg: 192.168.1.3,UDP,9996,Egress,dev,Test2
msg: 192.168.1.3,UDP,9997,Egress,dev,Test2
msg: 192.168.1.4,UDP,9996,Egress,dev,Test2
msg: 192.168.1.4,UDP,9997,Egress,dev,Test2
Here Below my playbook tasks :
my main task :
---
- name: Include JSON file
include_vars:
file: test.json
- include_tasks: rules.yml
loop: "{{ NetworkFlow }}"
loop_control:
loop_var: oi
my rule task :
---
- 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
For info, my oi.Service.Port can have a list of port separated by a comma !
I tried with a loop inside a with_nested and it work for the first key Test1, but i didn't get the correct parsing for the second NetworkFlow key Test2
TASK [test : set_fact] *****************************************************************************************************************************************
ok: [localhost]
TASK [test : debug] ********************************************************************************************************************************************
"msg": "192.168.1.1,TCP,443,Ingress,prod,Test1"
"msg": "192.168.1.1,TCP,22,Ingress,prod,Test1"
"msg": "192.168.1.1,TCP,53,Ingress,prod,Test1"
"msg": "192.168.1.1,UDP,21,Ingress,prod,Test1"
"msg": "192.168.1.2,TCP,443,Ingress,prod,Test1"
"msg": "192.168.1.2,TCP,22,Ingress,prod,Test1"
"msg": "192.168.1.2,TCP,53,Ingress,prod,Test1"
"msg": "192.168.1.2,UDP,21,Ingress,prod,Test1"
}
TASK [test : set_fact] *****************************************************************************************************************************************
ok: [localhost]
TASK [test : debug] ********************************************************************************************************************************************
"msg": "192.168.1.3,TCP,443,Egress,dev,Test2"
"msg": "192.168.1.3,TCP,22,Egress,dev,Test2"
"msg": "192.168.1.3,TCP,53,Egress,dev,Test2"
"msg": "192.168.1.3,UDP,21,Egress,dev,Test2"
"msg": "192.168.1.4,TCP,443,Egress,dev,Test2"
"msg": "192.168.1.4,TCP,22,Egress,dev,Test2"
"msg": "192.168.1.4,TCP,53,Egress,dev,Test2"
"msg": "192.168.1.4,UDP,21,Egress,dev,Test2"
Have anyone idea for how to deal with that please?
The task below creates the list Services including the servers
- set_fact:
Services: "{{ Services|default([]) + Service|from_yaml }}"
vars:
Service: |
{% for Port in item.1.Port.split(',') %}
- {{ item.0 }},{{ item.1.Protocol }},{{ Port }},{{ oi.Type }},{{ oi.Env }},{{ oi.GroupName }}
{% endfor %}
with_nested:
- "{{ oi.Server }}"
- "{{ oi.Service }}"
Services:
- 192.168.1.1,TCP,443,Ingress,prod,Test1
- 192.168.1.1,TCP,22,Ingress,prod,Test1
- 192.168.1.1,TCP,53,Ingress,prod,Test1
- 192.168.1.1,UDP,21,Ingress,prod,Test1
- 192.168.1.2,TCP,443,Ingress,prod,Test1
- 192.168.1.2,TCP,22,Ingress,prod,Test1
- 192.168.1.2,TCP,53,Ingress,prod,Test1
- 192.168.1.2,UDP,21,Ingress,prod,Test1
- 192.168.1.3,UDP,9996,Egress,dev,Test2
- 192.168.1.3,UDP,9997,Egress,dev,Test2
- 192.168.1.4,UDP,9996,Egress,dev,Test2
- 192.168.1.4,UDP,9997,Egress,dev,Test2
Let's imagine an inventory file like this:
node-01 ansible_ssh_host=192.168.100.101
node-02 ansible_ssh_host=192.168.100.102
node-03 ansible_ssh_host=192.168.100.103
node-04 ansible_ssh_host=192.168.100.104
node-05 ansible_ssh_host=192.168.100.105
[mainnodes]
node-[01:04]
In my playbook I now want to create some variables containing the IP addresses of the group mainnodes:
vars:
main_nodes_ips: "192.168.100.101,192.168.100.102,192.168.100.103,192.168.100.104"
main_nodes_ips_with_port: "192.168.100.101:3000,192.168.100.102:3000,192.168.100.103:3000,192.168.100.104:3000"
This is what I got so far:
vars:
main_nodes_ips: "{{groups['mainnodes']|join(',')}}"
main_nodes_ips_with_port: "{{groups['mainnodes']|join(':3000,')}}"
but that would use the host names instead of the IP addresses.
Any ideas how this could be done?
Update:
looking at the docs for a while, I think this would allow me to loop through all the ip adresses:
{% for host in groups['mainnodes'] %}
{{hostvars[host]['ansible_ssh_host']}}
{% endfor %}
But I just can't figure out how to create an array that holds all these IPs. So that I can use the |join() command on them.
Update2:
I just thought I had figured it out... but it turns out that you cannot use the {% %} syntax in the playbook... or can I?
Well in the vars section it didn't. :/
vars:
{% set main_nodes_ip_arr=[] %}
{% for host in groups['mesos-slave'] %}
{% if main_nodes_ip_arr.insert(loop.index,hostvars[host]['ansible_ssh_host']) %} {% endif %}
{% endfor %}
main_nodes_ips: "{{main_nodes_ip_arr|join(',')}}"
main_nodes_ips_with_port: "{{main_nodes_ip_arr|join(':3000,')}}"
I find the magic map extract here.
main_nodes_ips: "{{ groups['mainnodes'] | map('extract', hostvars, ['ansible_host']) | join(',') }}"
main_nodes_ips_with_port: "{{ groups['mainnodes'] | map('extract', hostvars, ['ansible_host']) | join(':3000,') }}:3000"
An alternative(idea comes from here):
main_nodes_ips: "{{ groups['mainnodes'] | map('extract', hostvars, ['ansible_eth0', 'ipv4', 'address']) | join(',') }}"
(Suppose the interface is eth0)
i came across this problem a while back and this is what i came up with (not optimal, but it works)
---
# playbook.yml
- hosts: localhost
connection: local
tasks:
- name: create deploy template
template:
src: iplist.txt
dest: /tmp/iplist.txt
- include_vars: /tmp/iplist.txt
- debug: var=ip
and the template file is
ip:
{% for h in groups['webservers'] %}
- {{ hostvars[h].ansible_ssh_host }}
{% endfor %}
This do the trick for me. Not relying on the interface name
- main_nodes_ips: "{{ groups['mainnodes'] | map('extract', hostvars, ['ansible_default_ipv4', 'address']) | join(',') }}"
- name: Create List of nodes to be added into Cluster
set_fact: nodelist={%for host in groups['mygroup']%}"{{hostvars[host].ansible_eth0.ipv4.address}}"{% if not loop.last %},{% endif %}{% endfor %}
- debug: msg=[{{nodelist}}]
- name: Set Cluster node list in config file
lineinfile:
path: "/etc/myfonfig.cfg"
line: "hosts: [{{ nodelist }}]"
as results you will have the following line in config file:
hosts: ["192.168.126.38","192.168.126.39","192.168.126.40"]
I got it to work on my own now. I'm not too happy about the solution, but it will do:
main_nodes_ips: "{% set IP_ARR=[] %}{% for host in groups['mainnodes'] %}{% if IP_ARR.insert(loop.index,hostvars[host]['ansible_ssh_host']) %}{% endif %}{% endfor %}{{IP_ARR|join(',')}}"
main_nodes_ips_with_port: "{% set IP_ARR=[] %}{% for host in groups['mainnodes'] %}{% if IP_ARR.insert(loop.index,hostvars[host]['ansible_ssh_host']) %}{% endif %}{% endfor %}{{IP_ARR|join(':3000,')}
I've done this by using ansible facts in a playbook.
This playbook takes ansible_all_ipv4_addresses list and ansible_nodename (which is actually fully qualified domain name), iterates through all hosts and saves the data in localpath_to_save_ips file on your localhost. You can change localpath_to_save_ips to the absolute path on your localhost.
---
- hosts: all
become: yes
gather_facts: yes
tasks:
- name: get ip
local_action: shell echo {{ ansible_all_ipv4_addresses }} {{ ansible_nodename }} >> localpath_to_save_ips
I found the "only way" to acceess other group's ip's, when any of the following is true:
some members are not bootstrapped by ansible yet
using serial
group is not part of playbook
Is as follows:
{% set ips=[] %}{% for host in groups['othergroup'] %}{% if ips.append(lookup('dig', host)) %}{% endif %}{% endfor %}{{ ips }}
Requires dnspython on the machine running ansible, install via
sudo apt-get install python-dnspython
If anyone knows a better way given the conditions, I'd love to get rid of this abomination.
this is what I did in order to not be relied on eth0 (thanks to ADV-IT's answer):
- name: gathering facts
hosts: mainnodes
gather_facts: true
- hosts: mainnodes
tasks:
- name: Create List of nodes
set_fact: nodelist={%for host in groups['mainnodes']%}"{{hostvars[host]['ansible_env'].SSH_CONNECTION.split(' ')[2]}}"{% if not loop.last %},{% endif %}{% endfor %}
I ran into a similar problem getting the IP address of a node in another group.
Using a construct like:
the_ip: "{{ hostvars[groups['master'][0]]['ansible_default_ipv4'].address }}"
works only when running the group master, which was not part of my playbook (I was running on localhost).
I have overcome the problem by adding an extra play to playbook, like:
- hosts: master
gather_facts: yes
become: no
vars:
- the_master_ip: "{{ hostvars[groups['master'][0]]['ansible_default_ipv4'].address }}"
tasks:
- debug: var=the_master_ip
- set_fact: the_ip={{ the_master_ip }}
After which I can use the the_ip in the next play of the playbook.
This may also solve the abomination mentioned by #Petroldrake ?
##Just fetch Ip's using -ansible_default_ipv4.address- & redirect to a local file & then use it
name: gathering_facts
hosts: hosts
gather_facts: true
tasks:
name: Rediret to the file
shell: echo "{{ansible_default_ipv4.address}}" >>ipss.txt
delegate_to: localhost