Trying to use subelement() filter where sometimes the list will be empty. I would still like to get a list where item.0 is populated but maybe item.1 is Undefined or None. Not sure this is possible.
Sample Playbook
- hosts: localhost
connection: local
vars:
result:
virtual_machine:
interfaces:
- name: interface 1
ip_addresses:
- address: 192.168.1.22
enabled: yes
- name: interface 2
ip_addresses: []
tasks:
- name: starting var
debug: var=result
- name: filter data
set_fact:
vm_interfaces: >
{{ result.virtual_machine.interfaces
| default({})
| subelements('ip_addresses', skip_missing=True)
| list }}
- name: new filtered var
debug: var=vm_interfaces
- name: loop through new var
debug:
msg: '{{ item.0.name }} {{ item.1.address }}'
loop: '{{ vm_interfaces }}'
loop_control:
label: '{{ item.0.name }}'
Sample Output
TASK [starting var] *************************************************************************************************
ok: [localhost] => {
"result": {
"virtual_machine": {
"interfaces": [
{
"ip_addresses": [
{
"address": "192.168.1.22",
"enabled": true
}
],
"name": "interface 1"
},
{
"ip_addresses": [],
"name": "interface 2"
}
]
}
}
}
TASK [filter data] **************************************************************************************************
ok: [localhost]
TASK [new filtered var] *********************************************************************************************
ok: [localhost] => {
"vm_interfaces": [
[
{
"ip_addresses": [
{
"address": "192.168.1.22",
"enabled": true
}
],
"name": "interface 1"
},
{
"address": "192.168.1.22",
"enabled": true
}
]
]
}
TASK [loop through new var] *****************************************************************************************
ok: [localhost] => (item=interface 1) => {
"msg": "interface 1 192.168.1.22"
Current less Ansible-y Solution
The way I am currently solving this problem now is to use nested Include_tasks essentially in (2) loops. This works, but my understanding is one should strive to manipulate the data to the point you can use loop.
Here's a possible way to do it. I'm simply replacing the empty ip list with a list containing an empty object. Adapt to your own needs.
- hosts: localhost
gather_facts: false
vars:
result:
virtual_machine:
interfaces:
- name: interface 1
ip_addresses:
- address: 192.168.1.22
enabled: yes
- name: interface 2
ip_addresses: []
replace_empty_interface:
ip_addresses: [{}]
vm_interfaces_empty: >
{{
result.virtual_machine.interfaces
| default({})
| selectattr('ip_addresses', 'defined')
| selectattr('ip_addresses', '==', [])
| map('combine', replace_empty_interface)
| list
}}
vm_interfaces_filled: >
{{
result.virtual_machine.interfaces
| default({})
| selectattr('ip_addresses', 'defined')
| rejectattr('ip_addresses', '==', [])
| list
}}
vm_interfaces: >
{{
(vm_interfaces_empty + vm_interfaces_filled)
| subelements('ip_addresses')
| list
}}
tasks:
- debug:
msg: "{{ item.0 }} | {{ item.1 }}"
loop: "{{ vm_interfaces }}"
Result:
TASK [debug] ***********************************************************************************************************************************************************************************
Sunday 09 May 2021 10:14:13 +0200 (0:00:00.088) 0:00:00.088 ************
ok: [localhost] => (item=[{'name': 'interface 2', 'ip_addresses': [{}]}, {}]) => {
"msg": "{'name': 'interface 2', 'ip_addresses': [{}]} | {}"
}
ok: [localhost] => (item=[{'name': 'interface 1', 'ip_addresses': [{'address': '192.168.1.22', 'enabled': True}]}, {'address': '192.168.1.22', 'enabled': True}]) => {
"msg": "{'name': 'interface 1', 'ip_addresses': [{'address': '192.168.1.22', 'enabled': True}]} | {'address': '192.168.1.22', 'enabled': True}"
}
PLAY RECAP *************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I made a playbook that devid the hosts to windows and linux groups. this is the script (for now its just Linux and print the items that in the group):
---
- name: firstPlaybook
hosts: all
gather_facts: true
tasks:
- name: Linux Group
add_host:
name: "{{ item }}"
group: LinuxGroup
when: hostvars[item].ansible_system == 'Linux'
with_items: "{{ ansible_play_hosts }} "
run_once: yes
- name: dubug
debug:
msg: "{{ item }}"
with_items: LinuxGroup
and when I run the script with -vvv this is what I saw in the tasks itself:
TASK [Linux Group] *************************************************************
task path: /home/opc/playbooks/playbook.yml:7
skipping: [windowsserver] => (item=windowsserver) => {
"ansible_loop_var": "item",
"changed": false,
"item": "windowsserver",
"skip_reason": "Conditional result was False"
}
creating host via 'add_host': hostname=linuxserver
changed: [windowsserver] => (item=linuxserver) => {
"add_host": {
"groups": [
"LinuxGroup"
],
"host_name": "linuxserver",
"host_vars": {
"group": "LinuxGroup"
}
},
"ansible_loop_var": "item",
"changed": true,
"item": "linuxserver"
}
Perform task: TASK: dubug (N)o/(y)es/(c)ontinue: y
Perform task: TASK: dubug (N)o/(y)es/(c)ontinue: *******************************
TASK [dubug] *******************************************************************
task path: /home/opc/playbooks/playbook.yml:15
ok: [windowsserver] => (item=LinuxGroup) => {
"msg": "LinuxGroup"
}
ok: [linuxserver] => (item=LinuxGroup) => {
"msg": "LinuxGroup"
}
why its adding the windows server the the linux group even thought it suppose to be skipped (I changed the ips to windowsserver and linuxserver that I wont publish them)?
All is working as expected. Take a look at
- debug:
msg: "{{ groups.LinuxGroup }}"
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"
}
}
}
I have a variable that contains a list
- debug:
var: plugin_versions
Output
ok: [localhost] => {
"plugin_versions": [
{
"name": "ace-editor",
"version": "1.1"
},
{
"name": "analysis-core",
"version": "1.95"
},
{
"name": "ant",
"version": "1.9"
}
]
Now I want to print out only the name
What Ive tried is
- debug:
var: plugin_versions.name
- debug:
var: plugin_versions[name]
But in both cases I get
TASK [plugins : debug] ***************************************************************************************************************
ok: [localhost] => {
"plugin_versions.name": "VARIABLE IS NOT DEFINED!"
}
TASK [plugins : debug] ***************************************************************************************************************
ok: [localhost] => {
"plugin_versions[name]": "VARIABLE IS NOT DEFINED!"
}
Im kinda clueless what else can I do here to print out the name only.
you could do it in a few ways. The plugin_versions is a list of dictionaries, you can print the name attribute of each dictionary using loops, here are 2 loop examples you could use:
---
- hosts: localhost
gather_facts: false
vars:
plugin_versions:
- name: ace-editor
version: '1.1'
- name: analysis-core
version: '1.95'
- name: ant
version: '1.9'
tasks:
- name: print variable - with_items
debug:
msg: "{{ item.name }}"
with_items:
- "{{ plugin_versions }}"
- name: print variable - with map filter
debug:
var: item
with_items:
- "{{ plugin_versions | map(attribute='name') | list }}"
output:
[http_offline#greenhat-29 tests]$ ansible-playbook -i hosts test.yml
PLAY [localhost] *******************************************************************************************************************************************************************************************************
TASK [print variable - with_items] *************************************************************************************************************************************************************************************
ok: [localhost] => (item={'name': 'ace-editor', 'version': '1.1'}) => {
"msg": "ace-editor"
}
ok: [localhost] => (item={'name': 'analysis-core', 'version': '1.95'}) => {
"msg": "analysis-core"
}
ok: [localhost] => (item={'name': 'ant', 'version': '1.9'}) => {
"msg": "ant"
}
TASK [print variable - with map filter] ********************************************************************************************************************************************************************************
ok: [localhost] => (item=ace-editor) => {
"item": "ace-editor"
}
ok: [localhost] => (item=analysis-core) => {
"item": "analysis-core"
}
ok: [localhost] => (item=ant) => {
"item": "ant"
}
PLAY RECAP *************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
[http_offline#greenhat-29 tests]$
hope it helps
Since you have a list (plugin_versions) you'll need to access each element by index:
---
- hosts: all
vars:
plugin_versions:
- { name: "ace-editor", version: "1.1"}
- { name: "analysis-core", version: "1.95"}
- { name: "ant", version: "1.9"}
tasks:
- debug: var=plugin_versions[0].name
also, if you want to loop through all the items in the list you can do something like this:
- debug: "var=item['name']"
with_items: "{{ plugin_versions }}"
You can iterate over the items of your variable with:
- debug:
var: item.name
with_items: "{{plugin_versions}}"
Say I have this dictionary
war_files:
server1:
- file1.war
- file2.war
server2:
- file1.war
- file2.war
- file3.war
and for now I just want to loop over each item (key), and then over each item in the key (value). I did this
- name: Loop over the dictionary
debug: msg="Key={{ item.key }} value={{ item.value }}"
with_dict: "{{ war_files }}"
And I get this. It is of course correct, but is NOT what I want.
ok: [localhost] => (item={'value': [u'file1.war', u'file2.war'], 'key': u'server1'}) => {
"item": {
"key": "server1",
"value": [
"file1.war",
"file2.war"
]
},
"msg": "Server=server1, WAR=[u'file1.war', u'file2.war']"
}
ok: [localhost] => (item={'value': [u'file1.war', u'file2.war', u'file3.war'], 'key': u'server2'}) => {
"item": {
"key": "server2",
"value": [
"file1.war",
"file2.war",
"file3.war"
]
},
"msg": "Server=server2, WAR=[u'file1.war', u'file2.war', u'file3.war']"
}
I want to get an output that says
"msg": "Server=server1, WAR=file1.war"
"msg": "Server=server1, WAR=file2.war"
"msg": "Server=server2, WAR=file1.war"
"msg": "Server=server2, WAR=file2.war"
"msg": "Server=server2, WAR=file3.war"
IOW, how can I write a task to iterates over the dictionary so it goes through each key, and then the items within each key? In essence, I have a nested array and want to iterate over it?
Hows this
- hosts: localhost
vars:
war_files:
server1:
- file1.war
- file2.war
server2:
- file1.war
- file2.war
- file3.war
tasks:
- name: Loop over subelements of the dictionary
debug:
msg: "Key={{ item.0.key }} value={{ item.1 }}"
loop: "{{ war_files | dict2items | subelements('value') }}"
dict2items, subelements filters are coming in Ansible 2.6.
FYI, if a filter for your objective doesn't exist, you can write your own in python without having to resort to jinja2 hacks. Ansible is easily extendable; filters in filter_plugins/*.py are searched by default adjacent to your plays/roles and are automatically included - see Developing Plugins for details.
Now Ansible allows this
- name: add several users
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
with_items:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
EDIT: At the time of writing this answer, Ansible 2.6 wasn't out. Please read the answer provided by #tmoschou, as it is much better.
Well, I couldn't find a very easy way to do it, however, with a little bit of jinja2, we can achieve something of this sort:
/tmp ❯❯❯ cat example.yml
---
- hosts: 127.0.0.1
vars:
war_files:
server1:
- file1.war
- file2.war
server2:
- file1.war
- file2.war
- file3.war
tasks:
- set_fact:
war_files_list_of_dicts: |
{% set res = [] -%}
{% for key in war_files.keys() -%}
{% for value in war_files[key] -%}
{% set ignored = res.extend([{'Server': key, 'WAR':value}]) -%}
{%- endfor %}
{%- endfor %}
{{ res }}
- name: let's debug the crap out of this
debug: var=war_files_list_of_dicts
- name: Servers and their WARs!!!
debug:
msg: "Server={{ item.Server }}, WAR={{ item.WAR }}"
with_items: "{{ war_files_list_of_dicts }}"
And, when the playbook is run:
/tmp ❯❯❯ ansible-playbook example.yml
[WARNING]: provided hosts list is empty, only localhost is available
PLAY [127.0.0.1] ***************************************************************
TASK [setup] *******************************************************************
ok: [127.0.0.1]
TASK [set_fact] ****************************************************************
ok: [127.0.0.1]
TASK [let's debug the crap out of this] ****************************************
ok: [127.0.0.1] => {
"war_files_list_of_dicts": [
{
"Server": "server1",
"WAR": "file1.war"
},
{
"Server": "server1",
"WAR": "file2.war"
},
{
"Server": "server2",
"WAR": "file1.war"
},
{
"Server": "server2",
"WAR": "file2.war"
},
{
"Server": "server2",
"WAR": "file3.war"
}
]
}
TASK [Servers and their WARs!!!] ***********************************************
ok: [127.0.0.1] => (item={'WAR': u'file1.war', 'Server': u'server1'}) => {
"item": {
"Server": "server1",
"WAR": "file1.war"
},
"msg": "Server=server1, WAR=file1.war"
}
ok: [127.0.0.1] => (item={'WAR': u'file2.war', 'Server': u'server1'}) => {
"item": {
"Server": "server1",
"WAR": "file2.war"
},
"msg": "Server=server1, WAR=file2.war"
}
ok: [127.0.0.1] => (item={'WAR': u'file1.war', 'Server': u'server2'}) => {
"item": {
"Server": "server2",
"WAR": "file1.war"
},
"msg": "Server=server2, WAR=file1.war"
}
ok: [127.0.0.1] => (item={'WAR': u'file2.war', 'Server': u'server2'}) => {
"item": {
"Server": "server2",
"WAR": "file2.war"
},
"msg": "Server=server2, WAR=file2.war"
}
ok: [127.0.0.1] => (item={'WAR': u'file3.war', 'Server': u'server2'}) => {
"item": {
"Server": "server2",
"WAR": "file3.war"
},
"msg": "Server=server2, WAR=file3.war"
}
PLAY RECAP *********************************************************************
127.0.0.1 : ok=4 changed=0 unreachable=0 failed=0
Here is my preferred way to loop over dictionaries:
input_data.yml contains the following:
----
input_data:
item_1:
id: 1
info: "Info field number 1"
item_2:
id: 2
info: "Info field number 2"
I then use a data structure like the above in a play using the keys() function and iterate over the data using with_items:
---
- hosts: localhost
gather_facts: false
connection: local
tasks:
- name: Include dictionary data
include_vars:
file: data.yml
- name: Show info field from data.yml
debug:
msg: "Id: {{ input_data[item]['id'] }} - info: {{ input_data[item]['info'] }}"
with_items: "{{ input_data.keys() | list }}"
The above playbook produces the following output:
PLAY [localhost] ***********************************************************
TASK [Include dictionary data] *********************************************
ok: [localhost]
TASK [Show info field from data.yml] ***************************************
ok: [localhost] => (item=item_2) => {
"msg": "Id: 2 - info: Info field item 2"
}
ok: [localhost] => (item=item_3) => {
"msg": "Id: 3 - info: Info field item 3"
}
ok: [localhost] => (item=item_1) => {
"msg": "Id: 1 - info: Info field item 1"
}
PLAY RECAP *****************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
dict2items
I found myself wanting to iterate over a heterogeneous set of keys and their associated values and use the key-value pair in a task. The dict2items filter is the least painful way I've found. You can find dict2items in Ansible 2.6
Example Dict
systemsetup:
remotelogin: "On"
timezone: "Europe/Oslo"
usingnetworktime: "On"
sleep: 0
computersleep: 0
displaysleep: 0
harddisksleep: 0
allowpowerbuttontosleepcomputer: "Off"
wakeonnetworkaccess: "On"
restartfreeze: "On"
restartpowerfailure: "On"
Example Task
---
- debug:
msg: "KEY: {{ item.key }}, VALUE: {{ item.value }}"
loop: "{{ systemsetup | dict2items }}"
One way of doing it that worked for me was using with_dict. Note the dict should not be named. Just the key value pairs.
- name: ssh config
lineinfile:
dest: /etc/ssh/sshd_config
regexp: '^#?\s*{{item.key}}\s'
line: '{{item.key}} {{item.value}}'
state: present
with_dict:
LoginGraceTime: "1m"
PermitRootLogin: "yes"
PubkeyAuthentication: "yes"
PasswordAuthentication: "no"
PermitEmptyPasswords: "no"
IgnoreRhosts: "yes"
Protocol: 2