How do I work with Ansible list of dicts? - ansible

I'm so confused with this. If I have a file containing:
users:
- name: jconnor
first: john
last: connor
uid: 3003
- name: sconnor
first: sarah
last: connor
uid: 3001
How do I get the details of each user? With this simple playbook:
- name: create users
hosts: localhost
gather_facts: false
tasks:
- name: Include vars
include_vars:
file: user_list.yml
name: users
- name: debug
debug:
msg: "{{ item }}"
with_dict: "{{ users }}"
I get the following which I can't use:
ok: [localhost] => (item={'value': [{u'last': u'connor', u'uid': 3003, u'name': u'jconnor', u'first': u'john'}, {u'last': u'connor', u'uid': 3001, u'name': u'sconnor', u'first': u'sarah'}], 'key': u'users'}) => {
"msg": {
"key": "users",
"value": [
{
"first": "john",
"last": "connor",
"name": "jconnor",
"uid": 3003
},
{
"first": "sarah",
"last": "connor",
"name": "sconnor",
"uid": 3001
}
]
}
}
I want to create user accounts with this but I simply don't understand how to use this structure.
Note that this is part of a larger structure and I can't change it.
Thanks

Since the users variable is a list of dicts, you should loop with loop or with_items. Then we can access the key of each dict with item.key. E.g.: item.name, item.uid, etc.
Note that you are importing the variables from the file with the name users. So this variable now contains the users hash of that file. If you skip name: users in include_var, then you can directly access the users dict while looping.
tasks:
- include_vars:
file: user_list.yml
name: users
- debug:
msg: "Username is {{ item.name }}, full name is {{ item.first }} {{ item.last }}, userid is {{ item.uid }}"
with_items: "{{ users.users }}"
This outputs message (showing 1 item):
ok: [localhost] => (item={u'last': u'connor', u'uid': 3003, u'name': u'jconnor', u'first': u'john'}) => {
"msg": "Username is jconnor, full name is john connor, userid is 3003"
}

Related

Ansible loop one COMPLEX array of objects looking for case in-sensitive matches in the second COMPLEX array

I have two complex objects with the following structures:
ProgA_Users
{
"Resources": [
{
"emails": [
{
"type": "work",
"value": "Oscar.Warren#domain.local"
}
],
"id": "2939XYZ",
"userName": "Oscar.Warren#domain.local#SOMEREALM"
},
{
"emails": [
{
"type": "work",
"value": "Alexandra.Savage#domain.local"
}
],
"id": "2032XYZ",
"userName": "Alexandra.Savage#domain.local#SOMEREALM"
}
]
}
ProgB_Usrs
[
{
"DistinguishedName": "CN=Warren\\, Oscar J.,OU=Users,OU=Finance Division,OU=Departments,DC=domain,DC=local",
"Name": "Warren, Oscar J.",
"ObjectClass": "user",
"mail": "Oscar.Warren#domain.local"
},
{
"DistinguishedName": "CN=Bodden\\, John B.,OU=Users,OU=Finance Division,OU=Departments,DC=domain,DC=local",
"Name": "Bodden, John B.",
"ObjectClass": "user",
"mail": "John.Bodden#domain.local"
}
]
In an Ansible Playbook I need to loop through the list of ProgA_Users and find all ProgB_Users with no matching mail attribute.
I tried the following YAML:
- name: Process Users
debug:
msg: "{{ item.userName }}"
loop: "{{ ProgA_Users.Resources }}"
loop_control:
loop_var: item
when: "{{ ad_users.objects|selectattr('mail','equalto','{{ item.emails[0].value }}')|list|length == 0 }}"
The problem is that Ansible is running the debug task on ALL objects, even when there is a matching member within ProgB_User.
I want the debug task to execute each time Ansible comes across a member of ProgA_Users that is not in ProgB_Users. The two arrays have objects with different schemas, so I am matching {ProgA_User}.emails[0].value against {ProgB_User}.mail. The match should not be case-sensitive.
How can I achieve this outcome?
The task below
- name: Find all ProgB_Users with no matching mail attribute
debug:
msg: "{{ ProgB_Users|rejectattr('mail', 'in', emails) }}"
loop: "{{ ProgA_Users.Resources }}"
loop_control:
label: "{{ emails }}"
vars:
emails: "{{ item.emails|map(attribute='value')|list }}"
gives
TASK [Find all ProgB_Users with no matching mail attribute] ************
ok: [localhost] => (item=['Oscar.Warren#domain.local']) =>
msg:
- DistinguishedName: CN=Bodden,OU=Departments,DC=domain,DC=local
Name: Bodden, John B.
ObjectClass: user
mail: John.Bodden#domain.local
ok: [localhost] => (item=['Alexandra.Savage#domain.local']) =>
msg:
- DistinguishedName: CN=Warren,OU=Departments,DC=domain,DC=local
Name: Warren, Oscar J.
ObjectClass: user
mail: Oscar.Warren#domain.local
- DistinguishedName: CN=Bodden,OU=Departments,DC=domain,DC=local
Name: Bodden, John B.
ObjectClass: user
mail: John.Bodden#domain.local
Example of a complete playbook for testing
- hosts: localhost
vars:
ProgA_Users:
Resources:
- emails:
- type: work
value: Oscar.Warren#domain.local
id: 2939XYZ
userName: Oscar.Warren#domain.local#SOMEREALM
- emails:
- type: work
value: Alexandra.Savage#domain.local
id: 2032XYZ
userName: Alexandra.Savage#domain.local#SOMEREALM
ProgB_Users:
- DistinguishedName: CN=Warren,OU=Departments,DC=domain,DC=local
Name: Warren, Oscar J.
ObjectClass: user
mail: Oscar.Warren#domain.local
- DistinguishedName: CN=Bodden,OU=Departments,DC=domain,DC=local
Name: Bodden, John B.
ObjectClass: user
mail: John.Bodden#domain.local
tasks:
- name: Find all ProgB_Users with no matching mail attribute
debug:
msg: "{{ ProgB_Users|rejectattr('mail', 'in', emails) }}"
loop: "{{ ProgA_Users.Resources }}"
loop_control:
label: "{{ emails }}"
vars:
emails: "{{ item.emails|map(attribute='value')|list }}"

Loop with subelements filter

Variable:
customers:
- name: CompanyX
destination_addresses:
- 192.168.0.0/24
- 192.168.1.0/24
- name: CompanyY
destination_addresses:
- 192.168.2.0/24
- 192.168.3.0/24
I'm trying to create address objects for each address in destination_addresses, and create an address group object that stores all addresses per customer.
The creation of each address works as expected like this:
- name: add address object for destination networks
fortinet.fortios.fortios_firewall_address:
state: present
firewall_address:
name: "{{ item.0.name }}-{{ item.1 }}"
subnet: "{{ item.1 }}"
loop: "{{ customers | subelements('destination_addresses') }}"
This creates:
CompanyX-192.168.0.0/24
CompanyX-192.168.1.0/24
CompanyY-192.168.2.0/24
CompanyY-192.168.3.0/24
But I'm struggling how to group the address objects.
This is what I use now:
- set_fact:
grp_members: "{{ grp_members | default([]) + [{ 'name': item.0.name ~ '-' ~ item.1 }] }}"
loop: "{{ customers | subelements('destination_addresses') }}"
loop_control:
extended: yes
- name: create address group
fortinet.fortios.fortios_firewall_addrgrp:
state: present
firewall_addrgrp:
name: "{{ item.name }}"
member: "{{ grp_members }}"
loop: "{{ customers }}"
Which creates the group CompanyX and CompanyY but with all addresses in each group, because the grp_members variable contains all addresses.
How can I limit the group members to only contain the addresses for CompanyX & CompanyY separately?
Current output:
- debug:
var: grp_members
"grp_members": [
{
"name": "CompanyX-192.168.0.0/24"
},
{
"name": "CompanyX-192.168.1.0/24"
},
{
"name": "CompanyY-192.168.2.0/24"
},
{
"name": "CompanyY-192.168.3.0/24"
}
]
Desired result for each customer:
"grp_members": [
{
"name": "CompanyX-192.168.0.0/24"
},
{
"name": "CompanyX-192.168.1.0/24"
}
]
"grp_members": [
{
"name": "CompanyY-192.168.2.0/24"
},
{
"name": "CompanyY-192.168.3.0/24"
}
]
The fortinet.fortios.fortios_firewall_addrgrp module expects a dictionary in the above syntax.
With the changed conditions you need the following:
- name: create address group
fortinet.fortios.fortios_firewall_addrgrp:
state: present
firewall_addrgrp:
name: "{{ item.name }}"
member: "{{ grp_members }}"
vars:
grp_members: "{{ [item.name] | product(item.destination_addresses) | map('join', '-') | map('community.general.dict_kv', 'name') }}"
loop: "{{ customers }}"
You continue iterating over customers. The variable grp_members is generated locally for each iteration.
via product a cross product of the customer with each IP is created
via join the two elements customer name and IP are connected.
dict_kv creates a dict from the list with the key name.
You don't need your task with set_fact anymore.
Here you can see the sample output of the joined addresses.
This task
- name: generate kv dict with customer-address
debug:
msg: "{{ grp_members }}"
vars:
grp_members: "{{ [item.name] | product(item.destination_addresses) | map('join', '-') | map('community.general.dict_kv', 'name') }}"
loop: "{{ customers }}"
produces this output
TASK [generate kv dict with customer-address] *******************************************************************************
ok: [localhost] => (item={'name': 'CompanyX', 'destination_addresses': ['192.168.0.0/24', '192.168.1.0/24']}) => {
"msg": [
{
"name": "CompanyX-192.168.0.0/24"
},
{
"name": "CompanyX-192.168.1.0/24"
}
]
}
ok: [localhost] => (item={'name': 'CompanyY', 'destination_addresses': ['192.168.2.0/24', '192.168.3.0/24']}) => {
"msg": [
{
"name": "CompanyY-192.168.2.0/24"
},
{
"name": "CompanyY-192.168.3.0/24"
}
]
}
old post
I think that's what you're looking for.
- name: create address group
fortinet.fortios.fortios_firewall_addrgrp:
state: present
firewall_addrgrp:
name: "{{ item.name }}"
member: "{{ item.destination_addresses | join(',') }}"
loop: "{{ customers }}"
I guess that member should be all addresses of the respective customer? These are in a list for the respective customer and you can join them to a string via join function.
So the task set_fact for grp_members would not be necessary.
If this is not the result you need, you have to describe it exactly.
Here you can see the sample output of the joined addresses.
This task
- name: Iterate over customers.
debug:
msg: "{{ item.name }}: {{ item.destination_addresses | join(',') }}"
loop: "{{ customers }}"
produces this output
TASK [Iterate over customers.] ***************************************************************************************
ok: [localhost] => (item={'name': 'CompanyX', 'destination_addresses': ['192.168.0.0/24', '192.168.1.0/24']}) => {
"msg": "CompanyX: 192.168.0.0/24,192.168.1.0/24"
}
ok: [localhost] => (item={'name': 'CompanyY', 'destination_addresses': ['192.168.2.0/24', '192.168.3.0/24']}) => {
"msg": "CompanyY: 192.168.2.0/24,192.168.3.0/24"
}

Ansible nested loop

Is it possible for Ansible to do a nested loop as I want to do a filtering and set fact.
The first fact that I have is
"username_and_role": [
{
"Name": "Jack",
"Role": "Admin User"
},
{
"Name": "Tom",
"Role": "Buyer"
},
{
"Name": "Jill",
"Role": "Seller"
}
]
The second fact that I have is
"username_and_status": [
{
"isLockedOut": "no",
"Name": "Jack"
},
{
"isLockedOut": "no",
"Name": "Tom"
},
{
"isLockedOut": "no",
"Name": "Jill"
},
{
"isLockedOut": "no",
"Name": "Brody"
},
{
"isLockedOut": "no",
"Name": "Steve"
}
]
My objective is that I want to retrieve isLockedOut from second data. If name in username_and_role is equal to name in username_and_status, retrieve the isLockedOut from username and status. and set a fact that show name, role and isLockedOut.
I've tried a few methods and it doesn't work as it didn't do the checking.
set_fact:
my_new_list: "{{[username_and_role, {'status': nameIsMatch}] }}"
cacheable: yes
with_items: "{{username_and_role}}"
vars:
nameIsMatch: "{% if item.Name == username%}item.isLockedOut{% else %}Name Does not Match{%endif %}"
loop: "{{username_and_status}}"
This is code has no error but it just did not do the checking between username_and_role & username_and_status
Convert the lists to dictionaries, e.g.
- set_fact:
username_and_role: "{{ username_and_role|
items2dict(key_name='Name',
value_name='Role') }}"
gives
username_and_role:
Jack: Admin User
Jill: Seller
Tom: Buyer
- set_fact:
username_and_status: "{{ username_and_status|
items2dict(key_name='Name',
value_name='isLockedOut') }}"
gives
username_and_status:
Brody: 'no'
Jack: 'no'
Jill: 'no'
Steve: 'no'
Tom: 'no'
Now you can retrieve the status by using the dictionary, e.g.
- debug:
msg: >
name: {{ item }}
role: {{ username_and_role[item]|d('NA') }}
status: {{ username_and_status[item]|d('NA') }}
loop: "{{ username_and_status.keys()|list }}"
gives
msg: |-
name: Jack role: Admin User status: no
msg: |-
name: Tom role: Buyer status: no
msg: |-
name: Jill role: Seller status: no
msg: |-
name: Brody role: NA status: no
msg: |-
name: Steve role: NA status: no
just to precise the nice answer of #vladimir-botka, if you just want the persons which are in both variables,
you could add a condition when
- debug:
msg: >
name: {{ item }}
role: {{ username_and_role[item] }}
status: {{ username_and_status[item] }}
when: username_and_role[item] is defined and username_and_status[item] is defined
loop: "{{ username_and_status.keys()|list }}"
- debug:
var: username_and_role | to_nice_json
result:
ok: [localhost] =>
username_and_role | to_nice_json: |-
{
"Jack": "Admin User",
"Jill": "Seller",
"Tom": "Buyer"
}

Tell Ansible to iterate through a (network camera) configuration json file and update every value via a new api call

I've got a .json file filled with hundreds of configuration values for an Axis network camera. The contents look like this:
{
"Name": "HTTPS.Port",
"Value": "443"
},
{
"Name": "Image.DateFormat",
"Value": "YYYY-MM-DD"
},
{
"Name": "Image.MaxViewers",
"Value": "20"
},
The Axis API, called Vapix, only provides an update function that updates a single value, so I have to circle through the values and trigger a new API call with every iteration:
name: update parameters
local_action:
module: uri
user: x
password: y
url: "{{ axis_snmp_role.server_url }}?action=update&{{ item }}"
with_items:
- "SNMP.V2c=yes"
- "SNMP.Enabled=yes"
- "ImageSource.I0.Sensor.ExposureValue=100"
Now the above example requires me to hardcode hundreds of config values into the loop. Is there a way to tell Ansible to go through the camera configuration json, update every value via a new api call and stop when there are no more values left within the json file?
Given the configuration's values is a list. For example
shell> cat data.yml
config: [
{"Name": "HTTPS.Port", "Value": "443"},
{"Name": "Image.DateFormat", "Value": "YYYY-MM-DD"},
{"Name": "Image.MaxViewers", "Value": "20"}]
The play
- hosts: localhost
tasks:
- include_vars:
file: data.json
- debug:
msg: "?action=update&{{ item.Name }}={{ item.Value }}"
loop: "{{ config }}"
gives
ok: [localhost] => (item={u'Name': u'HTTPS.Port', u'Value': u'443'}) => {
"msg": "?action=update&HTTPS.Port=443"
}
ok: [localhost] => (item={u'Name': u'Image.DateFormat', u'Value': u'YYYY-MM-DD'}) => {
"msg": "?action=update&Image.DateFormat=YYYY-MM-DD"
}
ok: [localhost] => (item={u'Name': u'Image.MaxViewers', u'Value': u'20'}) => {
"msg": "?action=update&Image.MaxViewers=20"
}
If this is what you want loop the uri module. For example
- local_action:
module: uri
user: x
password: y
url: "{{ axis_snmp_role.server_url }}?action=update&{{ item.Name }}={{ item.Value }}"
loop: "{{ config }}"

How to loop through similar vars files in an Ansible playbook

I need to create a single ansible playbook that will loop through multiple vars files in a single directory and use each configuration individually in a rest API POST.
Ideally, with the following vars files...
/my/vars/dir
- my_pen.yml
pen:
color: "blue"
- her_pen.yml
pen:
color: "red"
- his_pen.yml
pen:
color: "green"
...my playbook would execute a POST for each pen. Unfortunately, all files contain a configuration for the same object type so an include_vars task would only retain the configuration for "his_pen".
I've been able to get a list of all files in the directory using find:
- name: "find config files"
find:
paths: /path/to/config/files
patterns: '*.yml'
register: config_files
I have a task that can do the POST:
- name: Do post
uri:
url: "{{ rest_api_endpoint }}"
method: POST
user: "{{ username }}"
password: "{{ password }}"
body: "{{ pen }}"
body_format: json
return_content: yes
status_code: 200
register: post_result
Now I just need to fuse the two. Is this possible? I can't change the file structure, so I have to use what's in place.
Thoughts?
Let's create the list of pens first and then loop the list. The play below
- set_fact:
pens: "{{ pens|default([]) + [ lookup('file', item)|from_yaml ] }}"
loop: "{{ lookup('fileglob', 'vars/*.yml', wantlist=True) }}"
- debug:
msg: "{{ item }}"
loop: "{{ pens }}"
gives (abridged):
ok: [localhost] => (item={'pen': {'color': u'blue'}}) => {
"msg": {
"pen": {
"color": "blue"
}
}
}
ok: [localhost] => (item={'pen': {'color': u'green'}}) => {
"msg": {
"pen": {
"color": "green"
}
}
}
ok: [localhost] => (item={'pen': {'color': u'red'}}) => {
"msg": {
"pen": {
"color": "red"
}
}
}

Resources