How to render a literal 'null' in Jinja2 Template - ansible

I'm working on an ansible role that allow users to interact with a REST API. I create json.j2 templates that allow me to build the message payload and eventually submit. One of the fields expects either a string value ("") or null.
{
"value": "{{ example.value | default(null, true) }}"
}
This doesn't work and I get this error:
The task includes an option with an undefined variable. The error was: 'null' is undefined
I need that null value and I need to come in as the default value if no other value is provided.
How do I do this?

As pointed in the comments, null has no meaning in Python, None is the Python representation of what your are looking for.
Now, if you want to convert this to a JSON value, then there is a to_json filter in Ansible, so:
'{ "value": {{ example.value | default(None, true) | to_json }} }'
Would end up as:
{ "value": null }

Related

What value will be return that the "return another Undefined value" said in the Ansible document and when we use filter in Jinja2?

I was reading the ansible document and then it said:
Beginning in version 2.8, attempting to access an attribute of an
Undefined value in Jinja will return another Undefined value, rather
than throwing an error immediately. This means that you can now simply
use a default with a value in a nested data structure (in other words,
{{ foo.bar.baz | default('DEFAULT') }}) when you do not know if the
intermediate values are defined.
I can not understand it well. Is it said the expression "{{ foo.bar.baz | default('DEFAULT') }}" will be 'DEFAULT' when foo.bar.baz is not defined or it is said the expression "{{ foo.bar.baz }}" will be another value(mark it as VALUE) when foo.bar.baz is not defined and we need to defind another optional or seccond value like default('DEFAULT') in order to avoid making the expression return VALUE that we do not what it will be at all?
The url containing the statement is at: https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#omitting-parameters
I got the quotation by a little mouse wheel rotation.
The important part of the statement is in the "when you do not know if the intermediate values are defined".
Prior to 2.8, if you had:
{{ foo.bar.baz | default('DEFAULT') }}
with foo NOT having bar defined (ie. trying to access the attribute baz of the undefined variable bar), the error would be thrown by Jinja2, before reaching the | default(). With the changes referenced by the statement, trying to access baz when bar is not defined (the intermediate value), will not throw an error, but return another undefined, so that the | default() filter will intercept and be able to return DEFAULT.

Jinja2 - create a dynamic variable using value of variable as part of the key of another variable

I am writing a jinja2 template for an Ansible role. I have this in my role/x/vars/main.yml
switches:
- hostname: foo
customer: abc
abc_secret_key: myP#$$word
xyz_secret_key: myS3cr3t
In my template, I want to reference the secret key based on the value of customer variable.
I can reference abc_secret_key by using {{ item.abc_secret_key }}. That works, no problem. However I really want to build the variable name dynamically and use the value of the "customer" variable (in the case abc) as part of variable name abc_secret_key.
This does not work.
I get
"msg": "AnsibleUndefinedVariable: 'dict object' has no attribute u'abc'"
but, hopefully it illustrates what I am trying to do
my secret key is {{ item[item.customer]['_secret_key'] }}
I would like it to render like this:
my secret key is myP#$$word
I have tried about 10-15 different renditions but, can not pin down the right syntax.
As you see the dict key lookup with a literal string key, you can compose a dynamic string to serve as the dict key:
- debug:
msg: my secret key is {{ item[ item.customer ~ '_secret_key'] }}
Where the ~ is jinja2 syntax for string concatenation where either side is first coerced to a string, and then concatenated. You are welcome to use +, too, if you are certain that both sides are already strings (as is likely the case here, base on your cited example):
- debug:
msg: my secret key is {{ item[ item.customer + '_secret_key'] }}

Jinja2 templated file with json safe multiline include

I have a problem with a Jinja2 template I'm writing (called from Ansible).
The resultant file is a JSON file that I will send to an API (either with Ansible URI module or with curl). The template looks like this and basically works:
{
"description" : "my description",
"pipeline": "{% include 'root/pipeline.j2' %}"
}
The problem is that the content of root/pipeline.j2 is quite complex and includes multiple lines, quote characters and any number of other things that make the json file I'm creating invalid. What I want to do is parse the included file through a filter to convert it to a JSON valid string; something like this:
{
"description" : "my description",
"pipeline": "{% include 'root/pipeline.j2' | to_json %}"
}
But that doesn't work, probably because the filter is acting on the filename, not the included content.
Just for a little clarity when I create the template at the moment I see pipeline gets set to something like this:
"pipeline": "input {
"input1" {
<snipped>
"
It should appear thus:
"pipeline": "input {\n \"input1\" {<snipped>"
NB: I'm only giving the first couple of lines and I am using 'snipped' where I have remove the rest of the config.
Can anyone tell me how I can use an include within a jinja2 template that renders the result as a single line valid json string?
Thanks in advance for any assistance.
I finally managed to find a solution to my own question. Within the template that provides the JSON that is an API payload I am now setting a variable to the content of the pipeline template, which makes it easy to filter it with to_json:
{% set pipeline = lookup('template', 'root/pipeline.j2') %}
{
"description" : "my description",
"pipeline": {{ pipeline | to_json }}
}
I will leave this quesiton answer open for a while in case anyone can supply a better answer or explain why this is not a good one.
Thanks.

How do I check if variable defined in an Ansible playbook contains a string value?

I want to validate that the input values passed to the variables as extra_args.
I want to run a pre-task that passes if the variable contains a string value, else fails if it contains anything else.
The values are passed to them as extra_args when executing the playbook.
I want to run a pre-task that passes if the variable contains a string value, else fails if it contains anything else.
This task fails if the variable is not a string object:
- fail:
when: variable is not string
But be aware that all values passed as extra-vars will be strings, because that's what they are -- anything you type on your keyboard is a valid string. As there is no type declaration, even if a variable contains a numerical value, it will be stored in a string object.
It is different to variable values defined in YAML which undergo type autodetection performed by YAML parser. For example if you type myvar: true in YAML, it will be considered Boolean object true, but if you pass the same value with --extra-vars "myvar:true", it will be a string object true.
You need to specify another condition.
Here are few filters and tests in ansible you may find it useful
http://docs.ansible.com/ansible/latest/playbooks_filters.html
http://docs.ansible.com/ansible/latest/playbooks_tests.html
for validation you might use it as follows:
tasks:
- fail: msg="Variable '{{ item }}' is not a string"
when: string | search("^[a-zA-Z]*$")
I prefer to use the 'assert' module for such cases like this.
- name: Test if the type of 'variable' is string
assert:
that:
- variable is defined
- variable is string
fail_msg: |
variable: {{ variable | d() | to_nice_json }}
seealso: type check examples: https://github.com/ssato/ansible-role-assertive-programming-examples/blob/master/tasks/pre_type_checks.yml
BTW, if you want to define variables with types you want using --extra-vars (-e) option, you need to prepare yaml files define these variables and let them loaded using '#' such like '-e #/path/to/the/yaml_file'.

Access value through index number from ansible gather facts variable

I am using gather fact variable to get size information about host. for some server I am getting variable "ansible_devices": { "sda" and for few server getting "ansible_devices": { "cciss!c0d0".
Problem:- When I am using variable {{ ansible_devices.sda.size }} in my playbook and if sda key not found in ansible_device variable then obviously it gives me error
fatal: [xyz101] =>One or more undefined variables: dict object has no element sda
Getting value in ansible_device variable like below
"ansible_devices": {
"sda": {
"size": "68.33 GB",
........
}
},
"item": ""
or
"ansible_devices": {
"cciss!c0d0": {
"size": "68.33 GB",
........
}
},
"item": ""
Also I can access size here using {{ ansible_devices.sda.size }} in first case But unable to fetch value in {{ ansible_devices.cciss!c0d0.size }} in second case.
It might be the case special character in a json key that's why I am unable to fetch its value.
Is there any way to access this variable through key index {{ ansible_devices[0].size }} ?
or any other better way to access it.
You could use a conditional?
when: ansible_devices.sda exists
Or you can iterate through ansible_devices.keys() and with_items.
We can check key by using has_key in ansible playbook like below.
when: ansible_devices.has_key('sda')
Above check resolve my fetal error as I have added two task for these two keys. But still I am looking the solution where I can get these key value through index number. It will replace multiple condition into one.

Resources