Print only one item in list in ansible - ansible

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}}"

Related

ansible_facts.packages "skipping" my package check

I would like to check if my package is installed, but I would like to have all package types with a name.
Let me explain: I am looking for the OB2 package, but there are plenty of them, so I am looking for OB2* but it skips my search.
I tested with the name without wildcard (*) but it doesn't work any better:
- name: Gather the package facts
ansible.builtin.package_facts:
manager: auto
- name: Print the package facts
ansible.builtin.debug:
var: ansible_facts.packages
- name: Check whether a package called OB2 is installed
ansible.builtin.debug:
msg: "{{ ansible_facts.packages['OB2*'] | length }} versions of OB2are installed!"
when: "'OB2*' in ansible_facts.packages"
Is there a solution to find all outputs that start with OB2 within ansible_facts.package?
ansible_facts.packages is a dictionary. There are no keys in that dictionary named OB2* nor OB2 since no package has this exact name.
If you want to get all keys which names start with the string "OB2", one way is to filter out all others.
Transform you dict to a key/value list with the dict2items filter
Use the selectattr filter and apply the match test to find the relevant entries.
Since the key name is also contained in the parameter name inside each element in the value list , you can retain only the value using the map(attribute='someattr') filter
Last, flatten the list to get one single list containing all packages version
Here is a playbook illustrating the concept. For the example, I used as a prefix "zlib". Just change it back to whatever suits your needs
---
- hosts: localhost
gather_facts: false
vars:
package_prefix: "zlib"
filtered_packages: "{{ ansible_facts.packages | dict2items
| selectattr('key', 'match', package_prefix)
| map(attribute='value') | flatten }}"
tasks:
- name: gather package facts
ansible.builtin.package_facts:
- name: debug the raw variable
debug:
var: filtered_packages
- name: count relevant packages
vars:
pkg_num: "{{ filtered_packages | count }}"
debug:
msg: "There are {{ pkg_num }} packages
which name starts with {{ package_prefix }}"
- name: show some info about relevant packages
debug:
msg: "Package named {{ item.name }} is in category {{ item.category }}
and has version {{ item.version }}"
loop: "{{ filtered_packages }}"
Which gives on my Ubuntu local machine:
PLAY [localhost] *****************************************************************************************************************
TASK [gather package facts] ******************************************************************************************************
ok: [localhost]
TASK [debug the raw variable] ****************************************************************************************************
ok: [localhost] => {
"filtered_packages": [
{
"arch": "amd64",
"category": "libs",
"name": "zlib1g",
"origin": "Ubuntu",
"source": "apt",
"version": "1:1.2.11.dfsg-2ubuntu1.3"
},
{
"arch": "amd64",
"category": "libdevel",
"name": "zlib1g-dev",
"origin": "Ubuntu",
"source": "apt",
"version": "1:1.2.11.dfsg-2ubuntu1.3"
}
]
}
TASK [count relevant packages] ***************************************************************************************************
ok: [localhost] => {
"msg": "There are 2 packages which name starts with zlib"
}
TASK [show some info about relevant packages] ************************************************************************************
ok: [localhost] => (item={'name': 'zlib1g', 'version': '1:1.2.11.dfsg-2ubuntu1.3', 'arch': 'amd64', 'category': 'libs', 'origin': 'Ubuntu', 'source': 'apt'}) => {
"msg": "Package named zlib1g is in category libs and has version 1:1.2.11.dfsg-2ubuntu1.3"
}
ok: [localhost] => (item={'name': 'zlib1g-dev', 'version': '1:1.2.11.dfsg-2ubuntu1.3', 'arch': 'amd64', 'category': 'libdevel', 'origin': 'Ubuntu', 'source': 'apt'}) => {
"msg": "Package named zlib1g-dev is in category libdevel and has version 1:1.2.11.dfsg-2ubuntu1.3"
}
PLAY RECAP ***********************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You have to filter the keys, a direct address is not possible, because you don't know the exact name.
- name: Gather the package facts
ansible.builtin.package_facts:
- name: Filter package names
set_fact:
filtered_package_names: "{{ ansible_facts.packages | list
| map('regex_search', '^vim.*') | select('string') | list }}"
- name: Print filtered packages
debug:
var: filtered_package_names
- name: Print package details from all filtered packages
debug:
msg: "{{ ansible_facts.packages[item] }}"
with_items: "{{ filtered_package_names }}"
With list a list of the keys is created, then you can filter this list with regex_search, afterwards the list is reduced to the filter result.
== Edit begin
There is a smarter filter method. Instead of using map(regex_search) / select(string), you could use directly select(match), so the filtering would look like:
- name: Filter package names
set_fact:
filtered_package_names: "{{ ansible_facts.packages | list
| select('match', '^vim.*') | list }}"
== Edit end
The result is a list of package names that match your regex.
If you need more information about one of the packages, you can then use ansible_facts.packages[_your_item] to get the rest of the information.
Example output of the above tasks:
TASK [Gather the package facts] ****************************************************************************************
ok: [localhost]
TASK [Filter package names] ********************************************************************************************
ok: [localhost]
TASK [Print filtered packages] *****************************************************************************************
ok: [localhost] => {
"filtered_package_names": [
"vim",
"vim-common",
"vim-runtime",
"vim-tiny"
]
}
TASK [Print package details] *******************************************************************************************
ok: [localhost] => (item=vim) => {
"msg": [
{
"arch": "amd64",
"category": "editors",
"name": "vim",
"origin": "Ubuntu",
"source": "apt",
"version": "2:8.1.2269-1ubuntu5.7"
}
]
}
ok: [localhost] => (item=vim-common) => {
"msg": [
{
"arch": "all",
"category": "editors",
"name": "vim-common",
"origin": "Ubuntu",
"source": "apt",
"version": "2:8.1.2269-1ubuntu5.7"
}
]
}
ok: [localhost] => (item=vim-runtime) => {
"msg": [
{
"arch": "all",
"category": "editors",
"name": "vim-runtime",
"origin": "Ubuntu",
"source": "apt",
"version": "2:8.1.2269-1ubuntu5.7"
}
]
}
ok: [localhost] => (item=vim-tiny) => {
"msg": [
{
"arch": "amd64",
"category": "editors",
"name": "vim-tiny",
"origin": "Ubuntu",
"source": "apt",
"version": "2:8.1.2269-1ubuntu5.7"
}
]
}

Ansible subelements() filter where a subelement is an empty list

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

add_host - add all host

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 }}"

Ansible read variable from external file

I am storing all variables in json format in a external file and trying to read in the playbook
Here's an example
file
{out_file: exp_app_20.xml, control_file: export_control.xml }
{out_file: exp_app_21.xml, control_file: export_control.xml }
Now when I am trying to read the variables out_file and control_file I am not able to find a suitable way to read it .
I have tried with_items and with_lines but no luck
- name: searching for text file
gather_facts: false
vars:
host_tgt: TGT
hosts: "{{ host_tgt }}"
tasks:
- name: get the file contents
shell: cat /dir/export.prop
register: my_items
- debug:
var: my_items
- name: Export
shell: echo {{ item.out_file }} **---error**
with_items: my_items.stdout_lines
register: find_output
- debug:
var: find_output
Any suggestions appreciated
Assuming you can change the format of the file....
{
"array": [
{ "out_file": "exp_app_20.xml", "control_file": "export_control.xml" },
{ "out_file": "exp_app_21.xml", "control_file": "export_control.xml" }
]
}
Then in your playbook, load the file with include_vars....
tasks:
- include_vars:
file: /home/jack/test.json
name: my_items
- debug:
var: my_items
- debug:
msg: "{{ item.out_file }}"
with_items: "{{ my_items.array }}"
That gives this output:
TASK [debug] ************************************************************************************************************************
ok: [localhost] => {
"my_items": {
"array": [
{
"control_file": "export_control.xml",
"out_file": "exp_app_20.xml"
},
{
"control_file": "export_control.xml",
"out_file": "exp_app_21.xml"
}
]
}
}
TASK [debug] ************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "exp_app_20.xml"
}
ok: [localhost] => (item=None) => {
"msg": "exp_app_21.xml"
}

How to loop over this dictionary in Ansible?

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

Resources