Processing embedded lists inside dict variables in Ansible - ansible

I got the example code in Ansible:
- name: testing
hosts: localhost
vars:
svc:
https: ['tcp/443']
app_svc: ['tcp/5543', 'udp/5543', 'tcp/3100']
tasks:
- name: print
debug:
msg: port={{item.key}} value={{item.value}}
with_dict:
- "{{svc}}"
And this outputs to:
ok: [127.0.0.1] => (item=None) => {
"msg": "port=app_svc value=[u'tcp/5543', u'udp/5543', u'tcp/3100']"
}
ok: [127.0.0.1] => (item=None) => {
"msg": "port=https value=[u'tcp/443']"
}
What I would like to achieve is, when there is more than one element in list of values it would split like this:
- name=https, prot=tcp, port=443
- name=app_svc, prot=tcp, port=5543
- name=app_svc, prot=udp, port=5543
- name=app_svc, prot=tcp, port=3100
with_dict stanza only displays me a whole list and I couldn't find a way do do it differently. Is it possible to do it like that without reorganizing the var section? Thanks in advance for input.

To see the syntax errors run
# ansible-lint <YOUR-PLAYBOOK>
Correct syntax should be
- hosts: localhost
gather_facts: no
vars:
svc:
https: ['tcp/443']
app_svc: ['tcp/5543', 'udp/5543', 'tcp/3100']
tasks:
- name: print
debug:
msg: "port={{ item.key }} value={{ item.value }}"
with_dict: "{{ svc }}"
gives
"msg": "port=app_svc value=[u'tcp/5543', u'udp/5543', u'tcp/3100']"
"msg": "port=https value=[u'tcp/443']"
Change the loop
- name: print
debug:
msg: "name={{ item.0.key }},
prot={{ item.1.split('/')[0] }},
port={{ item.1.split('/')[1] }}"
loop: "{{ lookup('subelements', svc|dict2items, 'value') }}"
to get the format
"msg": "name=app_svc, prot=tcp, port=5543"
"msg": "name=app_svc, prot=udp, port=5543"
"msg": "name=app_svc, prot=tcp, port=3100"
"msg": "name=https, prot=tcp, port=443"
dict2items is available since version 2.6 . Without "dict2items" transform the data first. See below (not tested).
https:
- {key: 'https', value: ['tcp/443']}
- {key: 'app_svc', value: ['tcp/5543', 'udp/5543', 'tcp/3100']}

Related

Ansible - List of dictionaries

Let me introduce my problem. I have some list of dictionary in my Ansible code:
my_example_list = [
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
I need execute command which will iterate over this list and it should look something like:
- name: 'Example'
shell: 'Here is my {{ item.key }} and here is {{ item.value }}'
What I've do or try to do:
I was trying to do that with with_items but i'm not able to point into value of particular key.
I've also try to filter values using | first and | last but it's not worked in my case.
What I want to achieve:
Creating loop which will iterate via that list and inject separated key and value into command.
I was asked to show how I was trying to resolve my issue:
Here is some code:
# Showing last component failing
- name: "Try to show last component of my list"
debug:
msg: "{{ my_example_list[1] | last }}"
# When i'm trying to show first component of my list i get "key1"
- name: "Try to show first component of my list"
debug:
msg: "{{ my_example_list[1] | first }}"
# This shows me my list of dict
- name: "Trying use with_items"
debug:
msg: "{{ item }}"
with_items: "{{ my_example_list }}"
# But when i'm trying point to key and value for example
- name: "Trying use with_items point to key and value"
debug:
msg: "Here is my {{ item.key }} which keep {{ item.value }}"
with_items: "{{ my_example_list }}"
# It's failing.
Sorry it's not maybe solution with using loop. I'm just stack with that issue over few days... And as first step I want to know how correctly point to pair keys and values.
It also works well:
- name: Correct solution
debug:
msg: "This is my {{ item.key }} and my value {{ item.value }}"
with_dict: "{{ my_example_list }}"
Thanks #U880D for help! I'm not able to add some plus for your solution because I'm new joiner. Appreciate your answer! :)
Your data structure and naming seems to be unfavorable. There is no need to number the key name and therefore it should be avoided. Furthermore counting list elements in Python starts at 0 not 1.
The following minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
example_list: |
[
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
tasks:
- name: Example loop
debug:
msg: "{{ item }} is of type {{ item | type_debug }}"
loop: "{{ example_list }}"
- name: Example loop
debug:
msg: "{{ item.values() }}"
loop: "{{ example_list }}"
will result into an output of
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg: '{u''key1'': u''value_of_first_key''} is of type dict'
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg: '{u''key2'': u''value_of_second_key''} is of type dict'
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg: '{u''key3'': u''value_of_third_key''} is of type dict'
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg:
- value_of_first_key
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg:
- value_of_second_key
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg:
- value_of_third_key
Further Readings
How to work with lists and dictionaries in Ansible
Extended loop variables

Unique values from ansible output dict's

I have some servers with a lot of wordpress instances, who I ask them what versions they have.
- name: CONTADOR WP VERSIONES
shell: mycommand
register: wp_versions
- debug: msg: "{{ wp_versions.stdout_lines }}"
For example:
TASK [debug] *********************************************************************
ok: [server1] => {
"msg": [
"5.1.13"
]
}
ok: [server2] => {
"msg": [
"5.1.12",
"5.1.13"
]
}
ok: [server3] => {
"msg": [
"5.1.10",
"5.1.13",
]
}
I need to list a unique values like this:
"msg": [
"5.1.10",
"5.1.12",
"5.1.13",
]
I have tried all that i found but nothing works as I want.
Thanks
Use special variable ansible_play_hosts and extract the variables from the hostvars
- set_fact:
all_vers: "{{ ansible_play_hosts|
map('extract', hostvars, ['wp_versions', 'stdout_lines'])|
flatten|unique }}"
run_once: true
gives
all_vers:
- 5.1.13
- 5.1.12
- 5.1.10
You could do something like this:
- hosts: all
gather_facts: false
tasks:
- name: CONTADOR WP VERSIONES
shell: mycommand
register: wp_versions
- hosts: localhost
gather_facts: false
tasks:
# This tasks builds a flattened list of all the
# wp_versions.stdout_lines values collected from your hosts.
- name: Collect wp_versions information
set_fact:
all_wp_versions_pre: "{{ all_wp_versions_pre + hostvars[item].wp_versions.stdout_lines }}"
loop: "{{ groups.all }}"
vars:
all_wp_versions_pre: []
# Here we use the `unique` filter to produce a list of
# unique versions.
- name: Set all_wp_versions fact
set_fact:
all_wp_versions: "{{ all_wp_versions_pre|unique }}"
- debug:
var: all_wp_versions
Given you examples, this would produce the following output:
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"all_wp_versions": [
"5.1.13",
"5.1.12",
"5.1.10"
]
}

Variable value for another variable ansible

Sorry if there are many posts about variables inside variable my use case is different.
Trying to access an element from a variable list "efs_list" based on the index-number of the current host. There are three hosts in the inventory
vars:
efs_list:
- efs1
- efs2
- efs3
sdb_index: "{{ groups['all'].index(inventory_hostname) }}"
The values should be as follows
host1- efs1
host2- efs2
host3- efs3
Tried accessing it through efs_list.{{ sdb_index }}
for - debug: var=efs_list.{{ sdb_index }} the output is as intended
ok: [10.251.0.174] => {
"efs_list.0": "efs1"
}
ok: [10.251.0.207] => {
"efs_list.1": "efs2"
}
ok: [10.251.0.151] => {
"efs_list.2": "efs3"
}
But for
- debug:
msg: "{{ efs_list.{{ sdb_index }} }}"
fatal: [10.251.0.174]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ efs_list.{{ sdb_index }} }}"}
---
- name: SDB Snapshots Creation
hosts: all
remote_user: "centos"
become: yes
vars:
efs_list:
- efs1
- efs2
- efs3
sdb_index: "{{ groups['all'].index(inventory_hostname) }}"
tasks:
- debug: var=efs_list.{{ sdb_index }}
- debug:
msg: "{{ efs_list.{{ sdb_index }} }}"
- name: Get Filesystem ID
become: false
local_action: command aws efs describe-file-systems --creation-token "{{ efs_list.{{ sdb_index }} }}"
--region us-east-1 --query FileSystems[*].FileSystemId --output text
register: fs_id
It should attribute the element of list to current indexenter code here
extract filter will do the job. The input of the filter must be a list of indices and a container (array in this case). The tasks below
- set_fact:
sdb_index: "{{ [] + [ groups['all'].index(inventory_hostname) ] }}"
- debug:
msg: "{{ sdb_index|map('extract', efs_list)|list }}"
give
ok: [host1] => {
"msg": [
"efs1"
]
}
ok: [host2] => {
"msg": [
"efs2"
]
}
ok: [host3] => {
"msg": [
"efs3"
]
}
If the hosts are not sorted in the inventory it's necessary to sort them in the play
- set_fact:
my_hosts: "{{ groups['all']|sort }}"
- set_fact:
sdb_index: "{{ [] + [ my_hosts.index(inventory_hostname) ] }}"
- debug:
msg: "{{ sdb_index|map('extract', efs_list)|list }}"

Is it possible to use variable in Jinja2 default filter?

I guess it is not possible to refer to item variable when using
Jinja2 'default' filter?
Like in this example playbook:
---
- hosts: localhost
become: no
gather_facts: no
vars:
users:
- foo:
name: "foo"
home: /home/foo
- bar:
name: "bar"
tasks:
- name: debug
debug:
msg: "{{ item.home | default('/home/{{ item.name }}') }}"
loop: "{{ users }}"
If tried I get output like:
$ ansible-playbook test.yml |grep item
ok: [localhost] => (item={u'home': u'/home/foo', u'foo': None, u'name': u'foo'}) => {
ok: [localhost] => (item={u'bar': None, u'name': u'bar'}) => {
"msg": "/home/{{ item.name }}"
Obviously I want "/home/bar" not "/home/{{ item.name }}".
Just use string concatenation in the expression, don't use nested handlebars...
"{{ item.home | default('/home/' + item.name) }}"
This adds the item.name variable to the static /home part.

Ansible v2.2 error when using with_subelements which worked fine in 1.7

I am getting the following error while running my ansible playbook.
TASK [base : Rsyslog yapilandiriliyor.]
**************************************** fatal: [gkts.ahtapot]: FAILED! => {"failed": true, "msg": "The conditional check
'ansible_fqdn == {{item.1}}' failed. The error was: error while
evaluating conditional (ansible_fqdn == {{item.1}}): 'item' is
undefined\n\nThe error appears to have been in
'/etc/ansible/roles/base/tasks/rsyslog.yml': line 2, column 3, but
may\nbe elsewhere in the file depending on the exact syntax
problem.\n\nThe offending line appears to be:\n\n---\n- name: Rsyslog
yapilandiriliyor.\n ^ here\n"}
This worked fine in ansible 1.7 but doesn't work in ansible 2.2.1
- name: Rsyslog yapilandiriliyor.
template:
src: "{{ rsyslog['conf']['source'] }}"
dest: "{{ rsyslog['conf']['destination'] }}"
owner: "{{ rsyslog['conf']['owner'] }}"
group: "{{ rsyslog['conf']['group'] }}"
mode: "{{ rsyslog['conf']['mode'] }}"
# when: "ansible_fqdn == item.1"
when: "ansible_fqdn == {{item.1}}"
with_subelements:
- "{{ossimciks}}"
- "{{clients}}"
notify:
- rsyslog servisini yeniden baslat
sudo: yes
tags: rsyslog
ossimciks and clients are defined in my vars file:
ossimciks:
server01:
fqdn: "OSSIMCIK_FQDN"
port: "20514"
clients:
- "LOG_KAYNAGI_FQDN"
- "LOG_KAYNAGI_FQDN"
What am I missing?
with_subelements:
- "{{ossimciks}}"
- "{{clients}}"
Syntax
I don't know how where/how/when this might have changed in Ansible, but I think that
the first element in with_subelements is a variable, and
the second element in with_subelements is a key.
This works in playbooks I've written, and matches the relevant docs (since 2.5, there are no with_* loops [1] in the docs since they were always lookups anyhow [2]):
with_subelements:
- "{{ ossimciks }}"
- clients
Data
It's not clear to me which item you're attempting to target with {{ item.1 }} in your playbook, but if making a minor change to your data is permissible, you can structure it so that any of the information shown is accessible within a with_subelements loop (I've also expanded and restructured the data a bit to clarify what the loop is doing):
---
- hosts: localhost
gather_facts: false
vars:
fqdn_var: "OSSIMCIK_FQDN_2"
ossimciks:
- server: "server01"
fqdn: "OSSIMCIK_FQDN_1"
port: "20514"
clients:
- "LOG_KAYNAGI_FQDN_1"
- "LOG_KAYNAGI_FQDN_2"
- server: "server02"
fqdn: "OSSIMCIK_FQDN_2"
port: "20514"
clients:
- "LOG_KAYNAGI_FQDN_1"
- "LOG_KAYNAGI_FQDN_2"
tasks:
- name: Output ossimciks contents.
debug:
msg: "{{ item.0.server }} client: {{ item.1 }}"
with_subelements:
- "{{ ossimciks }}"
- clients
when: item.0.fqdn == fqdn_var
This outputs:
skipping: [localhost] => (item=None) => {"skip_reason": "Conditional result was False"}
skipping: [localhost] => (item=None) => {"skip_reason": "Conditional result was False"}
ok: [localhost] => (item=None) => {
"msg": "server02 client: LOG_KAYNAGI_FQDN_1"
}
ok: [localhost] => (item=None) => {
"msg": "server02 client: LOG_KAYNAGI_FQDN_2"
}
[1] http://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
[2] Loops are actually a combination of things with_ + lookup(), so any lookup plugin can be used as a source for a loop, ‘items’ is lookup.
Edited to add: the playbooks where I've used the syntax above were definitely in the 2.x release series--I think ~2.1.

Resources