I'm working on a playbook to modify a sql script, I need to create a string with a concatenation of conditionals.
this is the conditional structure:
(server.primary_name||'.'||server.arpa_domain like '%server_name%')
This is the ansible task:
- name: Query Conditionals
set_fact:
query_conditionals_list: "{{ query_conditionals_list + [ '(server.primary_name||.||server.arpa_domain like %' + item + '%)' ] }}"
with_items: "{{ server_names }}"
when: "item not in query_conditionals"
loop_control:
label: "{{item}}"
- name: Join names
set_fact:
query_conditionals: "{{query_conditionals_list | join (' OR ')}}"
I believe that I'm not escaping correctly the parentesis and the single qoutes.
Is it possible to escape them?
Thanks
EDIT
I made some modifications.
As suggested by mdaniel, instead of creating the string from the get go, I'm creating first a list and then doing a join with the OR. So that solves my OR problem.
Although, I was not able to escape the single quotes yet
I was able to find a workaround using vars on the ansible task.
While using vars, I was able to maintain the structure I wanted to use and save the single quotes.
- name: Query Conditionals
set_fact:
query_conditionals_list: "{{ query_conditionals_list + [ server_item ] }}"
when: "item not in query_conditionals_list"
loop_control:
label: "{{item}}"
vars:
server_item: "(server.primary_name||'.'||server.arpa_domain like '%{{item}}%')"
with_items: "{{ server_names }}"
Related
this
- name: ugly
hosts: localhost
vars:
badstr: "asdf{%jkl"
mydir: "."
mydict:
filea:
Value: "blue!42!"
fileb:
Value: "a{%isbad"
tasks:
- copy:
dest: "{{ item.key }}"
content: "{{ item.value.Value }}"
loop: "{{ mydict|default({})|dict2items }}"
gives me this error:
fatal: [localhost]: FAILED! => {"msg": "An unhandled exception occurred while templating 'asdf{%jkl'. Error was a <class 'ansible.errors.AnsibleError'>, original message: template error while templating string: Encountered unknown tag 'jkl'.. String: asdf{%jkl"}
The 'mydict' structure is returned from a plugin and I do not get to define the members. One of the 'Value's contains a "{%". Any reference to it will cause an error, whether as a variable, file content or in a template.
I have tried all kinds of quoting and combinations of unsafe, {{, %raw, etc. It either gives me the error or puts the name of the variable in the file.
How can I write the value to a file? Or just use it as a variable?
Ansible 2.8.4 on MacOS 11.3, also ansible 2.9 on RHEL 7.
You can use !unsafe for the variables expected to have these chars. Check this documentation. when !unsafe is used, the string/variable will never get templated.
- name: ugly
hosts: localhost
vars:
badstr: !unsafe "asdf{%jkl"
mydir: "."
mydict:
filea:
Value: !unsafe "blue!42!"
fileb:
Value: !unsafe "a{%isbad"
tasks:
- copy:
dest: "{{ item.key }}"
content: "{{ item.value.Value }}"
loop: "{{ mydict|default({})|dict2items }}"
When handling values returned by lookup plugins, Ansible uses a data
type called unsafe to block templating. Marking data as unsafe
prevents malicious users from abusing Jinja2 templates to execute
arbitrary code on target machines. The Ansible implementation ensures
that unsafe values are never templated. It is more comprehensive than
escaping Jinja2 with {% raw %} ... {% endraw %} tags.
You can use the same unsafe data type in variables you define, to
prevent templating errors and information disclosure. You can mark
values supplied by vars_prompts as unsafe. You can also use unsafe in
playbooks. The most common use cases include passwords that allow
special characters like { or %, and JSON arguments that look like
templates but should not be templated. For example:
---
mypassword: !unsafe 234%234{435lkj{{lkjsdf
The problem here is not in the copy task where the values are
evaluated; the problem is how they are being set. For example, if I
create a simple ansible module named example.sh that looks like
this:
#!/bin/sh
cat <<EOF
{
"files": {
"filea": {
"Value": "blue!42!"
},
"fileb": {
"Value": "a{%isbad"
}
}
}
EOF
I can write a playbook like this:
- name: ugly
hosts: localhost
tasks:
- example:
register: mydict
- copy:
dest: "{{ item.key }}"
content: "{{ item.value.Value }}"
loop: "{{ mydict.files|dict2items }}"
And this runs as expected, creating without any errors a file fileb with the content:
a{%isbad
Similarly, if I read the data from a JSON file and pass it through from_json, it also works fine:
- name: ugly
hosts: localhost
tasks:
- set_fact:
mydict: "{{ lookup('file', 'data.json')|from_json }}"
- copy:
dest: "{{ item.key }}"
content: "{{ item.value.Value }}"
loop: "{{ mydict.files|dict2items }}"
The problem only happens if you define the variables in a context in
which Ansible is looking for Jinja templating -- so, as the values of
variables in a playbook, a vars file, the arguments to set_fact,
etc.
You can potentially work around the problem by changing how you are
consuming these values.
Is it possible to include a JSON query in the actual task? All of the examples show using an additional var for the query.
Taking the example from Ansible Filters
- name: "Display all ports from cluster1"
debug:
var: item
loop: "{{ domain_definition | json_query(server_name_cluster1_query) }}"
vars:
server_name_cluster1_query: "domain.server[?cluster=='cluster1'].port"
Converted to:
- name: "Display all ports from cluster1"
debug:
var: item
loop: "{{ domain_definition | json_query(domain.server[?cluster=='cluster1'].port) }}"
as is it returns:
FAILED! => {"reason": "Syntax Error while loading YAML.\n found unknown escape character '?'
I have tried to add an escape backslash before the question mark, but it still fails with:
"template error while templating string: unexpected char '?'
It's possible to use back-ticks ` . For example
- name: "Display all ports from cluster1"
debug:
var: item
loop: "{{ domain_definition | json_query('domain.server[?cluster==`cluster1`].port') }}"
(not tested)
Ok it appears you can escape double quote the query and it will work!
- name: "Display all ports from cluster1"
debug:
var: item
loop: "{{ domain_definition | json_query(\"domain.server[?cluster=='cluster1'].port\") }}"
To step a little bit aside from escape hell, I like to take advantage of yaml scalar blocks
- name: "Display all ports from cluster1"
vars:
my_query: >-
domain.server[?cluster=='cluster1'].port
debug:
var: item
loop: "{{ domain_definition | json_query(my_query) }}"
You don't have to escape anything this way (works for quotes and backslash as well, nice for regexps)
I have this array of names and a main object I want to intersect the two, the common will be removed the and the rest will be retained.
VPC Names
["A_VPC", "B_VPC"]
ECS_OBJECTS
[{
"vpc_name": "A_VPC",
"client_name": "A"
},
{
"vpc_name": "B_VPC",
"client_name": "B"
},
{
"vpc_name": "C_VPC",
"client_name": "C"
}]
The end result I want will be that every common will be removed except C because they are not in common with the two objects.
[{
"vpc_name":"C_VPC",
"client_name": "C"
}]
My code thus far is some like this..
- name: Intersect ecs_instances objects to the existing VPC created
set_fact:
vpc_to_be_created: "{{ ecs_instances | difference(vpc_names) }}"
Is this the code that you're looking for ?
- set_fact:
vpc_to_be_created: "{{ vpc_to_be_created | combine(item) }}"
loop: "{{ ecs_objects }}"
when: item.vpc_name not in vpc_names
The cleanest and fastest way would be probably using JMESPath:
- name: Intersect ecs_instances objects to the existing VPC created
set_fact:
vpc_to_be_created: "{{ ecs_instances | json_query(query) }}"
vars:
query: "#[?!contains(`{{ vpc_names|to_json }}`, vpc_name)]"
Or build a differential list from scratch, as suggested in the other answer, but with fixed code:
- set_fact:
vpc_to_be_created: "{{ vpc_to_be_created | default([]) + [item] }}"
loop: "{{ ecs_instances }}"
when: item.vpc_name not in vpc_names
This has a drawback of being less efficient (loop), a somewhat messy output because of that, and a requirement for the set_fact task (you can use the above JMESPath query directly in other modules).
I need to be able to set variables using tasks in Ansible. I use set_fact for this, but cannot seem to access the fact I set with this. What is wrong with the code below:
- name: kludge1
set_fact: fake_y = "{{ [] }}"
- name: Loop
debug:
msg: "{{ item }}"
with_items: "{{ fake_y }}"
You have spaces before and after =...
- name: kludge1
set_fact: fake_y="{{ [] }}"
Avoid var= shortcut syntax. Use original YAML syntax instead, it gives less errors:
- name: kludge1
set_fact:
fake_y: "{{ [] }}"
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' }}"