Exclude AnsibleUndefined from a list - ansible

Relevant part of task:
- name: Get join-token for worker nodes
set_fact:
join_token_worker: "{{ ansible_play_hosts_all | map('extract', hostvars, ['result', 'swarm_facts', 'JoinTokens', 'Worker']) }}"
run_once: true
- debug:
msg: "{{ join_token_worker }}"
run_once: true
And the output looks like this:
TASK [deploy_docker : Get join-token for worker nodes] ********************************************************************************************************************************************************************************************************************************************************
ok: [eu-central-1a-01]
TASK [deploy_docker : debug] **********************************************************************************************************************************************************************************************************************************************************************************
ok: [eu-central-1a-01] => {
"msg": "[AnsibleUndefined, AnsibleUndefined, AnsibleUndefined, 'SWMTKN-1-2xgm1l987hh18gbq0tc8vrwn209qlxge6nkwdq4pof2zd4e2vs-7f7ig2ama5amqq8iuii4o8xxo', AnsibleUndefined, AnsibleUndefined, 'SWMTKN-1-2xgm1l987hh18gbq0tc8vrwn209qlxge6nkwdq4pof2zd4e2vs-7f7ig2ama5amqq8iuii4o8xxo', AnsibleUndefined, AnsibleUndefined]"
}
I want to extract the token from this list - but I don't know how to filter out AnsibleUndefined value. I am using docker_swarm module to get result.

Let's minimize the case. For example, the playbook
- hosts: h1,h2,h3
gather_facts: false
vars:
d2:
h1:
k1:
k2: v1
h2:
k1:
kX: v2
h3:
k1:
k2: v3
tasks:
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', d2, ['k1', 'k2'])|
list }}"
run_once: true
gives
msg: '[''v1'', Undefined, ''v3'']'
There are more options for how to "Exclude AnsibleUndefined from a list".
Use select (credit #mdaniel)
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', d2, ['k1', 'k2'])|
select|
list }}"
run_once: true
gives
msg:
- v1
- v3
Use regex_replace
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', d2, ['k1', 'k2'])|
map('regex_replace', '(.*)', '\\1')|
list }}"
run_once: true
gives
msg:
- v1
- ''
- v3
If you want to create a dictionary iterate the hosts. For example
- set_fact:
worker_dict: "{{ worker_dict|default({})|
combine({item: d2[item].k1.k2|default('')}) }}"
loop: "{{ ansible_play_hosts_all }}"
run_once: true
gives
worker_dict:
h1: v1
h2: ''
h3: v3

Related

how to make a list from ansible_facts with multiple hosts

I'm trying to make a list with IP addresses of various hosts and then use this list in another task. My question is, how can I pick an IP (I need the public IP) from the output of each host and add it to a list? I need the IPs that do not start with 10..
Later, I need to use this list in another task.
I extract this information by running this playbook:
- hosts: facts
become: true
gather_facts: True
tasks:
- debug:
msg: "The ip: {{ item }}"
with_items: "{{ ansible_all_ipv4_addresses }}"
Later, I need to use this list in another task:
- wait_for:
host: "{{ item[0] }}"
port: "{{ item[1] }}"
state: started
delay: 0
timeout: 2
delegate_to: localhost
become: false
ignore_errors: no
ignore_unreachable: yes
register: result
failed_when: not result.failed
with_nested:
- [ IP LIST HERE]
- [443,80,9200,9300,22,5432,6432]
You can access those values from the hostvars right away, then use a reject filter with a match test in order to reject what you don't want to test for.
Which, in a debug task would gives:
# note: ports list reduced for brevity
- debug:
msg: "I should wait for interface {{ item.0 }}:{{ item.1 }}"
loop: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', ansible_play_hosts)
| map(attribute='value.ansible_all_ipv4_addresses', default=[])
| flatten
| reject('match', '10\..*')
| product(_ports)
}}
loop_control:
label: "{{ item.0 }}"
run_once: true
delegate_to: localhost
vars:
_ports:
- 22
- 80
In my lab, this give:
ok: [ansible-node-1 -> localhost] => (item=172.18.0.3) =>
msg: I should wait for interface 172.18.0.3:22
ok: [ansible-node-1 -> localhost] => (item=172.18.0.3) =>
msg: I should wait for interface 172.18.0.3:80
ok: [ansible-node-1 -> localhost] => (item=172.18.0.4) =>
msg: I should wait for interface 172.18.0.4:22
ok: [ansible-node-1 -> localhost] => (item=172.18.0.4) =>
msg: I should wait for interface 172.18.0.4:80
Try the example below
shell> cat pb.yml
- hosts: all
vars:
ip_list: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_all_ipv4_addresses')|
map('first')|list }}"
ip_list_reject: "{{ ip_list|reject('match', '10\\.')|list }}"
tasks:
- setup:
gather_subset: network
- block:
- debug:
var: ip_list
- debug:
var: ip_list_reject
- wait_for:
host: "{{ item.0 }}"
port: "{{ item.1 }}"
state: started
delay: 0
timeout: 2
delegate_to: localhost
register: result
with_nested:
- "{{ ip_list_reject }}"
- [443, 80, 9200, 9300, 22, 5432, 6432]
run_once: true

Ansible - Looking for files and compare their hash

Practice:
I have the file files.yml with a list of files and their respective md5_sum hash, like:
files:
- name: /opt/file_compare1.tar
hash: 9cd599a3523898e6a12e13ec787da50a /opt/file_compare1.tar
- name: /opt/file_compare2tar.gz
hash: d41d8cd98f00b204e9800998ecf8427e /opt/file_compare2.tar.gz
I need to create a playbook to check this list of files if the current hash is the same or if it was changed, the playbook should have a debug message like below:
---
- hosts: localhost
connection: local
vars_files:
- files.yml
tasks:
- name: Use md5 to calculate checksum
stat:
path: "{{ item.name }}"
checksum_algorithm: md5
register: hash_check
with_items:
- "{{ files }}"
- name: Debug files - Different
debug:
msg: |
"Hash changed: {{ item.name }}"
when:
- item.hash != hash_check
with_items:
- "{{ files }}"
- name: Debug files - Equal
debug:
msg: |
"Hash NOT changed: {{ item.name }}"
when:
- item.hash == hash_check
with_items:
- "{{ files }}"
- debug:
msg: |
- "{{ hash_check }} {{ item.name }}"
with_items:
- "{{ files }}"
For example, given the files
files:
- name: /scratch/file_compare1.tar
hash: 4f8805b4b64dcc575547ec1c63793aec /scratch/file_compare1.tar
- name: /scratch/file_compare2.tar.gz
hash: 2dc4f1e9ca4081cc49d25195627982ef /scratch/file_compare2.tar.gz
the tasks below
- name: Use md5 to calculate checksum
stat:
path: "{{ item.name }}"
checksum_algorithm: md5
register: hash_check
loop: "{{ files }}"
- name: Debug files - Different
debug:
msg: |
Hash NOT changed: {{ item.0.name }}
{{ item.0.hash.split()|first }}
{{ item.1 }}
with_together:
- "{{ files }}"
- "{{ hash_check.results|map(attribute='stat.checksum')|list }}"
when: item.0.hash.split()|first == item.1
give
msg: |-
Hash NOT changed: /scratch/file_compare1.tar
4f8805b4b64dcc575547ec1c63793aec
4f8805b4b64dcc575547ec1c63793aec
msg: |-
Hash NOT changed: /scratch/file_compare2.tar.gz
2dc4f1e9ca4081cc49d25195627982ef
2dc4f1e9ca4081cc49d25195627982ef
A more robust option would be to create a dictionary with the calculated hashes
- name: Use md5 to calculate checksum
stat:
path: "{{ item.name }}"
checksum_algorithm: md5
register: hash_check
loop: "{{ files }}"
- set_fact:
path_hash: "{{ dict(_path|zip(_hash)) }}"
vars:
_path: "{{ hash_check.results|map(attribute='stat.path')|list }}"
_hash: "{{ hash_check.results|map(attribute='stat.checksum')|list }}"
gives
path_hash:
/scratch/file_compare1.tar: 4f8805b4b64dcc575547ec1c63793aec
/scratch/file_compare2.tar.gz: 2dc4f1e9ca4081cc49d25195627982ef
Then use this dictionary to compare the hashes. For example, the task below gives the same results
- name: Debug files - Different
debug:
msg: |
Hash NOT changed: {{ item.name }}
{{ item.hash.split()|first }}
{{ path_hash[item.name] }}
loop: "{{ files }}"
when: item.hash.split()|first == path_hash[item.name]
The next option is to create a dictionary with the original hashes and both lists of original and calculated hashes
- name: Use md5 to calculate checksum
stat:
path: "{{ item.name }}"
checksum_algorithm: md5
register: hash_check
loop: "{{ files }}"
- set_fact:
hash_name: "{{ dict(_hash|zip(_name)) }}"
hash_orig: "{{ _hash }}"
hash_stat: "{{ hash_check.results|map(attribute='stat.checksum')|list }}"
vars:
_hash: "{{ files|map(attribute='hash')|map('split')|map('first')|list }}"
_name: "{{ files|map(attribute='name')|list }}"
gives
hash_name:
2dc4f1e9ca4081cc49d25195627982ef: /scratch/file_compare2.tar.gz
4f8805b4b64dcc575547ec1c63793aec: /scratch/file_compare1.tar
hash_orig:
- 4f8805b4b64dcc575547ec1c63793aec
- 2dc4f1e9ca4081cc49d25195627982ef
hash_stat:
- 4f8805b4b64dcc575547ec1c63793aec
- 2dc4f1e9ca4081cc49d25195627982ef
Then calculate the difference of the lists and use it to extract both lists of changed and unchanged files
- set_fact:
files_diff: "{{ _diff|map('extract', hash_name)|list }}"
files_orig: "{{ _orig|map('extract', hash_name)|list }}"
vars:
_diff: "{{ hash_orig|difference(hash_stat) }}"
_orig: "{{ hash_orig|difference(_diff) }}"
- name: Debug files changed
debug:
var: files_diff
- name: Debug files NOT changed
debug:
var: files_orig
gives
files_diff: []
files_orig:
- /scratch/file_compare1.tar
- /scratch/file_compare2.tar.gz
I used your suggestion to complement the playbook, it's working now.
The idea is to get a list of files, read each one and compare with both hash, file, and current hash.
---
- hosts: localhost
connection: local
gather_facts: false
vars_files:
- files3.yml
tasks:
- stat:
path: "{{ item.file }}"
checksum_algorithm: md5
loop: "{{ files }}"
register: stat_results
- name: NOT changed files
debug:
msg: "NOT changed: {{ item.stat.path }}"
when: item.stat.checksum == item.item.checksum.split()|first
loop: "{{ stat_results.results }}"
loop_control:
label: "{{ item.stat.path }}"
- name: Changed files
debug:
msg: "CHANGED: {{ item.stat.path }}"
when: item.stat.checksum != item.item.checksum.split()|first
loop: "{{ stat_results.results }}"
loop_control:
label: "{{ item.stat.path }}"
Result:
>> ansible-playbook playbooks/check-file3.yml
PLAY [localhost] ********************************************************************************************************************************************************************************************************************
TASK [stat] *************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item={'file': '/opt/file_compare1.tar', 'checksum': '9cd599a3523898e6a12e13ec787da50a /opt/file_compare1.tar'})
ok: [localhost] => (item={'file': '/opt/file_compare2.tar.gz', 'checksum': 'd41d8cd98f00b204e9800998ecf8427e /opt/file_compare2.tar.gz'})
TASK [NOT changed files] ************************************************************************************************************************************************************************************************************
skipping: [localhost] => (item=/opt/file_compare1.tar)
ok: [localhost] => (item=/opt/file_compare2.tar.gz) => {
"msg": "NOT changed: /opt/file_compare2.tar.gz"
}
TASK [Changed files] ****************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=/opt/file_compare1.tar) => {
"msg": "CHANGED: /opt/file_compare1.tar"
}
skipping: [localhost] => (item=/opt/file_compare2.tar.gz)
PLAY RECAP **************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ansible: How to filter dict2items and run playbook only for the matched values

I have a dict playbook which looks like this:
x_php_versions_installed:
ea-php71:
- ea-php71-php-bcmath
- ea-php71-php-xmlrpc
- ea-php71-php-zip
- pecl-memcached
- pecl-imagick
ea-php72:
- ea-php72-php-cli
- ea-php72-php-common
- ea-php72-php-curl
- pecl-imagick
I would like to filter them, to write me each item.value which contains 'ea' string but not everything else. My task looks like this:
- name: Write out only the ea packages
debug:
msg: '{{ item.value }}'
when: item.value | selectattr(item.value, 'contains', 'ea')
loop: '{{ x_php_versions_installed | dict2items }}
But it does not work, because it will list all of the packages, not only the ea ones. The expected answer should look like this:
...
"msg": [
"ea-php71-php-bcmath",
"ea-php71-php-xmlrpc",
"ea-php71-php-zip"
]
...
"msg": [
"ea-php72-php-cli",
"ea-php72-php-common",
"ea-php72-php-curl"
]
...
Another possibility is to filter out the 'pecl' string, it will gave me the same result and it also works fine.
Q: "Filter item.value which contains ea string."
A: The task below does the job
- debug:
msg: "{{ item.value|select('match','^ea-(.*)$')|list }}"
loop: "{{ x_php_versions_installed|dict2items }}"
gives (abridged)
msg:
- ea-php71-php-bcmath
- ea-php71-php-xmlrpc
- ea-php71-php-zip
msg:
- ea-php72-php-cli
- ea-php72-php-common
- ea-php72-php-curl
Note: The test match by default "succeeds if it finds the pattern at the beginning of the string". The task below gives the same result
- debug:
msg: "{{ item.value|select('match', 'ea-')|list }}"
loop: "{{ x_php_versions_installed|dict2items }}"
Q: "Filter out the pecl string."
A: Change the filter to reject and fit the regex. For example, the task below gives the same result
- debug:
msg: "{{ item.value|reject('match','^pecl-(.*)$')|list }}"
loop: "{{ x_php_versions_installed|dict2items }}"
Notes:
Select the lists without iteration. Declare the variables
x_php_versions_installed_keys: "{{ x_php_versions_installed.keys()|list }}"
x_php_versions_installed_ea_vals: "{{ x_php_versions_installed|dict2items|
map(attribute='value')|
map('select', 'match', 'ea-')|list }}"
x_php_versions_installed_ea: "{{ dict(x_php_versions_installed_keys|
zip(x_php_versions_installed_ea_vals)) }}"
gives
x_php_versions_installed_ea:
ea-php71:
- ea-php71-php-bcmath
- ea-php71-php-xmlrpc
- ea-php71-php-zip
ea-php72:
- ea-php72-php-cli
- ea-php72-php-common
- ea-php72-php-curl
Example of a complete playbook for testing
- hosts: localhost
vars:
x_php_versions_installed:
ea-php71:
- ea-php71-php-bcmath
- ea-php71-php-xmlrpc
- ea-php71-php-zip
- pecl-memcached
- pecl-imagick
ea-php72:
- ea-php72-php-cli
- ea-php72-php-common
- ea-php72-php-curl
- pecl-imagick
x_php_versions_installed_keys: "{{ x_php_versions_installed.keys()|list }}"
x_php_versions_installed_ea_vals: "{{ x_php_versions_installed|dict2items|
map(attribute='value')|
map('select', 'match', 'ea-')|list }}"
x_php_versions_installed_ea: "{{ dict(x_php_versions_installed_keys|
zip(x_php_versions_installed_ea_vals)) }}"
tasks:
- debug:
msg: "{{ item.value|select('match','^ea-(.*)$')|list }}"
loop: "{{ x_php_versions_installed|dict2items }}"
- debug:
msg: "{{ item.value|select('match', 'ea-')|list }}"
loop: "{{ x_php_versions_installed|dict2items }}"
- debug:
msg: "{{ item.value|reject('match','^pecl-(.*)$')|list }}"
loop: "{{ x_php_versions_installed|dict2items }}"
- debug:
msg: "{{ item.value|reject('match','pecl-')|list }}"
loop: "{{ x_php_versions_installed|dict2items }}"
- debug:
var: x_php_versions_installed_ea

Invert a dictionary with list values in ansible

How do I convert a dictionary like below
{
"host1": ["tag1", "tag2"],
"host2": ["tag1"]
}
to below in ansible:
{
"tag1": ["host1","host2"],
"tag2": ["host1"]
}
I have been trying to do this but got stuck due to the value being a list.
The playbook
- hosts: localhost
vars:
dict1:
host1: [tag1, tag2]
host2: [tag1]
tasks:
- set_fact:
dict2: "{{ dict2|default({})|combine({item: list_of_hosts}) }}"
loop: "{{ dict1.values()|flatten|unique }}"
vars:
list_of_hosts: "{{ dict1|
dict2items|
selectattr('value', 'contains', item)|
map(attribute='key')|
list }}"
- debug:
var: dict2
gives
dict2:
tag1: [host1, host2]
tag2: [host1]
Optionally, use json_query
vars:
list_of_hosts: "{{ dict1|dict2items|json_query(_query) }}"
_query: '[?value.contains(#, `{{ item }}`)].key'

VARIABLE IS NOT DEFINED when trying to register output in playbook

I'm trying to register a variable with the output to a query of a F5 pool and I'm getting this error:
"<type 'list'>": "VARIABLE IS NOT DEFINED!",
What is that I'm doing wrong?
Any help appreciated.
Thanks!
---
- name: GRAB F5 FACTS
hosts: f5
connection: local
gather_facts: no
tasks:
- name: Collect BIG-IP facts
bigip_device_facts:
gather_subset: ltm-pools
provider: "{{ prov }}"
register: bigip_device_facts
- name: FACTS OUTPUT
debug:
var: "{{ item.members | rejectattr('state', 'match', '^present$') | map(attribute='name') | list }}"
register: jkout
with_items: "{{ bigip_device_facts.ltm_pools }}"
when: item.full_path == "/Common/mypool"
- name: Set a variable
debug:
msg: "jkvar={{ jkout }}"
You are using the debug: module with the option var: and this expects a variable, not a jinja2 template.
So either change it to:
debug:
var: item.members
or
debug:
msg: "{{ item.members }}"
Like said by #dgw, the problem is with the var option of debug module.
https://docs.ansible.com/ansible/latest/modules/debug_module.html#parameters
This playbooks works:
- name: test rejectattr
hosts: localhost
gather_facts: no
vars:
members:
- { name: "one", state: "present" }
- { name: "two", state: "absent" }
- { name: "three", state: "present" }
tasks:
- name: FACTS OUTPUT
debug:
msg: "{{ members | rejectattr('state', 'match', '^present$') | map(attribute='name') | list }}"
Thanks for your responses. I'll investigate it further.
Apart from that, I think I've been able to solve it another way.
- name: FACTS OUTPUT
set_fact:
listado: "{{ item.members | rejectattr('state', 'match', '^present$') | map(attribute='name') | list }}"
with_items: "{{ bigip_device_facts.ltm_pools }}"
when: item.full_path == "/Common/mypool"
- debug: msg={{ listado }}
register: jkout
- name: Set a variable
debug:
msg: "jkvar={{ jkout }}"
Is that a right way to do it?
Thanks!!

Resources