Adding dynamic value into jinja2 expression - ansible

I have an ansible playbook that looks like this:
---
- hosts: localhost
vars:
filename: "me-0.0.1"
tasks:
- name: get filenames
find:
paths: /home/vagrant/test
patterns: 'me\-[\d]\.[\d]\.[\d]\.jar'
use_regex: yes
register: fn
- name: remove old files
file:
path: "{{ item }}"
state: absent
with_items:
"{{ (fn.files | sort(attribute='ctime')) | map(attribute='path') | reject('search', 'me-0.0.1') | list }}"
the object here is to get the value stored in the filename variable into the expression in with items replacing the hardcoded me-0.0.1 but I am not sure how to go about that.
So my question here is how do I substitute an ansible variable into this expression so that the filter is dynamic.

To answer my own question the answer is this:
{{ (fn.files | sort(attribute='ctime')) | map(attribute='path') | reject('search', (filename)) | list }}"
Meaning you drop the literal quotes and include the externally registered variable in brackets, I hope this helps others also.

Related

Filter list in Ansible for files ending with j2

I am trying to filter a list but have only limited success.
The list is hardcoded (all_files) and filtering starting with anchor '^' works just fine.
But what I really need is identify all *.j2 files
My Ansible version is 2.9.25
- name: Execute any command on localhost
hosts: localhost
gather_facts: false
tasks:
- set_fact:
all_files: [
"ansible_vars/dev-deploy-vars.yml",
"Precompiled/Application/config.js.j2",
"Precompiled/Application/web.config.j2"
]
- set_fact:
files_starting_with_pattern: "{{ j2_files | select('match', '^Precompiled') | list }}"
files_ending_with_pattern: "{{ j2_files | select('match', 'j2$') | list }}"
Any idea? All I need is a list of jinja2 files (which can be empty)
Thanks!
Your problem is that you're using match instead of search to look for a pattern at the end of the string. From the documentation:
match succeeds if it finds the pattern at the beginning of the string, while search succeeds if it finds the pattern anywhere within string. By default, regex works like search, but regex can be configured to perform other tests as well, by passing the match_type keyword argument. In particular, match_type determines the re method that gets used to perform the search. The full list can be found in the relevant Python documentation here.
So you want:
- name: Execute any command on localhost
hosts: localhost
gather_facts: false
vars:
all_files:
- ansible_vars/dev-deploy-vars.yml
- Precompiled/Application/config.js.j2
- Precompiled/Application/web.config.j2
tasks:
- set_fact:
files_starting_with_pattern: >-
{{ all_files | select("match", "^Precompiled") | list }}
files_ending_with_pattern: >-
{{ all_files | select("search", "j2$") | list }}
- debug:
var: files_starting_with_pattern
- debug:
var: files_ending_with_pattern
You could alternately use match if you modify your search pattern:
- set_fact:
files_starting_with_pattern: >-
{{ all_files | select("match", "^Precompiled") | list }}
files_ending_with_pattern: >-
{{ all_files | select("match", ".*j2$") | list }}

How to replace several characters in an Ansible variable's value?

I have a variable called joomlaversion which I get using json_query. The value of joomlaversion is 4.0.2 but I am trying to swap the dots with dashes so it becomes 4-0-2
How can I substitute dots for dashes in this ansible variable value?
I am using Ansible 2.9.6
Here is what I have tried.
---
- name: Download JSON content
uri:
url: https://api.github.com/repos/joomla/joomla-cms/releases
return_content: yes
register: jsoncontent
- name: Get latest version of Joomla from the tag using contains
set_fact:
joomlaversion: "{{ jsoncontent.json | to_json | from_json |json_query(jmesquery)|json_query(jmesquery2) }}"
vars:
jmesquery: "[? (draft==`false` && prerelease==`false`)]"
jmesquery2: "[?name.contains(#, 'Joomla! 4')].tag_name|[0]"
- name: Replace the dots with dashes in Joomla version
set_fact:
joomlaversion2: "{{ joomlaversion }} | replace('.', '-')"
#joomlaversion2: '{{ joomlaversion | regex_findall("\\."),("\\-") }}'
Rather than changing the dots to dashes it is appending | replace('.','-') on to the variable value, so it becomes "4.0.2 | replace ('.', '-')"
Perhaps I could use filters as is mentioned at https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#manipulating-strings
If I could split it using split(".") then join it again afterwards perhaps?
If I could split it using split(".") then join it again afterwards perhaps?
You could very well do that!
E.g.:
- set_fact:
joomlaversion2: "{{ joomlaversion.split('.') | join('-') }}"
Or, use regex_replace, which will find a pattern and replace it with hyphen.
# since . matches any char, we need to escape it with \
- set_fact:
joomlaversion2: "{{ joomlaversion | regex_replace('\.', '-') }}"
you have to add {{ }} and escape . with \:
- set_fact:
joomlaversion: "4.0.2"
- set_fact:
joomlaversion2: "{{ joomlaversion | regex_replace('\\.', '-') }}"
- debug:
var: joomlaversion2
result:
ok: [localhost] => {
"joomlaversion2": "4-0-2"
}

How to filter a dictionary using regex on value

I need to filter a dictionary in ansible based on regex matching it's values.
mydict:
some_value: /my/path
another_value: 2345
anotherpath: /my/other/path
What I tried is:
set_fact:
newvar: "{{ mydict | dict2items | selectattr('value', 'match', '^/my/.+$') | list }}"
But what i get is:
expected string or buffer
How can I filter all values based on part of their string for using them later in with_items?
The issue you currently have is that the regex match cannot handle integers. This will solve your issue, though it's slightly more verbose:
- set_fact:
file_list: []
- set_fact:
file_list: "{{ file_list + [item.value] }}"
when: item.value|string | match('^/my/.+$')
loop: "{{ mydict | dict2items }}"
If you can ensure that all values will always be strings, then you could use:
- set_fact:
newvar: "{{ mydict | dict2items | selectattr('value', 'match', '^/my/.+$') | map(attribute='value') | list }}"
Thanks #matt-p for clarifying :-) I didn't know this was actually caused by the match function itself. Is this more like an issue I should report to the Ansible community?
BTW I changed your code a little bit to fit the actual Ansible standard (the when condition is using deprecated syntax):
- set_fact:
file_list: []
- set_fact:
file_list: "{{ file_list + [item.value] }}"
when:
- item.value is string
- item.value is match('^/my/.+$')
with_items: "{{ mydict | dict2items }}"

Iterate over unique list created from string split

I have a list of domains:
---
domains:
- foo.bar
- baz.bar
I have tasks, where I need to iterate over these domains, extract domain tail, make an unique list of these tails and then create directories named by these tails.
Something like this but AFAIK jinja2 doesn't support list comprehension:
---
- name: Ensure all directories exist
file:
path: "/tmp/sandbox/{{ item }}"
state: directory
with_items: "[domain.split('.')[-1] for domain in domains] | unique"
Is it possible or do I need to create a custom jinja2 filter? Will this work?
---
- name: Ensure all directories exist
file:
path: "/tmp/sandbox/{{ item }}"
state: directory
with_items: "{{ domain_tails | my_custom_filter }}"
Thanks!
You can achieve this with map and regex_search:
- debug: msg="Ensure dir for {{ item }}"
with_items: "{{ domains | map('regex_search','\\w+$') | list | unique }}"
\w+$ match the last word (i.e. domain tail after dot).
Note that the slash is escaped, because it is inside double quotes.

Ansible - Use default if a variable is not defined

I'm customizing linux users creation inside my role. I need to let users of my role customize home_directory, group_name, name, password.
I was wondering if there's a more flexible way to cope with default values.
I know that the code below is possible:
- name: Create default
user:
name: "default_name"
when: my_variable is not defined
- name: Create custom
user:
name: "{{my_variable}}"
when: my_variable is defined
But as I mentioned, there's a lot of optional variables and this creates a lot of possibilities.
Is there something like the code above?
user:
name: "default_name", "{{my_variable}}"
The code should set name="default_name" when my_variable isn't defined.
I could set all variables on defaults/main.yml and create the user like that:
- name: Create user
user:
name: "{{my_variable}}"
But those variables are inside a really big hash and there are some hashes inside that hash that can't be a default.
You can use Jinja's default:
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"
Not totally related, but you can also check for both undefined AND empty (for e.g my_variable:) variable. (NOTE: only works with ansible version > 1.9, see: link)
- name: Create user
user:
name: "{{ ((my_variable == None) | ternary('default_value', my_variable)) \
if my_variable is defined else 'default_value' }}"
If anybody is looking for an option which handles nested variables, there are several such options in this github issue.
In short, you need to use "default" filter for every level of nested vars. For a variable "a.nested.var" it would look like:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ((a | default({})).nested | default({}) ).var | default('bar') }}"
or you could set default values of empty dicts for each level of vars, maybe using "combine" filter. Or use "json_query" filter. But the option I chose seems simpler to me if you have only one level of nesting.
In case you using lookup to set default read from environment you have also set the second parameter of default to true:
- set_facts:
ansible_ssh_user: "{{ lookup('env', 'SSH_USER') | default('foo', true) }}"
You can also concatenate multiple default definitions:
- set_facts:
ansible_ssh_user: "{{ some_var.split('-')[1] | default(lookup('env','USER'), true) | default('foo') }}"
If you are assigning default value for boolean fact then ensure that no quotes is used inside default().
- name: create bool default
set_fact:
name: "{{ my_bool | default(true) }}"
For other variables used the same method given in verified answer.
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"
If you have a single play that you want to loop over the items, define that list in group_vars/all or somewhere else that makes sense:
all_items:
- first
- second
- third
- fourth
Then your task can look like this:
- name: List items or default list
debug:
var: item
with_items: "{{ varlist | default(all_items) }}"
Pass in varlist as a JSON array:
ansible-playbook <playbook_name> --extra-vars='{"varlist": [first,third]}'
Prior to that, you might also want a task that checks that each item in varlist is also in all_items:
- name: Ensure passed variables are in all_items
fail:
msg: "{{ item }} not in all_items list"
when: item not in all_items
with_items: "{{ varlist | default(all_items) }}"
The question is quite old, but what about:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ( a | default({})).get('nested', {}).get('var','bar') }}"
It looks less cumbersome to me...
#Roman Kruglov mentioned json_query. It's perfect for nested queries.
An example of json_query sample playbook for existing and non-existing value:
- hosts: localhost
gather_facts: False
vars:
level1:
level2:
level3:
level4: "LEVEL4"
tasks:
- name: Print on existing level4
debug:
var: level1 | json_query('level2.level3.level4') # prints 'LEVEL4'
when: level1 | json_query('level2.level3.level4')
- name: Skip on inexistent level5
debug:
var: level1 | json_query('level2.level3.level4.level5') # skipped
when: level1 | json_query('level2.level3.level4.level5')
You can also use an if statement:
# Firewall manager: firewalld or ufw
firewall: "{{ 'firewalld' if ansible_os_family == 'RedHat' else 'ufw' }}"

Resources