How can one generate a dict in Jinja/Ansible as a var? - ansible

I need to dynamically generate a dict and assign it to an Ansible var so that the same data is available to every host and does not need to be regenerated on each one. This will be looped over by a task and template. It is possible to build lists by nesting Jinja in a var definition like so:
var_list: >
[
{% set data = [] %}
{% for host in groups['all'] %}
{{ data.append(host) }}
{% endfor %}
{{ data }}
]
However, I am not able to do so with dicts:
var_dict: >
{
{% set data = dict() %}
{% for host in groups['all'] %}
{{ data | combine({host: []}) }}
{% endfor %}
{{ data }}
}
This results in a giant, garbled string: {u'host.subdom.dom.tld': []} {}...
What I am expecting is a var set to a true dict object that can have its components referenced individually (like {{ var_dict.host1[0] }}), with a structure similar to the following JSON:
{
"host1": [],
"host2": []
}
Is there a way to cause this to expand into a proper dict when assigned to the variable? Or is there perhaps another way to generate a dict from a loop without that awful set_fact hackery?

Either of your templates is broken (the first one produces nested lists, the second one prints data in loop with this expression {{ data | combine({host: []}) }}, the value of data remains empty until the end).
Jinja2 is a templating engine, you don't really need to create data structures to print them later. You can form your output directly.
For the list:
var_list: >
[ {% for host in groups['all'] %}'{{ host }}',{% endfor %} ]
For the dictionary:
var_dict: >
{ {% for host in groups['all'] %}'{{ host }}': [],{% endfor %} }
As you expect the values to be interpreted by Ansible, you don't need to pay attention to the trailing coma, otherwise there is loop.last.

Related

Access yaml key name with liquid

Is it possible to access the key names along with key values from a yaml file using liquid?
For example from a .yaml file that looks like this:
- type: a
id: b
author: c
website: d
Have liquid code that looks something like this:
{%- for item in YAMLFILE -%}
{{ item.??? }} = {{ item.??? }}
{%- endfor -%}
with the following output:
type = a
id = b
author = c
website = d
I am trying to do this this way because my yaml file has different key names along with values and it would be a pain having to embed a bunch of different if else statements to account for every possible key name.
Thank you all so much!
Assuming your yaml file is in _data/test, you can use:
{% for list in site.data.test %}
{% for item in list %}
{{ item[0] }} = {{ item[1] }}
{% endfor %}
{% endfor %}
item[0] is the key and item[1] is the value. It will work with different number of keys and names in each list.
Also be aware that four spaces in Markdown formats the content of the second for loop as a code block. Simply do not indent if putting this code in a Markdown file.

Cannot iterate through a dictionary, how to check if it`s empty?

I am trying to upgrade a server for a web application, I have to iterate through a key value dictionary, but I am given the following error
FAILED! => {"changed": false, "msg": "AnsibleUndefinedVariable: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'iteritems'"}
I tried
{
"version":"{{ version.actual_version_number }}",
"integrations": {
{% for id, port in integration_details.items() %}
{% if integration_details is defined and integration_details is not empty %}
{{ id }} : {{ port }}
{% endif %}
{%- if not loop.last -%},{% endif %}
{% endfor %}
}
}
If anyone could help with this problem would be really appreciated!
Assuming that your data structure is a dict use if integration_details is defined and integration_details.keys()|length > 0 this will check if there are any subkeys present. If its a list use if integration_details is defined and integration_details|length > 0
But seeing the error you posted you might have a for loop with iteritems()somewhere, which is not in the code you posted.
This may be because you use python3 but want to iterate your dict with iteritems() instead of items() or your variable is not a dict but a different type.

Using Jinja2 Expression within Jinja2 Statement

Let's say I have the following Jinja2 variables:
'dev_ami' = 'ami-123456'
'dev_located_ami' = 'ami-123456'
'prod_ami' = 'ami-654321'
'prod_located_ami' = 'ami-654321'
I would like to set a condition upon when the 'dev_ami' variable is equal to the 'dev_located_ami' variable. This would easily be done as shown in the following statement:
{% if dev_ami == dev_located_ami %}
... do some stuff
{% else %}
... do some other stuff
{% endif %}
But I would like to dynamically compare amis based on the deployment environment contained in a list ['dev','prod', etc...]. The following contains a templating error since there is an expression within a statement as such - {% {{ .. }} %}:
{% for env_type in ['dev','prod'] %}
{% if {{ env_type }}_ami == {{ env_type }}_located_ami %}
... do stuff
{% else %}
... do other stuff
{% endif %}
{% endfor %}
I have tried to set variables to represent the expressions I would like in the following code but unfortunately they are compiled literally as 'dev_ami' and 'dev_located_ami' whereas I would like them compiled to their corresponding variable values 'ami-123456' and 'ami-123456':
{% for env_type in ['dev','prod'] %}
{% set ami = "%s_ami"|format(env_type) %}
{% set located_ami = "%s_located_ami"|format(env_type) %}
{% if ami == located_ami %}
... do stuff
{% else %}
... do other stuff
{% endif %}
{% endfor %}
I have checked through various filters and so far have had no success. Would appreciate advice on getting this specific implementation to work properly. Thank you in advance.
I think you might be approaching the problem with the wrong datastructure in mind.
Dynamically generating variable names in order to compare amis in different environemts sounds like a massive overkill to me. Are you familiar with lists & dictionaries?
Try to start from something like this (pseudocode):
dict = { environments:
prod:
ami1: foo
ami1_located: foo
dev:
ami1: bar
ami1_located:baz
}
for env in dict[environments]:
if env[ami1] == env[ami1_located]:
[...]

Jinja2 Templating: Conditionally forming a set from Ansible Variables

I am trying to generate some config using Jinja2 templating and Ansible variables. The framework under which I am currently working does not allow me to perform the following operation in Ansible and thus I was hoping to achieve the same results in Jinja2.
My Ansible variables are as follows:
---
Top:
inner:
type: type1
other_random_variable:
- random: 1
inner2:
type: type2
inner3:
type: type1
The above structure works well when I am iterating over a loop and forming a configuration file as follows:
{% if Top is defined %}
{% for inner_vars in Top %}
# perform substitution here
{% endfor %}
{% endif %}
What I would like to do is to form a set of types such that I can generate another configuration for each unique type.
Is there any way for me to iterate through Top and add an item to a set?
I think I have a solution that could work:
{% set types = [] %}
{% if Top is defined%}
{% for inner_var in Top %}
{% if types.append(Top[inner_var].type) %}{% endif %}
{% endfor %}
{% endif %}
{{ types|unique }}

jinja2: substituting variables into a for loop statement

Let's say I have the following python dictionary:
d = { "name0": "Johnny",
"name1": "Stephanie",
"name2": "Jake",
"name3": "Nicole",
"name4": "Cody"
}
I'm passing this dictionary to a jinja2 template using flask. How can I iterate over these names in the template? Here's what I have so far:
{% for i in range(5) %}
{% for name in d.name{{ i }} %}
{{ name }}
{% endfor %}
{% endfor %}
I know this isn't the best way to do this (this example simplifies my data to the extreme). Best way would probably be to put all the names in a list called "names" and embed that within the dictionary like so: d = { "names": ["Johnny", Stephanie", etc.] }. I'm wondering if my specific question has a solution.
Iterate over the dictionary like you can in python:
{% for k, v in d.items() %}
{{ v }}
{% endfor %}

Resources