Ansible use Find Module on Registered Variable - ansible

My goal is to create a registered variable that I can use to search through and register other variables based on it. Mostly it is a find module that is searching for files which then, based upon the filename, should be registered in other variables. Here is an example of what I have but it is not filtering out the variables properly:
- name: Register all files
find:
paths: ./files/
patterns: '*.pkg,*.dmg'
file_type: file
register: installers
- name: Find Slack Installers
find:
paths: "{{ item }}"
patterns: "*slack*"
loop: "{{ installers.files|map(attribute='path')|map('basename')|list }}"
register: slack_installers
- debug:
msg: "{{ slack_installers }}"
This then outputs the following:
TASK [Find Slack Installers] *******************************************************************************************************************************
ok: [localhost] => (item=slack.19.5.dmg)
ok: [localhost] => (item=slack.19.6.pkg)
ok: [localhost] => (item=box.1.2.3.dmg)
(ignore the versions of things, these are just fake names/versions but I'm just testing the find functionality)
As you can see the "Find Slack Installers" task is just registering everything within the "installers" variable rather than actually finding only things with the slack pattern.
EDIT FOR extended question:
set_fact:
"{{ item.item|basename|lower }}_installers": "{{ installers.files|map(attribute='path')|map('basename')|list|select('search', '{{ item.item|basename|lower }}') }}"
loop: "{{ selected_installers.results }}"
loop_control:
label: "{{ item.item|basename }}"
when: item.user_input == 'y' and
item.item == ( item.item|basename )

Using the find module twice in a row does not make sense.
The find module is to find files in a folder and its subfolders, but not to filter a list (even if file names are in this list), as you tried here. Have a look at the examples for find in the Ansible docs.
However, you have two possibilities.
Using find with using the search word slack in pattern:
Add the search word slack directly as pattern in the find module: *slack*.pkg,*slack*.dmg.
- name: Register all files
find:
paths: ./files/
patterns: '*slack*.pkg,*slack*.dmg'
file_type: file
register: installers
- debug:
msg: "{{ installers.files|map(attribute='path')|map('basename')|list }}"
Result:
TASK [Register all files] **************
ok: [localhost]
TASK [debug] ***************************
ok: [localhost] => {
"msg": [
"slack.19.5.dmg",
"slack.19.6.pkg"
]
}
Using find to search only for the packages, then filtering your list:
Using select('regex', 'slack') you can filter the list by specific words in regex syntax, in your case specifying slack is enough.
- name: Register all files
find:
paths: ./files/
patterns: '*.pkg,*.dmg'
file_type: file
register: installers
- debug:
msg: "{{ installers.files|map(attribute='path')|map('basename')|list|select('regex', 'slack') }}"
Result:
TASK [Register all files] **************
ok: [localhost]
TASK [debug] ***************************
ok: [localhost] => {
"msg": [
"slack.19.5.dmg",
"slack.19.6.pkg"
]
}
EDIT: Extended Question
Nested jinja expressions are not allowed and not required.
If you want to specify a variable or a filter expression, just write it without using another jinja expression with {{ and }}.
Try the following select:
select('search', item.item|basename|lower )
Tip
If you use several operators and are not sure which operator binds stronger, you can also use parentheses like in mathematics.
The parenthesis should not be necessary at this point, but it does not interfere.
select('search', (item.item|basename|lower) )
But if you want to get e.g. the second element (index=1) of your list, then the term looks like this:
( installers.files|map(attribute='path')|map('basename')|list )[1]
At this point, the parentheses are important so that the inner expression is evaluated first, and then the corresponding element can be accessed.

Related

How to remove u' from ansible list

In my playbook, I am trying to get list of sub-directory names using the find module and then extracting the basename from path. I have been able to get the list but the elements are prepended with u'. How can I remove those from the output?
Ansible version 2.9
I tried to look at these SO posts here and here, but couldn't get it to work.
I may not have fully understood how they should be applied
This is part of my playbook:
- name: set item.path | basename
set_fact: dir_name_list2_basename="{{ item.path | basename}}"
with_items: "{{ zookeeper_data_dir.files}}"
register: item_path_basename_list
- debug:
msg: "{{item_path_basename_list.results}}"
- name: debug item.path | basename as list
debug:
var: item.ansible_facts.dir_name_list2_basename
with_items: "{{item_path_basename_list.results}}"
- debug: msg="item_path_basename_list.results {{ item_path_basename_list.results | map(attribute='ansible_facts.dir_name_list2_basename') | list }}"
- name: set fact to array
set_fact: basename_array="{{ item_path_basename_list.results | map(attribute='ansible_facts.dir_name_list2_basename') | list }}"
- debug:
msg: "basename_array &&&&&&&& {{basename_array}}"
And this is the output of the last debug:
ok: [zk3-dev] => {
"msg": "basename_array &&&&&&&& [u'version-2_backup', u'version-2']"
}
ok: [zk2-dev] => {
"msg": "basename_array &&&&&&&& [u'version-2_backup', u'version-2']"
}
ok: [zk1-dev] => {
"msg": "basename_array &&&&&&&& [u'version-2_backup', u'version-2']"
}
I would like the basename_array to show up as ["version-2_backup", "version-2"] without the u prefix
How should I change my set fact to array task, so I will get the desired result?
Since ["version-2_backup", "version-2"] is actually a JSON array, you could use the to_json filter.
This said, your long set of tasks looks like an overcomplicated process for a requirement that can be achieved with the right set of map filters, since map can apply the same filter to all the elements of a list, you can easily fit your basename in it.
So, given:
- debug:
msg: >-
basename_array &&&&&&&&
{{
zookeeper_data_dir.files
| map(attribute='path')
| map('basename')
| to_json
}}
This yields:
ok: [localhost] => {
"msg": "basename_array &&&&&&&& [\"version-2_backup\", \"version-2\"]"
}
Note that the double quotes are escaped because you are using the JSON stdout callback. But, if you change the callback to YAML, this would yield exactly what you expected:
ok: [localhost] =>
msg: basename_array &&&&&&&& ["version-2_backup", "version-2"]

SubString in Ansible and/or Jinja2

I'm trying to unmout all mountpoints, excepted if they are part of the current list:
excluded: ['home', 'cdrom', 'tmpfs', 'sys', 'run', 'dev', 'root']
Sample fstab only devices:
/dev/mapper/vgroot-local_home
devtmpfs
tmpfs
/dev/mapper/vgroot-local_home should be excluded from unmounting because the substring home is present on the array and the same for devtmpfs substring tmpfs. For tmpfs we have a perfect match. The goal is to check against devices.
After checking all Ansible filters and the Jinja2 documentation, I didn't find a solution to this problem. All Ansible facts are collected.
- name: Ensure the mountpoint is on the excluded list
ansible.posix.mount:
path: '{{ mount.device }}'
state: unmounted
when: {{ ??? }}
with_items: '{{ ??? }}'
become: true
tags: mountpoints
To test if a string contains a substring in Jinja, we use the in test, much like Python:
"somestring" in somevariable
In your case, you want to check if a given string contains any substring from the excluded list. Conceptually, what we want is something like the Python expression
if any(x in mount.device for x in excluded)
Using Jinja filters, we need to reverse our logic a little bit. We
can use the select filter to get a list of strings from the
excluded list that are contained in a given target string (such as
mount.device) like this:
excluded|select('in', item)
If item matches anything in the excluded list, the above
expression will result in a non-empty list (which evaluates to true
when used in a boolean context).
Used in a playbook, it would look like this:
- hosts: localhost
gather_facts: false
vars:
excluded: ['home', 'cdrom', 'tmpfs', 'sys', 'run', 'dev', 'root']
mounts:
- /dev/mapper/vgroot-local_home
- devtmpfs
- tmpfs
- something/else
tasks:
- debug:
msg: "unmount {{ item }}"
when: not excluded|select('in', item)
loop: "{{ mounts }}"
The above playbook produces as output:
TASK [debug] *******************************************************************
skipping: [localhost] => (item=/dev/mapper/vgroot-local_home)
skipping: [localhost] => (item=devtmpfs)
skipping: [localhost] => (item=tmpfs)
ok: [localhost] => (item=something/else) => {
"msg": "unmount something/else"
}
That is, it skips the task when the current loop item contains a
substring from the excluded list.
Assuming that your goal is "unmount all filesystems except those for
which the device name contains a substring from the excluded list",
you might write:
- name: Unmount filesystems that aren't excluded
ansible.posix.mount:
path: '{{ mount.device }}'
state: unmounted
when: not excluded|select('in', item.device)
loop: "{{ ansible_mounts }}"
become: true
tags: mountpoints
Iterate basename if you don't want to exclude the items of mounts because of matching the path, e.g. if you don't want to exclude /dev/mapper/vgroot-local_home because of dev in the excluded list
- debug:
msg: "Unmount {{ item }}"
loop: "{{ mounts|map('basename') }}"
when: not excluded|select('in', item)

Check if all elements from a list are present in another list with Ansible

I am looking for the easiest way to check if all elements from a list are present in another bigger list, in Ansible.
Example checking that ['pkg_mgr', 'python'] are both present in ansible_facts.
when: "{{ ['pkg_mgr', 'python'] | difference(ansible_facts.keys()) | length == 0 }}"
Q: "I am far from being pleased regarding how ugly it looks. I would be more than happy to see cleaner solutions."
A: An empty list evaluates to False in Ansible. It's not necessary to test the length of the list. Ansible condition when expands the expression by default. It's not necessary to close it in braces. The equivalent condition is
when: not ['pkg_mgr', 'python']|difference(ansible_facts.keys())
Python3 returns a dictionary view object instead of a list for methods dict.keys(), dict.values(), and dict.items(). Add list filter to make the code portable. See Dictionary Views.
when: not ['pkg_mgr', 'python']|difference(ansible_facts.keys()|list)
I was able to find a solution that works but I am far from being pleased regarding how ugly it looks.
- when: "{{ ['pkg_mgr', 'python'] | difference(ansible_facts.keys()) | length == 0 }}"
...
I would be more than happy to see cleaner solutions.
How about using is subset?
Test:
- name: "Check lists"
hosts: localhost
connection: local
tasks:
- debug:
msg: "{{ ['pkg_mgr', 'python'] is subset(ansible_facts.keys()) }}"
- debug:
msg: "{{ ['pkg_mgr', 'python', 'foo'] is subset(ansible_facts.keys()) }}"
Output:
PLAY [Check lists] *****************************************************************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": true
}
TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": false
}

Ansible - Iterate through array using with items

I do have a simple json file, where i need to pull a set of value from EACH array item, but during iteration it fails.
My playbook looks like:
code:
---
- name: direct - this works like charm
set_fact:
bb: "{{ pr_json.json.issues[0].fields.customfield_11756.value }}"
- debug:
var: bb
- name: via array - this is not working since iteration is not happening
set_fact:
dd_branch: "{{ pr_json.json.issues[{{ item }}].fields.customfield_11756.value }}"
register: mass
- debug:
var: mass
Getting output as:
TASK [jira_update : direct - this works like charm] ********************************************************************************************************************
task path: /home/test/ansible_jira/roles/jira_update/tasks/call.yml:3
ok: [localhost] => {
"ansible_facts": {
"bb": "R4.19"
},
"changed": false
}
TASK [jira_update : debug] *********************************************************************************************************************************************
task path: /home/test/ansible_jira/roles/jira_update/tasks/call.yml:7
ok: [localhost] => {
"bb": "R4.19"
}
TASK [jira_update : via array - this is not working since iteratoin is not happening] **********************************************************************************
task path: /home/test/ansible_jira/roles/jira_update/tasks/call.yml:10
fatal: [localhost]: FAILED! => {
"msg": "template error while templating string: expected token ':', got '}'. String: {{ pr_json.json.issues[{{ item }}].fields.customfield_11756.value }}"
}
Please do let us know how can I iterate through an array variable value on every sequence.
tried this too, but can somebody help to iterate the array values, please.
- name: Create PR request in TEMS JIRA
jira:
uri: "{{ tems_jira }}"
username: "{{ user }}"
password: "{{ pass }}"
operation: create
project: PR
issuetype: 'PR-Form'
summary: "{{ pr_json.json| json_query('issues[].fields.summary') }}"
description: "{{ pr_json.json | json_query('issues[].fields.description') }}"
args:
fields:
customfield_10303:
value: "{{ pr_json.json | json_query('issues[].fields.customfield_11756.value') }}"
Youy need to feed your list into a with_items iterator. Thats what sets the item variable for looping purposes.
- name: via array - this is not working since iteration is not happening
set_fact:
dd_branch: "{{ pr_json.json.issues[ item ].fields.customfield_11756.value }}"
register: mass
with_items:
- 0
- 1
That will iterate through all of the list items of pr_json.json.issues which will let you dive deeper into the variable structure like you are looking for. There are a lot of other factors that you can feed into the loop that might interest you that you can find detailed here.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html

Ansible check if variables are set

I want to automate the installation process of our software for our client. Therefore I wrote an Ansible playbook which has a task which should check if all the mandatory variables are set:
- name: Check environment variables.
hosts: all
vars_files:
- required_vars.yml
tasks:
- fail: msg="Variable '{{ item }}' is not defined"
when: item not in hostvars[inventory_hostname]
with_items:
- required_vars
The required_vars.yml looks like this:
required_vars:
- APPHOME: /home/foo/bar
- TMPDIR: /home/foo/bar/tmp
When I execute the playbook via ansible-playbook -i inventory/dev.yml playbook.yml I get the following error:
TASK [Gathering Facts] *************************************************************************************************************************************************************************************************************************ok: [localhost]
TASK [fail] ************************************************************************************************************************************************************************************************************************************failed:
[localhost] (item=required_vars) => {"changed": false, "failed": true, "item": "required_vars", "msg": "Variable 'required_vars' is not defined"}
It is obvious that I am doing something wrong, but I cannot point to the error. Can you help me please?
Edit: the accepted answer helped me out. Thank you.
But I have two more questions:
The executed task says:
TASK [fail]
skipping: [some_ip] => (item=/root)
skipping: [some_ip] => (item=TMPDIR: /home/foo/bar/tmp)
It is getting skipped because all variables are set, correct?
I think I figured out how to print the correct message, if the variable is not set:
- name: Check environment variables.
hosts: all
vars_files:
- required_vars.yml
tasks:
- fail:
msg: "Variable '{{ item }}' is not defined"
with_items: "{{ required_vars }}"
when: item is undefined
Correct? Or is there a better solution?
Two problems here:
You want to iterate over the value of required_vars variable value, so you need to provide it as an argument to with_items: "{{ required_vars }}":
with_items: "{{ required_vars }}"
Currently you are providing a list of a single element with a statically defined string required_vars.
You need to change the data type of the elements in your required_vars list to strings:
required_vars:
- "APPHOME: /home/foo/bar"
- "TMPDIR: /home/foo/bar/tmp"
Currently (because of : followed by space) you defined dictionaries, so for example in the first iteration item will have a value of { "APPHOME": "/home/foo/bar" }, which will then always fail on the when condition.
Bonus problem:
you defined a message in the form "Variable '{{ item }}' is not defined";
Ansible reports Variable 'required_vars' is not defined;
the above is not an error, as you think ("I get the following error"), but a correct result of the fail module with the message you defined yourself.
Since you have only one value for 'with_items' I think it should look like this:
with_items: "{{ required_vars }}"
On one line and with the brackets and quotation marks. Once you have more then one item, you can use the list like you did:
with_items:
- "{{ one }}"
- "{{ two }}"

Resources