Ansible regex replace in variable to cisco interface names - ansible

I'm currently working with a script to create interface descriptions based on CDP neighbor info, but it's placing the full names e.g., GigabitEthernet1/1/1, HundredGigabitEthernet1/1/1.
My regex is weak, but I would like to do a regex replace to keep only the first 3 chars of the interface name.
I think a pattern like (dredGigatbitEthernet|abitEthernet|ntyGigabitEthernet|etc) should work, but not sure how to put that into the playbook line below to modify the port value
nxos_config:
lines:
- description {{ item.value[0].port }} ON {{ item.value[0].host }}
E.g, I am looking for GigabitEthernet1/1/1 to end up as Gig1/1/1
Here is an example of input data:
{
"FastEthernet1/1/1": [{
"host": "hostname",
"port": "Port 1"
}]
}
Final play to make it correct using ansible net neighbors as the source
Thank you - I updated my play, adjusted for ansible net neighbors
- name: Set Interface description based on CDP/LLDP discovery
hosts: all
gather_facts: yes
connection: network_cli
tasks:
- debug:
msg: "{{ ansible_facts.net_neighbors }}"
- debug:
msg: >-
description
{{
item[0].port
| regex_search('(.{3}).*([0-9]+\/[0-9]+\/[0-9]+)', '\1', '\2')
| join
}}
ON {{ item.value[0].host }}"
loop: "{{ ansible_facts.net_neighbors | dict2items }}"
loop_control:
label: "{{ item.key }}"
Thanks for the input!

Given that you want the three first characters along with the last 3 digits separated by a slash, then the regex (.{3}).*([0-9]+\/[0-9]+\/[0-9]+) should give you two capturing groups containing those two requirements.
In Ansible, you can use regex_search to extract those groups, then join them back, with the help on the join Jinja filter.
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: >-
description
{{
item.key
| regex_search('(.{3}).*([0-9]+\/[0-9]+\/[0-9]+)', '\1', '\2')
| join
}}
ON {{ item.value[0].host }}"
loop: "{{ interfaces | dict2items }}"
loop_control:
label: "{{ item.key }}"
vars:
interfaces:
GigabitEthernet1/1/1:
- port: Port 1
host: example.org
HundredGigabitEthernet1/1/1:
- port: Port 2
host: example.com
This yields:
TASK [debug] ***************************************************************
ok: [localhost] => (item=eth0) =>
msg: description Gig1/1/1 ON example.org"
ok: [localhost] => (item=eth1) =>
msg: description Hun1/1/1 ON example.com"

Related

how to make a list from ansible_facts with multiple hosts

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

List name server from resolv.conf with hostname in one line per host

I need to get the DNS server(s) from my network, I tried using:
- hosts: localhost
gather_facts: no
tasks:
- name: check resolv.conf exists
stat:
path: /etc/resolv.conf
register: resolv_conf
- name: check nameservers list in resolv.conf
debug:
msg: "{{ contents }}"
vars:
contents: "{{ lookup('file', '/etc/resolv.conf') | regex_findall('\\s*nameserver\\s*(.*)') }}"
when: resolv_conf.stat.exists == True
But this does not quite gives the result I need.
Will it be possible to write a playbook in such a way that the result looks like the below?
hostname;dns1;dns2;dnsN
The declaration below gives the list of nameservers
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
You can join the hostname and the items on the list
msg: "{{ inventory_hostname }};{{ nameservers|join(';') }}"
Notes
Example of a complete playbook for testing
- hosts: localhost
vars:
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
tasks:
- debug:
var: nameservers
- debug:
msg: |
{{ inventory_hostname }};{{ nameservers|join(';') }}
The simplified declaration below works fine if there is no nameserver.* in the comments
nameservers: "{{ lookup('file', '/etc/resolv.conf')|
regex_findall('\\s*nameserver\\s*(.*)') }}"
Unfortunately, the Linux default file /etc/resolv.conf contains the comment:
| # run "systemd-resolve --status" to see details about the actual nameservers.
This regex will match nameservers.
nameservers:
- s.
You can solve this problem by putting at least one space behind the keyword nameserver.
regex_findall('\\s*nameserver\\s+(.*)') }}"
However, this won't help if there is the keyword nameserver in the comment.
Q: "No filter named 'split'"
A: There is no filter split in Ansible less than 2.11. Use regex_replace instead
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('regex_replace', '^(.*) (.*)$', '\\2')|list }}"
Since your regex_findall already creates you a list with all DNS servers, you just need to add the hostname to that list and join the whole list with a semicolon.
- name: check nameservers list in resolv.conf
debug:
msg: >-
{{
(
[ ansible_hostname ] +
lookup('file', '/etc/resolv.conf', errors='ignore')
| regex_findall('\s*nameserver\s*(.*)')
) | join(';')
}}
Which will result in something like (b176263884e6 being the actual hostname of a container):
TASK [check nameservers list in resolv.conf] *****************************
ok: [localhost] =>
msg: b176263884e6;1.1.1.1;4.4.4.4;8.8.8.8
Note that you don't even need the stat task, as you can ignore errors of the lookup with errors='ignore'.
This will, then, give you only the hostname, along with a warning:
TASK [check nameservers list in resolv.conf] *****************************
[WARNING]: Unable to find '/etc/resolv.conf' in expected paths
(use -vvvvv to see paths)
ok: [localhost] =>
msg: b176263884e6

How to match/search a substring from a dict attribute that is a list

Here's the scenario:
a playbook that calls a role to create users in multiple servers, including a VM Scale Set (where ansible_hostnames can't be predicted) - inventory is already being dynamically generated and works fine and not the issue
a users dict variable will provide the user list as well as a series of attributes for each
one of these attributes is a server list named target_servers - this variable's attribute is the actual issue
target_servers is used by the playbook to decide if the user will be present/absent on that particular server - it complements ansible's inventory
target_servers might include only the starting name of a particular target host, a sub-string, like "vmss" as a "vmss*" wildcard, but also fixed hostnames server12345, server12346, etc.
so, dynamic inventory tells ansible which servers to connect to, but the variable tells it whether the user should be created or removed from that particular servers (i.e. servers have different users)
Objective(s):
Have a conditional that checks if a target_server list element content matches the ansible_hostname (i.e. if the substring found in the target_servers list (from the users dict) matches, then we provision the user; additionally, off course, if the list provides the entire hostname, it should match and the users also be provisioned)
Here's the code:
---
- hosts: all
become: yes
vars:
users:
user1:
is_sudo: no
is_chrooted: yes
auth_method: hvault
sa_homedir: firstname1lastname1
state: present
target_servers:
- vmss
- ubuntu
user2:
is_sudo: no
is_chrooted: yes
auth_method: hvault
sa_homedir: firstname2lastname2
state: present
target_servers:
- vmss
- ubuntu18
tasks:
- debug:
msg: "{{ ansible_hostname }}"
- debug:
msg: "{{ item.value.target_servers }}"
loop: "{{ lookup('dict', users|default({})) }}"
# This is just to exemplify what I'm trying to achieve as it is not supposed to work
- debug:
msg: "ansible_hostname is in target_servers of {{ item.key }}"
loop: "{{ lookup('dict', users|default({})) }}"
when: ansible_hostname is match(item.value.target_servers)
Here's the output showing that the match string test cannot be applied to a list (as expected):
TASK [debug] ************************************************************************************************************************************************
ok: [ubuntu18] =>
msg: ubuntu18
TASK [debug] ************************************************************************************************************************************************
ok: [ubuntu18] => (item={'key': 'user1', 'value': {'is_sudo': False, 'is_chrooted': True, 'auth_method': 'hvault', 'sa_homedir': 'firstname1lastname1', 'state': 'present', 'target_servers': ['vmss', 'ubuntu']}}) =>
msg:
- vmss
- ubuntu
ok: [ubuntu18] => (item={'key': 'user2', 'value': {'is_sudo': False, 'is_chrooted': True, 'auth_method': 'hvault', 'sa_homedir': 'firstname2lastname2', 'state': 'present', 'target_servers': ['vmss', 'ubuntu18']}}) =>
msg:
- vmss
- ubuntu18
TASK [debug] ************************************************************************************************************************************************
fatal: [ubuntu18]: FAILED! =>
msg: |-
The conditional check 'ansible_hostname is match(item.value.target_servers)' failed. The error was: Unexpected templating type error occurred on ({% if ansible_hostname is match(item.value.target_servers) %} True {% else %} False {% endif %}): unhashable type: 'list'
The error appears to be in 'test-play-users-core.yml': line 32, column 5, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- debug:
^ here
Already tried researching about selectattr, json_query and subelements but I currently lack the understanding on how to make them work to match a substring inside a dict attribute that is a list.
In the example above, by changing from is match() to in, exact hostnames work fine, but that is not the goal. I need to match both exact hostnames and sub-strings for these hostnames.
Any help on how to accomplish this or suggestions about alternate methods will be greatly appreciated.
The example here might work if I could find a way to run it against a list (target_servers) after having already looped through the entire dictionary (are nested loops possible?): https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#testing-strings
I guess I've just found what I needed: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/subelements_lookup.html
Will try and provide an update soon.
Update: yes, subelements work! Here's the code needed:
- name: test 1
debug:
msg: "{{ item.1 }} matches {{ ansible_hostname }}"
with_subelements:
- "{{ users }}"
- target_servers
when: >
ansible_hostname is match(item.1)
You can use the select filter to apply the in test to all the elements of your users' target_servers list.
This would be your debug task:
- debug:
msg: "hostname is in target_servers of {{ item.key }}"
loop: "{{ users | dict2items }}"
loop_control:
label: "{{ item.key }}"
when: >-
item.value.target_servers
| select('in', inventory_hostname)
| length > 0
Given the playbook:
- hosts: all
gather_facts: false
vars:
_hostname: ubuntu18
users:
user1:
target_servers:
- vmss
- ubuntu
user2:
target_servers:
- vmss
- ubuntu18
tasks:
- debug:
msg: "hostname is in target_servers of {{ item.key }}"
loop: "{{ users | dict2items }}"
loop_control:
label: "{{ item.key }}"
when: >-
item.value.target_servers
| select('in', inventory_hostname)
| length > 0
This yields:
ok: [ubuntu18] => (item=user1) =>
msg: hostname is in target_servers of user1
ok: [ubuntu18] => (item=user2) =>
msg: hostname is in target_servers of user2
Doing it with subelements instead:
- hosts: all
gather_facts: false
vars:
_hostname: ubuntu18
users:
user1:
target_servers:
- vmss
- ubuntu
user2:
target_servers:
- vmss
- ubuntu18
tasks:
- debug:
msg: "hostname is in target_servers of {{ item.0.key }}"
loop: "{{ users | dict2items | subelements('value.target_servers') }}"
loop_control:
label: "{{ item.0.key }}"
when: item.1 in inventory_hostname
Will yield:
skipping: [ubuntu18] => (item=user1)
ok: [ubuntu18] => (item=user1) =>
msg: hostname is in target_servers of user1
skipping: [ubuntu18] => (item=user2)
ok: [ubuntu18] => (item=user2) =>
msg: hostname is in target_servers of user2

Jinja2 expression for split , replace and join

In Ansible playbooks, We have the variable 'dns_name: xyz.abc.pqr.*.com' where as we have one template file called es_config.yml there the value of cname should be (cname: .abc.pqr..com)
How can we write jinja2 expression for this ?
dns_name: xyz.abc.com (Or) xyz.abc.pqr.***.com
cname: *.abc.com (Or) .abc.pqr.**.com (We have to use variable of dns_name)
Playbook
- hosts: elastic-search-servers
gather_facts: true
vars:
es_admin_hostname: test.develop123.com
tasks:
- name: split string
set_fact:
cname: "{{ es_admin_hostname.split('.') }} | first | replace('*')"
- name: debug
debug:
msg: "{{ cname[1:] }} is dns name"
Required Output
*.develop123.com
just change your split by regex_replaces:
- name: split string
set_fact:
cname: "{{ es_admin_hostname| regex_replace('^[^\\.]+', '*') }}"
- name: debug
debug:
msg: "{{ cname }} is dns name"
result:
ok: [localhost] => {
"msg": "*.develop123.com is dns name"
}
'^[^\\.]+' means trap all chars from beginning of string until i meet a dot and replace them by * (the \\ is needed because . is special char)

How do you properly loop through a file for tasks within a block in Ansible?

This has been a challenge and I am stuck on what else to try. Basically, I have a text file that has an unknown amount of lines. I wish to read through each line and set facts for each line that is then used as variables for a win_domain_user module.
If there is only one line in the text file, then I can accomplish my task with no issues or errors. But, if there is more than one line, I do not know how to start the process again with the next line.
For example:
- name: "Pre-task AD | Get user account info"
win_shell: |
Get-Content C:\AD\user_info.txt
register: 'read_output'
The line of text in the user_info.txt file looks like this:
first-John;last-Doe;display_name-Doe, John (US);title-Engineer;employee_type-Employee;user-john.doe;email-john.doe#company.com;customer-Some_Business;dept_groups-Test_Group
Example of two lines:
first-John;last-Doe;display_name-Doe, John (US);title-Engineer;employee_type-Employee;user-john.doe;email-john.doe#company.com;customer-Some_Business;dept_groups-Test_Group
first-Jane;last-Doe;display_name-Doe, Jane (US);title-Engineer II;employee_type-Employee;user-jane.doe;email-jane.doe#company.com;customer-Some_Business;dept_groups-Test_Group
The set facts looks like:
- set_fact:
first: "{{ read_output.stdout | regex_search(...) }}"
last: "{{ read_output.stdout | regex_search(...) }}"
user: "{{ read_output.stdout | regex_search(...) }}"
email: "{{ read_output.stdout | regex_search(...) }}"
customer: "{{ read_output.stdout | regex_search(...) }}"
title: "{{ read_output.stdout regex_search(...) }}"
display_name: "{{ read_output.stdout regex_search(...) }}"
employee_type: "{{ read_output.stdout | regex_search(...) }}"
dept_groups: "{{ read_output.stdout | regex_search(...) }}"
The winrm Active Directory related module looks like:
- name: "Active Directory | Add domain user account to domain controller"
win_domain_user:
firstname: "{{ first }}"
surname: "{{ last }}"
name: "{{ first }}{{ space }}{{ last }}"
description: "{{ title }}"
password: "{{ password_var_here}}"
upn: "{{ user }}{{ domain_fqdn_var_here }}"
email: "{{ email }}"
state: 'present'
path: "{{ ou_path_here }}"
groups: "Domain Users, {{ dept_groups }}"
attributes:
displayName: "{{ first }}{{ space }}{{ last }}"
domain_username: "{{ domainusername_var_here }}"
domain_password: "{{ domainpassword_var_here }}"
domain_server: "{{ dc_var_here }}"
register: 'add_ad_user'
I tried using a win_shell ForEach (PowerShell) loop to action the lines in C:\AD\user_info.txt but I was not able to figure out how to use set_facts within the ForEach loop and use the win_domain_user module within it. So, instead, I looked into using with_items which from what I understand is now loop. From the info above, I was going to create 2 yml files, one that has the 'Get-Content C:\AD\user_info.txt' / register and then use the include_tasks yml with block for the tasks to run against the data from the Get-Content.
The tricky part is that I do not know how many lines are going to be in that user_info.txt file. Am I on the right track as far as using two files for this or is there a more efficient way?
Each line from the file will be in the registered variable read_output.stdout_lines. We can loop with this variable and split the line using semi-colon (;) to get the individual attributes of the user.
If we take only the first line, the example below with return John:
"{{ read_output.stdout_lines[0].split(';')[0].split('-')[1] }}"
Similarly a debug task:
- name: "Pre-task AD | Get user account info"
win_shell: |
Get-Content C:\AD\user_info.txt
register: read_output
- name: Show user details after splitting
debug:
msg:
- "First: {{ first }}"
- "Last: {{ last }}"
- "Display name: {{ display_name }}"
vars:
first: "{{ item.split(';')[0].split('-')[1] }}"
last: "{{ item.split(';')[1].split('-')[1] }}"
display_name: "{{ item.split(';')[2].split('-')[1] }}"
loop: "{{ read_output.stdout_lines }}"
will produce:
"msg": [
"First: John",
"Last: Doe",
"Display name: Doe, John (US)"
]
Continuing with this theme, we can have the win_domain_user task like (not tested):
- name: "Active Directory | Add domain user account to domain controller"
win_domain_user:
firstname: "{{ first }}"
surname: "{{ last }}"
name: "{{ first }} {{ last }}"
description: "{{ title }}"
password: "{{ password_var_here }}"
upn: "{{ user }}{{ domain_fqdn_var_here }}"
email: "{{ email }}"
state: 'present'
path: "{{ ou_path_here }}"
groups: "Domain Users, {{ dept_groups }}"
attributes:
displayName: "{{ first }} {{ last }}"
domain_username: "{{ domainusername_var_here }}"
domain_password: "{{ domainpassword_var_here }}"
domain_server: "{{ dc_var_here }}"
vars:
first: "{{ item.split(';')[0].split('-')[1] }}"
last: "{{ item.split(';')[1].split('-')[1] }}"
display_name: "{{ item.split(';')[2].split('-')[1] }}"
name: "{{ first }} {{ last }}"
title: "{{ item.split(';')[3].split('-')[1] }}"
email: "{{ item.split(';')[6].split('-')[1] }}"
user: "{{ item.split(';')[5].split('-')[1] }}"
dept_groups: "{{ item.split(';')[8].split('-')[1] }}"
loop: "{{ read_output.stdout_lines }}"
As already stated in the comments, the real good solution would be to change your file format at source to something that is easy to parse. Being in ansible the best format would indeed be yaml/json. But any other standard would make this much easier (xml, csv...).
Note: I know your current data is actually csv with semicolon delimiters. But it doesn't have a header and values contain the field name that must be extracted on each line
As an other remark: registering the output of a cat command (e.g. Get-Content) from a (win-)shell to get a file content is a bad practice. This is precisely why the fetch and slurp modules exist.
The below is just a workaround to get a dict out of your custom and not really ideal format. It will break as soon as a value does not strictly respect the current inferred schema (i.e. contains a dash, a semicolon, a new line...) and will probably become a real nightmare to maintain every time you encounter an exception. But at least it does what you expect from your example and you can build upon that to adapt it to your exact needs.
Edit: my original solution was using a regex (see history if you want to look at it). But #seshadri_c opened my eyes on a much simpler solution that leads to the same result using only split. I borrowed the idea while keeping the key/value mapping dynamic.
Given the following files/user_data.txt
first-John;last-Doe;display_name-Doe, John (US);title-Engineer;employee_type-Employee;user-john.doe;email-john.doe#company.com;customer-Some_Business;dept_groups-Test_Group
first-Jane;last-Doe;display_name-Doe, Jane (US);title-Engineer II;employee_type-Employee;user-jane.doe;email-jane.doe#company.com;customer-Some_Business;dept_groups-Test_Group
The loop_bad_file.yml playbook
---
- name: Loop on a poorly formated file
hosts: localhost
gather_facts: false
vars:
# File name on the remote (here relative to the current dir for the example....)
bad_file_name: files/user_data.txt
# list of user dicts we get by:
#
# 1. spliting each line from the original file (`select` removes the last trainling blank line)
# So we get ['line1....', 'line2....']
#
# 2. split each line on semicolons so we get:
# [['key1-value1', 'key2-value2', ....], ['key1-value1', 'key2-value2', ....], ...]
#
# 3. split each field-value on a dash so we get:
# [[['key1', 'value1'], ['key2', 'value2'], ..., [['key1', 'value1'], ['key2', 'value2'], ...], ...]
#
# 4. Turn this back into a list of dicts using the key/values pairs in each element
user_list: "{{
(bad_file.content | b64decode).split('\n') | select
| map('split', ';')
| map('map', 'split', '-')
| map('items2dict', key_name=0, value_name=1)
}}"
tasks:
- name: Get file content from remote
slurp:
src: "{{ bad_file_name }}"
register: bad_file
- name: Show the result using the transformed list of dicts
vars:
debug_msg: |-
This is entry for {{ item.display_name }}
first: {{ item.first }}
last: {{ item.last }}
title: {{ item.title }}
employee type: {{ item.employee_type }}
email: {{ item.email }}
customer: {{ item.customer }}
department group: {{ item.dept_groups }}
debug:
msg: "{{ debug_msg.split('\n') }}"
loop: "{{ user_list }}"
Gives:
$ ansible-playbook loop_bad_file.yml
PLAY [Loop on a poorly formated file] *********************************************************************
TASK [Get file content from remote] *********************************************************************
ok: [localhost]
TASK [Show the result using the transformed list of dicts] *********************************************************************
ok: [localhost] => (item={'first': 'John', 'last': 'Doe', 'display_name': 'Doe, John (US)', 'title': 'Engineer', 'employee_type': 'Employee', 'user': 'john.doe', 'email': 'john.doe#company.com', 'customer': 'Some_Business', 'dept_groups': 'Test_Group'}) => {
"msg": [
"This is entry for Doe, John (US)",
"first: John",
"last: Doe",
"title: Engineer",
"employee type: Employee",
"email: john.doe#company.com",
"customer: Some_Business",
"department group: Test_Group"
]
}
ok: [localhost] => (item={'first': 'Jane', 'last': 'Doe', 'display_name': 'Doe, Jane (US)', 'title': 'Engineer II', 'employee_type': 'Employee', 'user': 'jane.doe', 'email': 'jane.doe#company.com', 'customer': 'Some_Business', 'dept_groups': 'Test_Group'}) => {
"msg": [
"This is entry for Doe, Jane (US)",
"first: Jane",
"last: Doe",
"title: Engineer II",
"employee type: Employee",
"email: jane.doe#company.com",
"customer: Some_Business",
"department group: Test_Group"
]
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You may want to try to use csvfile lookup plugin [1] to load data from csv file as a variable or iterable object.
[1] https://docs.ansible.com/ansible/latest/collections/ansible/builtin/csvfile_lookup.html

Resources