Ansible - passing dynamic variables into Jinja2 template - ansible

I'm having a problem accessing dynamically named Ansible variables in a Jinja2 template. I have a list of tenants like this:
tenants:
- liamtest1
- liamtest2
In my playbook I create terraform configuration files for each of these tenants like this:
- name: Generate a .tf file for each tenant in list
template:
src: templates/tenant.tf.j2
dest: "{{ enviro }}/terraform/{{ item }}.tf"
with_items: "{{ hostvars[inventory_hostname][enviro]['tenants'] }}"
Later in the playbook I use the terraform module to apply my configuration and register the outputs to a variable:
- name: Run terraform
terraform:
project_path: "{{ enviro }}/terraform/"
state: present
register: tf_result
I've prefixed my terraform outputs with the tenant name so that I don't get duplicates. This bit is all working fine and I can I can display these outputs with a debug task, for example tenant_domain:
- debug:
var: tf_result.outputs.{{ item + '_domain' }}.value
with_items: "{{ hostvars[inventory_hostname][enviro]['tenants'] }}"
Produces this output:
ok: [localhost] => (item=liamtest1) => {
"ansible_loop_var": "item",
"item": "liamtest1",
"tf_result.outputs.liamtest1_domain.value": "liamtest1.mydomain.com"
}
ok: [localhost] => (item=liamtest2) => {
"ansible_loop_var": "item",
"item": "liamtest2",
"tf_result.outputs.liamtest2_domain.value": "liamtest2.mydomain.com"
}
The bit I can't seem to do is generate another set of files (this time javascript files for mongodb) from another Jinja2 template.
I've tried this:
- name: Generate a .js file for each tenant in list
vars:
domain: tf_result.outputs.{{ item + '_domain' }}.value
template:
src: templates/tenant.js.j2
dest: "{{ enviro }}/mongodb/{{ item }}.js"
with_items: "{{ hostvars[inventory_hostname][enviro]['tenants'] }}"
If I reference that in my Jinja2 template using {{ domain }} it ends up with just a string e.g. tf_result.outputs.liamtest1_domain.value in the first file and tf_result.outputs.liamtest2_domain.value in the second file.
I also tried using lookup in the Jinja2 template like this:
{{ lookup('vars', domain) }}
Which gives me:
"AnsibleUndefinedVariable: No variable found with this name: tf_result.outputs.liamtest1_domain.value"
I've also tried some other variations such as:
{{ lookup(hostvars[inventory_hostname], domain) }}
I've tried a few other things as well, I'm not sure they're all worth mentioning as none of them worked but for example I tried setting the variable inside the Jinja template instead of at the task level like this for example:
{% set domain = lookup('vars', 'tf_result.outputs.' + item + '_domain' %}

You simply have a syntax problem in your yaml.
# Wrong
vars:
domain: tf_result.outputs.{{ item + '_domain' }}.value
This is declaring a var which value is a concatenation of (literally) "tf_result.outputs." followed by the value of the current item and "_domain.value". What you want is the actual value contained in that full variable. This is the correct syntax:
# Correct
vars:
domain: "{{ tf_result.outputs[item + '_domain'].value }}"

Related

Ansible conditionals with nested loops

I've read the ansible docs on conditionals and loops. But it's still not clear to me how it exactly works.
my yaml structure looks like this:
---
myusers:
- username: user1
homedir: 'home1'
sshkey: 'ssh-rsa bla1'
- username: user2
homedir: 'home2'
sshkey: 'ssh-rsa bla2'
process:
- transfer:
transtype: 'curl'
traname: 'ftps://targetsystem'
my playbook part looks like this:
- name: test j2
debug:
msg: |-
dest: "/var/tmp/{{ item.0.username }}/{{ item.1.traname }} {{ item.1.transtype }}"
when: item.0.process is not none
loop: "{{ myusers | subelements('process')}}"
Now I only want to loop when the sub-element process exists. I had this working at one point but don't understand what I changed to break it.
Mainly I don't understand what the effect of the sequence of 'when' and 'loop' has. It appears to me when I run it that the condition 'when' is ignored. Also when I swap the sequence of when and loop.
The error I get when running the playbook is :
FAILED! => {"msg": "could not find 'process' key in iterated item {u'username': u'user1' ...
I've also tried with different conditions like:
item.0.process is defined
myusers.username.process is not none
etc...
By default, the subelements filter (and the corresponding lookup) requires each top level element to have the subelement key (and will error with the above message if it does not exist)
You can change this behavior by setting the skip_missing parameter (note: I also fixed the index to address the traname key which was the wrong one in your question example)
- name: test j2
debug:
msg: |-
dest: "/var/tmp/{{ item.0.username }}/{{ item.1.traname }} {{ item.1.transtype }}"
loop: "{{ myusers | subelements('process', skip_missing=true) }}"

how do I use an ansible string variable that contains jinja delimiters?

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.

Ansible: how to check if a variable is being loaded in a playbook?

I'm trying to write a playbook that will load vars from a group vars file then check if a variable exists
my playbook is like this:
---
- hosts: "{{ target }}"
roles:
- app
tasks:
- name: alert if variable does not exist
fail:
msg: "{{ item }} is not defined"
when: "{{ item }}" is not defined
with_items:
- country
- city
- street
...
My inventory file contains
[app]
ansible-slave1
ansible-slave2
[db]
ansible-db
[multi:children]
app
db
and I have the roles/app/vars/main.yml containing
country: "France"
city: "Paris"
What I was expecting is the playbook to output "street is not defined" but I have a syntax issue I can't resolve
[vagrant#ansible-master vagrant]$ ansible-playbook --inventory-file=ansible_master_hosts test_variables.yml --extra-vars "target=ansible-slave1" --syntax-check
ERROR! Syntax Error while loading YAML.
The error appears to have been in '/vagrant/test_variables.yml': line 10, column 24, but may be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
msg: "{{ item }} is not defined"
when: "{{ item }}" is not defined
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
I'd be happy with any hints.
Thanks
You have "" in invalid place of "when" statement. This should be like this:
msg: "{{ item }} is not defined"
when: "{{ item }} is not defined"
So the output will be:
failed: [hostname] (item=street) => {"changed": false, "item": "street", "msg": "street is not defined"}
there is on open issue conditional is defined fails to capture undefined var .
as a workaround I'd suggest to change the where condition to the following:
when: "{{ item }}" == ""

Iterating over vars **only** defined in inventory for a host

I would like to iterate over the vars defined in the inventory file of an ansible playbook. Indeed, in my inventory I define some vars to put in a .ini
file on the target host. I have something like this in my inventory file:
[myhost:vars]
VAR1=VALUE1
VAR2=VALUE2
I tried something like this:
- name: fill ini file with variables
ini_file:
path: "{{ myfile.ini }}"
section: vars
option: "{{ item.key }}"
value: "{{ item.value }}"
create: yes
with_dict: "{{ hostvars[inventory_hostname] }}"
myfile.ini should look like this at the end:
[vars]
VAR1=VALUE1
VAR2=VALUE2
The problem is: I properly have 'VAR1' and 'VAR2', but also all the Ansible variables for the host. I would like to iterate only on those defined in
the inventory (inventory_vars ? Does not exist).
Any help would be appreciated :)
There is no way to determine were a certain fact/variable was set, or filter them by their origin.
Instead, you can define a dictionary (JSON) in your inventory:
[myhost]
localhost
[myhost:vars]
my_dict={"VAR1": "VALUE1", "VAR2": "VALUE2"}
and use it in the iterator:
with_dict: "{{ my_dict }}"
Mind that in your example with_dict is wrongly indented.

Use Dict in Vars with Templates in Ansible

I'm trying to use templates with different sets of variables for each itteration of a determined set of tasks. For example, in one of the tasks I'd like to set specific values for postgres:
- name: Define values for postgres-ds
template:
src: postgres-ds.xml.j2
dest: /opt/ear_{{ instance_control.value }}/postgres-ds.xml
vars: "{{ postgres_desenv }}"
notify: Restart Service
In role/vars/main.yaml, I defined:
postgres_desenv:
var1: somevalue
var2: someothervalue
...
Still, I get the following error:
fatal: [rmt]: FAILED! => {
"failed": true,
"reason": "Vars in a Task must be specified as a dictionary, or a list of dictionaries
...
When I try to use the same variable in another context, it works fine:
- debug:
msg: "{{ item.key }} - {{ item.value }}"
with_dict: "{{ postgres_desenv }}"
I tried following the answers to this question but I'm still stuck.
My next step is to use a variable to call the variable inside vars, something like:
- name: Define values for postgres-ds
template:
src: postgres-ds.xml.j2
dest: /opt/ear_{{ instance_control.value }}/postgres-ds.xml
vars: postgres_{{ another_var }}
notify: Restart Service
You can do something like this:
- name: Define values for postgres-ds
template:
src: postgres-ds.xml.j2
dest: /opt/ear_{{ instance_control.value }}/postgres-ds.xml
vars:
settings: "{{ postgres_desenv }}"
notify: Restart Service
Then within the template you could refer to, e.g.,
{{ settings.var1 }}
In my case, following the answer above, all i had to do is using {{ item.value.(mydictkey) }} and that's it
In my case i defined a global variable like so:
vars:
vhosts:
web1
port: 8080
dir: /mywebsite
web2:
...
Then in the task I used:
- name: Render template
template:
src: "../templates/httpd.vhost.conf.j2" # Local template
dest: "/etc/httpd/conf.d/{{ item.key }}.conf" # Remote destination
owner: root
group: root
mode: 644
with_dict: "{{ vhosts }}"
In the template I used:
<VirtualHost *:{{ item.value.port }}>
DocumentRoot /var/www/{{ item.value.dir }}
</VirtualHost>
If postgres_desenv is defined in vars/main.yml that will be loaded automatically and be available to the role and rest of the playbook. Why do you have to specify that again using "vars" option in the template module task?

Resources