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"
}
Related
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 }}"
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"
}
How to loop over dict in this example?
I can loop over list of strings (lorem:), but I can not loop over objects: dict.
I am using module which needs as input list and dict, so I have be able use both cases, but before module call I have to do some workaround with keys from dict so I have to be able reference it.
I am not sure what I am doing wrong. Can anyone show me a proper example?
Thanks
---
- name: test
hosts: localhost
connection: local
vars:
persons:
foo:
name: john
state: us
objects:
phone: samsung
color: black
capacity: 32
lorem:
- 1
- 2
- 3
bar:
name: helmut
state: de
objects:
phone: lg
color: red
capacity: 16
lorem:
- 4
- 5
- 6
tasks:
- name: List of strings is OK
debug:
msg: "{{ item.0.value.name }} and object: {{ item.1 }}"
loop: "{{ persons | dict2items |subelements('value.lorem',{ 'skip_missing': True }) }}"
- name: Dict referencing key:value is not OK
debug:
msg: "Name: {{ item.0.value.name }} and object: {{ item.1.[value] }} with key name: {{ item.1.[key]}}"
loop: "{{ persons | dict2items |subelements('value.objects',{ 'skip_missing': True }) }}"
Produces an error: fatal: [localhost]: FAILED! => {"msg": "the key 'objects' should point to a list, got {u'color': u'black', u'phone': u'samsung', u'capacity': 32}"}
Best I can think of is to do something like this. Using jinja to generate a list that returns what you need.
---
- name: test
hosts: localhost
connection: local
vars:
persons:
foo:
name: john
state: us
objects:
phone: samsung
color: black
capacity: 32
lorem:
- 1
- 2
- 3
bar:
name: helmut
state: de
objects:
phone: lg
color: red
capacity: 16
lorem:
- 4
- 5
- 6
tasks:
- debug:
msg: |
[
{% for p in persons %}
{% for o in persons[p].objects %}
{
"name": "{{ persons[p].name }}",
"key": "{{ o }}",
"value": "{{ persons[p].objects[o] }}"
},
{% endfor %}
{% endfor %}
]
Output
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": [
{
"key": "color",
"name": "john",
"value": "black"
},
{
"key": "phone",
"name": "john",
"value": "samsung"
},
{
"key": "capacity",
"name": "john",
"value": "32"
},
{
"key": "color",
"name": "helmut",
"value": "red"
},
{
"key": "phone",
"name": "helmut",
"value": "lg"
},
{
"key": "capacity",
"name": "helmut",
"value": "16"
}
]
}
Oh, and if you wanted to use that in a loop, just something like this.
- debug:
msg: "{{ item.name }} {{ item.key }} {{ item.value }}"
loop_control:
label: "{{ item.name }} {{ item.key }} {{ item.value }}"
loop: |
[
{% for p in persons %}
{% for o in persons[p].objects %}
{
"name": "{{ persons[p].name }}",
"key": "{{ o }}",
"value": "{{ persons[p].objects[o] }}"
},
{% endfor %}
{% endfor %}
]
Output
TASK [debug] *******************************************************************
ok: [localhost] => (item=john color black) => {
"msg": "john color black"
}
ok: [localhost] => (item=john phone samsung) => {
"msg": "john phone samsung"
}
ok: [localhost] => (item=john capacity 32) => {
"msg": "john capacity 32"
}
ok: [localhost] => (item=helmut color red) => {
"msg": "helmut color red"
}
ok: [localhost] => (item=helmut phone lg) => {
"msg": "helmut phone lg"
}
ok: [localhost] => (item=helmut capacity 16) => {
"msg": "helmut capacity 16"
}
You can use with_items as well.
Please refer below code:
playbook -->
---
- name: test hosts: localhost gather_facts: no tasks:
- include_vars: vars.yml
- name: debug
debug:
msg: "{{ item.vars.persons }}"
with_items:
- "{{ vars }}"
output --->
"msg": {
"bar": {
"lorem": [
4,
5,
6
],
"name": "helmut",
"objects": {
"capacity": 16,
"color": "red",
"phone": "lg"
},
"state": "de"
},
"foo": {
"lorem": [
1,
2,
3
],
"name": "john",
"objects": {
"capacity": 32,
"color": "black",
"phone": "samsung"
},
"state": "us"
}
}
}
I have task which calls an API and I register the o/p in a varaible;
- name: Get Object storage account ID
uri:
url: 'https://api.softlayer.com/rest/v3.1/SoftLayer_Network_Storage_Hub_Cleversafe_Account/getAllObjects.json?objectFilter={"username":{"operation":"{{ item }}"}}'
method: GET
user: abxc
password: 66c94c447a6ed8a0cf058774fe38
validate_certs: no
register: old_existing_access_keys_sl
with_items: '{{ info["personal"].sl_cos_accounts }}'
old_existing_access_keys_sl holds:
"old_existing_access_keys_sl.results": [
{
"json": [
{
"accountId": 12345,
"id": 70825621,
"username": "xyz-11"
}
]
},
{
"json": [
{
"accountId": 12345,
"id": 70825621,
"username": "abc-12"
}
]
}
I want to make a list of id's for further processing an tried the following task but this did not work:
- name: Create a list of account ids
set_fact:
admin_usernames = "{{ item.json[0].id | list }}"
with_items: old_existing_access_keys_sl.results
I am not sure if that's even possible. I also tried this:
- name: create a list
set_fact:
foo: "{% set foo = [] %}{% for i in old_existing_access_keys_sl.results %}{{ foo.append(i) }}{% endfor %}"
foo always comes as blank and as a string:
TASK [result] *****************************************************************************************************************************************
ok: [localhost] => {
"foo": ""
}
Given your example data, you can extract a list of ids using the json_query filter, like this:
---
- hosts: localhost
gather_facts: false
vars:
old_existing_access_keys_sl:
results:
[
{
"json": [
{
"accountId": 12345,
"id": 70825621,
"username": "xyz-11"
}
]
},
{
"json": [
{
"accountId": 12345,
"id": 70825621,
"username": "abc-12"
}
]
}
]
tasks:
- debug:
var: old_existing_access_keys_sl|json_query('results[*].json[0].id')
This will output:
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"old_existing_access_keys_sl|json_query('results[*].json[0].id')": [
70825621,
70825621
]
}
If you want to store these in a new variable, you can replace that debug task with set_fact:
- set_fact:
admin_ids: "{{ old_existing_access_keys_sl|json_query('results[*].json[0].id') }}"
Update
For a list of dictionaries, just change the json_query expression:
- debug:
var: "old_existing_access_keys_sl|json_query('results[*].json[0].{id: id, username: username}')"
For more information, see the jmespath website for documentation and examples.
I recently discovered the use of for loops in Ansible and was very excited about it.
Tries to use it inside the debug module and it worked superfine, but when I am trying to use the same inside "user" module, the control flow is not able to identify the "name" keyword of user module. Below is my poetry,
- hosts: testservers
tasks:
- name: Setting user facts
set_fact:
username: "{{ lookup('ini', 'name section=userDetails file=details.ini') }}"
userpass: "{{ lookup('ini', 'password section=userDetails file=details.ini') }}"
- name: User creation
become: true
# debug:
# msg: |
# {% for x,y in item.1,item.2 %}
# {{ x }} is the username and its password is {{ y }}
# {% endfor %}
# with_items:
# - { 1: "{{ username.split(',') }}", 2: "{{ userpass.split(',') }}" }
user: |
{% for x,y in item.1,item.2 %}
name: "{{ x }}"
password: "{{ y }}"
{% endfor %}
with_items:
- { 1: "{{ username.split(',') }}", 2: "{{ userpass.split(',') }}" }
Details.ini file contents below
#User basic details
[userDetails]
name=vasanth,vasanthnagkv
password=vasanth12,pass2
The commented part above works fine. but the uncommented part throws the below error
failed: [10.0.0.47] (item={1: [u'vasanth', u'vasanthnagkv'], 2: [u'vasanth12', u'pass2']}) => {
"changed": false,
"invocation": {
"module_args": {
"append": false,
"create_home": true,
"force": false,
"move_home": false,
"non_unique": false,
"remove": false,
"ssh_key_bits": 0,
"ssh_key_comment": "ansible-generated on APUA-02",
"ssh_key_type": "rsa",
"state": "present",
"system": false,
"update_password": "always"
}
},
"item": {
"1": [
"vasanth",
"vasanthnagkv"
],
"2": [
"vasanth12",
"pass2"
]
},
"msg": "missing required arguments: name"
}
to retry, use: --limit #/home/admin/ansiblePlaybooks/userCreation/userCreate.retry
PLAY RECAP ************************************************************************************************************************************************************************************
10.0.0.47 : ok=2 changed=0 unreachable=0 failed=1
Appreciate any kind of help here.
This line user: | means your passing a string to user module using the block style indicator: https://yaml-multiline.info/)
Since Ansible will just treat it as a string, you are not passing the required name parameter to the user module
Try splitting the names after the lookup in the first task so you can have the names and passwords list:
- name: Setting user facts
set_fact:
username: "{{ lookup('ini', 'name section=userDetails file=details.ini').split(',') }}"
userpass: "{{ lookup('ini', 'password section=userDetails file=details.ini').split(',') }}"
Once you have both the username and password list, you can use both variables by:
- user:
name: "{{ item }}"
password: "{{ userpass[index] }}"
loop: "{{ username }}"
loop_control:
index_var: index