Ansible use dict2items with subelements - ansible

I'm new to Ansible and am having trouble figuring out how to properly do this...
I've got a list of users and under them is a list of git config settings that I want Ansible to setup for me. As an example, I've got something like this:
---
- hosts: all
gather_facts: no
vars:
user_list:
- name: john.doe
git_config:
- user.name: John Doe
- user.email: john.doe#example.com
- user.signingkey: 0A46826A
- name: jane.doe
git_config:
- user.name: Jane Doe
- diff.tool: meld
tasks:
- debug:
msg: "User: {{ item.0.name }}, Key: {{ item.1 }}"
loop: "{{ user_list | subelements('git_config') }}"
# Eventually, I would like to be able to do something like this using Ansible's git_config module: https://docs.ansible.com/ansible/2.4/git_config_module.html
# - git_config:
# name: "{{ item.1.key }}"
# value: "{{ item.1.value }}"
# scope: global
# loop: "{{ user_list | <<Some Ansible Magic>> }}"
# become: yes
# become_user: "{{ item.0.name }}"
I run a test with this
ansible-playbook git_config.yml
Which gives me this:
PLAY [all] *****************************************************************************************************************************************************************************************************************************************************************************
TASK [debug] ***************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=[{u'name': u'john.doe', u'git_config': [{u'user.name': u'John Doe'}, {u'user.email': u'john.doe#example.com'}, {u'user.signingkey': u'0A46826A'}]}, {u'user.name': u'John Doe'}]) => {
"msg": "User: john.doe, Key: {u'user.name': u'John Doe'}"
}
ok: [localhost] => (item=[{u'name': u'john.doe', u'git_config': [{u'user.name': u'John Doe'}, {u'user.email': u'john.doe#example.com'}, {u'user.signingkey': u'0A46826A'}]}, {u'user.email': u'john.doe#example.com'}]) => {
"msg": "User: john.doe, Key: {u'user.email': u'john.doe#example.com'}"
}
ok: [localhost] => (item=[{u'name': u'john.doe', u'git_config': [{u'user.name': u'John Doe'}, {u'user.email': u'john.doe#example.com'}, {u'user.signingkey': u'0A46826A'}]}, {u'user.signingkey': u'0A46826A'}]) => {
"msg": "User: john.doe, Key: {u'user.signingkey': u'0A46826A'}"
}
ok: [localhost] => (item=[{u'name': u'jane.doe', u'git_config': [{u'user.name': u'Jane Doe'}, {u'diff.tool': u'meld'}]}, {u'user.name': u'Jane Doe'}]) => {
"msg": "User: jane.doe, Key: {u'user.name': u'Jane Doe'}"
}
ok: [localhost] => (item=[{u'name': u'jane.doe', u'git_config': [{u'user.name': u'Jane Doe'}, {u'diff.tool': u'meld'}]}, {u'diff.tool': u'meld'}]) => {
"msg": "User: jane.doe, Key: {u'diff.tool': u'meld'}"
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
What I want is to be able to extract the key and value somehow (I need to know the user's name so that I can become the user and then I need to loop through all of the git config key/values so that I can use Ansible's git config module: https://docs.ansible.com/ansible/2.4/git_config_module.html):
"msg": "User: jane.doe, Key: diff.tool, Value: meld"
with something like this
- name: just testing
debug:
msg: "User: {{ item.0.name }}, Key: {{ item.1.key }}, Value: {{ item.1.value }}"
Please see git_config.yml to get a better idea of what I'm trying to do.

Related

Ansible check if value exists in dictionary list

Below is my variable list
hostlist:
- { name: 'host1', ip_addr: '192.168.2.31', hostgrp: 'physical_workstation' }
- { name: 'host2', ip_addr: '192.168.2.32', hostgrp: 'physical_workstation' }
- { name: 'host3', ip_addr: '192.168.2.33', hostgrp: 'virtual_machine' }
I treid below
- name: Conditional test
debug:
msg: "hello world"
when: hostlist|selectattr("name", "equalto", "host1")|list|length != 0
This does not work as showing below error
The error was: TemplateRuntimeError: no test named 'equalto'
There is solution of upgrding Jinaj2. But is there any other method instead of using selectattr. I wish not to upgrade Jinja2
Create the list of names and test the name is in the list, e.g.
- debug:
msg: "hello world"
loop:
- 'host1'
- 'host9'
when: item in _names
vars:
_names: "{{ hostlist|map(attribute='name')|list }}"
gives
ok: [localhost] => (item=host1) =>
msg: hello world
skipping: [localhost] => (item=host9)
Check only host1
- debug:
msg: "hello world"
when: "'host1' in _names"
vars:
_names: "{{ hostlist|map(attribute='name')|list }}"
gives
ok: [localhost] => (item=host1) =>
msg: hello world

Iterating list of dictionaries with dictionary

I'm having trouble grasping data manipulation in Ansible. Here's the setup:
- hosts: emirr01
gather_facts: no
vars:
setup:
- emirr01: { label: "label1" }
- emirr02: { label: "label2" }
- emirr03: { label: "label3" }
lookup: [ "emirr01", "emirr02"]
use_labels: [ ]
tasks:
- debug: msg="setup={{ setup }}"
- debug: msg="lookup={{ lookup }}"
- debug: msg="item0={{ item.0 }} item1={{ item.1 }}"
when: inventory_hostname == item.1
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
- set_fact:
use_labels: "{{ use_labels + [ item.1.label ] }}"
when: item.0 == item.1.label
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
- debug: msg="use_labels={{ use_labels }}"
What I need is to set a fact use_labels which is a list of labels as defined in setup list for each host found in lookup list. What I expect is a list like this:
[ "label1", "label2" ]
My problem is not being able to "reach" label in a list I get in item.1 which is (example):
item1=[{'emirr02': {'label': 'label2'}}
Here's is the error:
$ ansible-playbook test.yml -l emirr01
PLAY [emirr01] ****************************************************************************************************************************************************************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] =>
msg: 'setup=[{''emirr01'': {''label'': ''label1''}}, {''emirr02'': {''label'': ''label2''}}, {''emirr03'': {''label'': ''label3''}}]'
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] =>
msg: lookup=['emirr01', 'emirr02']
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr01']) =>
msg: 'item0={''emirr01'': {''label'': ''label1''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr02'])
ok: [emirr01] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr01']) =>
msg: 'item0={''emirr02'': {''label'': ''label2''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr02'])
ok: [emirr01] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr01']) =>
msg: 'item0={''emirr03'': {''label'': ''label3''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr02'])
TASK [set_fact] ***************************************************************************************************************************************************************************************************************************************************************************************************************************
fatal: [emirr01]: FAILED! =>
msg: |-
The conditional check 'item.0 == item.1.label' failed. The error was: error while evaluating conditional (item.0 == item.1.label): 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'label'
The error appears to be in '/home/ansible/test.yml': line 21, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- set_fact:
^ here
It all boils down to "digging" for a value from a dictionary in a list. Does anyone have an idea of how to reach item.1.label? It's a little bit frustrating since this is trivial in Python or other programming languages but in Ansible... Or maybe my mistake is to use a wrong kind of with_* loop for this purpose?
Q: "Expect a list like this [label1, label2]"
setup:
- emirr01: { label: "label1" }
- emirr02: { label: "label2" }
- emirr03: { label: "label3" }
lookup: [ "emirr01", "emirr02"]
A: This is a typical example of a wrong data structure for this use-case. A list of dictionaries can't be used in mapping the filter extract. Moreover, the keys in the dictionaries don't have to be unique, because they are hidden in the items of the list. The solution is simple with the data in the dictionary
setup:
emirr01: { label: "label1" }
emirr02: { label: "label2" }
emirr03: { label: "label3" }
- set_fact:
use_labels: "{{ lookup|map('extract', setup, 'label')|list }}"
gives
use_labels:
- label1
- label2
One of the options is creating a dictionary from the list first. For example
- set_fact:
setup2: "{{ setup2|default({})|combine(item) }}"
loop: "{{ setup }}"
Then the dictionary setup2 can be used to get the same result
It's possible to filter the list directly. For example, the task below gives the same result too
- set_fact:
use_labels: "{{ setup|map('dict2items')|map('first')|
selectattr('key', 'in', lookup )|
map(attribute='value.label')|
list }}"
Your issue with the with_nested is actually coming from how those "two-lists" loops are working.
When you are using loops like with_nested, the item.0 is the item of the first list, while the item.1 is the item of the second list.
Because lookup is a list and not a list of dictionaries like
lookup:
- label: "emirr01"
- label: "emirr02"
You can use the value of item.1 right away.
So the way to have you issue fixed is to use it this way:
- set_fact:
use_labels: "{{ use_labels + [ item.0[item.1].label ] }}"
when: item.0[item.1].label is defined
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
Here is a playbook demonstrating this:
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
use_labels: "{{ use_labels | default([]) + [ item.0[item.1].label ] }}"
when: item.0[item.1].label is defined
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
vars:
setup:
- emirr01:
label: "label1"
- emirr02:
label: "label2"
- emirr03:
label: "label3"
lookup:
- "emirr01"
- "emirr02"
- debug:
msg: "{{ use_labels }}"
Wich gives:
PLAY [localhost] ***************************************************************************************************
TASK [set_fact] ****************************************************************************************************
ok: [localhost] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr01'])
skipping: [localhost] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr02'])
skipping: [localhost] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr01'])
ok: [localhost] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr02'])
skipping: [localhost] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr01'])
skipping: [localhost] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr02'])
TASK [debug] *******************************************************************************************************
ok: [localhost] => {
"msg": [
"label1",
"label2"
]
}
PLAY RECAP *********************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How can I traverse nested lists in Ansible?

I have a list of devices, each of which has a varying list of attributes that must be created on the devices, one at a time. Is there a way to build a nested set of loops to do this? The with_nested construct applies every attribute to every device; I need only a single attribute per device per call.
This playbook demonstrates what I have tried (Ansible 2.7.1, Python 2.7.1):
- name: Test nested list traversal
hosts: localhost
connection: local
vars:
Stuff:
- Name: DeviceA
Info: AInfo
Values:
- ValueA1
- ValueA2
- Name: DeviceB
Info: BInfo
Values:
- ValueB1
- ValueB2
- ValueB3
tasks:
- name: Loop test
debug:
msg: "{{ item.Name }},{{ item.Info }}, {{ item.Values }}"
with_items:
- "{{ Stuff }}"
What I get: (One call per device, containing all attributes at once)
ok: [localhost] => (item={u'Info': u'AInfo', u'Values': [u'ValueA1', u'ValueA2'], u'Name': u'DeviceA'}) =>
msg: DeviceA,AInfo, [u'ValueA1', u'ValueA2']
ok: [localhost] => (item={u'Info': u'BInfo', u'Values': [u'ValueB1', u'ValueB2', u'ValueB3'], u'Name': u'DeviceB'}) =>
msg: DeviceB,BInfo, [u'ValueB1', u'ValueB2', u'ValueB3']
What I want (each msg represents a separate operation to be performed on the device with just that one attribute)
msg: DeviceA, AInfo, ValueA1
msg: DeviceA, AInfo, ValueA2
msg: DeviceB, BInfo, ValueB1
msg: DeviceB, BInfo, ValueB2
msg: DeviceB, BInfo, ValueB3
You can get what you want using the subelements filter:
---
- hosts: localhost
gather_facts: false
vars:
Stuff:
- Name: DeviceA
Info: AInfo
Values:
- ValueA1
- ValueA2
- Name: DeviceB
Info: BInfo
Values:
- ValueB1
- ValueB2
- ValueB3
tasks:
- debug:
msg: "{{ item.0.Name }}, {{ item.0.Info }}, {{ item.1 }}"
loop: "{{ Stuff|subelements('Values') }}"
loop_control:
label: "{{ item.0.Name }}"
Running the above playbook gets you:
PLAY [localhost] ******************************************************************************************************************************************************************************
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => (item=DeviceA) => {
"msg": "DeviceA, AInfo, ValueA1"
}
ok: [localhost] => (item=DeviceA) => {
"msg": "DeviceA, AInfo, ValueA2"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB1"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB2"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB3"
}
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0

Ansible Playbook Using Lists/Dictionaries With One Or More Values

I would like to use Ansible to automate OpenShift user creations.
One or more users may be specified with one password each
Each user will be joined to one or more groups
I'm currently testing with lists and subelements, and am close.
vars file
---
host_group: localhost
users:
- username: jdoe
password: tester123
groups:
- test-group1
- username: jdosephina
password: tester456
groups:
- test-group2
- test-group3
test.yaml
---
- hosts: "{{ host_group | default('empty_group') }}"
vars_files:
- /etc/ansible/vars/openshift_user_creation_vars.yaml
tasks:
- name: Create user on master server
debug:
msg: "htpasswd -b /etc/origin/master/htpasswd {{ item[0].username }} {{ item[0].password }}"
become: yes
with_subelements:
- "{{ users }}"
- groups
- name: Restart atomic service
debug:
msg: "systemctl restart atomic-openshift-master-api"
become: yes
- name: Add user to group
debug:
msg: "oc adm groups add-users {{ item[1] }} {{ item[0].username }}"
with_subelements:
- "{{ users }}"
- groups
The issue I have now is if multiple groups are specified (for jdosephina, in this example), the first command to create the user is also run multiple times.
TASK [Create user on master server] *********************************************************************************************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "htpasswd -b /etc/origin/master/htpasswd jdoe tester123"
}
ok: [localhost] => (item=None) => {
"msg": "htpasswd -b /etc/origin/master/htpasswd jdosephina tester456"
}
ok: [localhost] => (item=None) => {
"msg": "htpasswd -b /etc/origin/master/htpasswd jdosephina tester456"
}
TASK [Restart atomic service] ***************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "systemctl restart atomic-openshift-master-api"
}
TASK [Add user to group] ********************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "oc adm groups add-users test-group1 jdoe"
}
ok: [localhost] => (item=None) => {
"msg": "oc adm groups add-users test-group2 jdosephina"
}
ok: [localhost] => (item=None) => {
"msg": "oc adm groups add-users test-group3 jdosephina"
}
What would my best method be to run the command that uses groups one or more times based on number of groups, but only run the first command once for each user?
I needed to change {{ item[0].username }} {{ item[0].password }} in the user creation block to {{ item.username }} {{ item.password }}, remove - groups, and change with_subelements to with_items.
Full new playbook:
test.yaml
---
- hosts: "{{ host_group | default('empty_group') }}"
vars_files:
- /etc/ansible/vars/openshift_user_creation_vars.yaml
tasks:
- name: Create user on master server
debug:
msg: "htpasswd -b /etc/origin/master/htpasswd {{ item.username }} {{ item.password }}"
become: yes
with_items:
- "{{ users }}"
- name: Restart atomic service
debug:
msg: "systemctl restart atomic-openshift-master-api"
become: yes
- name: Add user to group
debug:
msg: "oc adm groups add-users {{ item[1] }} {{ item[0].username }}"
with_subelements:
- "{{ users }}"
- groups

Splitting Ansible output into 2 different strings

I have the following on my playbook:
- name: Debug
debug:
msg: "{{ item.stdout }}"
with_items:
- "{{ tapip.results }}"
- "{{ hostname.results }}"
Which returns me this:
TASK [Debug] ***************************************************************************************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "192.168.0.104"
}
ok: [localhost] => (item=None) => {
"msg": "hostname1"
}
I would like to break the hostname and ip into 2 different strings. What is the best way to achieve this?
btw, I have also tried the following:
- name: Debug
debug:
msg: "{{ item.stdout.0 }} {{ item.stdout.1 }}"
with_items:
- "{{ tapip.results }}"
- "{{ hostname.results }}"
Which returns this:
TASK [Debug] ***************************************************************************************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "1"
}
ok: [localhost] => (item=None) => {
"msg": "h"
}
Any ideas would be appreciated
Try this
name: Debug
debug:
msg: "{{ item.stdout }}"
with_items:
tapip.results
hostname.results
name: Debug
debug:
msg: "{{ item.stdout }}"
with_items:
tapip.results
hostname.results

Resources