Ansible: Include a file if it exists and do nothing if it doesn't - ansible

I have a Jinja2 template in Ansible and I need to include some files conditionally.
Basically, I have a for loop that includes "subparts" that might or might not be present. The following sample demonstrates the basic code that would work if all tools actually had a 'pbr.j2' file, but it crashes because Ansible cannot find some of the files:
{% for tool in tools %}
{% set template = 'tools/' + tool.name + '/pbr.j2' %}
{% include template %}
{% endfor %}
I have also tried the exists and is_file filters, but I always get "false" even when the file exists. Here is a sample code of what I have tried:
{% for tool in tools %}
{% set template = 'tools/' + tool.name + '/pbr.j2' %}
{% if template|exists %}
{% include template %}
{% endif %}
{% endfor %}
The result of this last sample is that no file gets included. If I replace exists with is_file, I get the same behavior.
What do I need to change to accomplish what I want?

I have found a solution to my problem: using ignore missing in the include directive.
In this case, I would use:
{% for tool in tools %}
{% set template = 'tools/' + tool.name + '/pbr.j2' %}
{% include template ignore missing %}
{% endfor %}
Ansible will ignore missing files and only the existing files from that loop will get included.

You are trying to use Ansible filters (exists, is_file) in a Jinja2 template. This works for templates processed by Ansible templating engine, but not for templates processed by the template module.
Jinja2 does not have capability to check the existence of a file, so you need to move your logic to Ansible.
For example: create a find task to search for directories in tools/, and provide an intersection of find-results list with tools list to Jinja2.

Related

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]:
[...]

Passing filename as variable to Jekyll include doesn't work

This works perfectly fine:
{% capture foo %}{% include test.html %}{% endcapture %}
I want to do this:
frontmatter.md:
---
baaz: test.html
layout: layout.html
---
layout.html:
{% capture foo %}{% include {{ page.baaz }} %}{% endcapture %}
But when I do I'm given this error:
"Liquid Exception: Invalid syntax for include tag. File contains invalid characters or sequences: Valid syntax: {% include file.ext param='value' param2='value' %}"
I've seen this addressed in several other questions, with the most recent explanation I've found being this:
"...dynamic filename paths can't be added due to the fact that the included files are calculated and added at the compilation phase and not at run time phase. And compilation phase means dynamic paths aren't yet recognized."
But that source is nearly two years old. Does anyone have a solution to this yet? Or a workaround that would allow me to include a file defined as a variable in frontmatter?
You can try {% include page.baaz %}
Edit : after some investigations, it appears that your syntax is correct, and that the error fires only when page.baaz is not present.
This ends up in an include tag which looks like this for liquid :
{% include %}
In order to avoid this error on certain pages/post with no baaz set, you can use a condition.
{% if page.baaz %}
{% capture foo %}{% include {{ page.baaz }} %}{% endcapture %}
{% endif %}
I just came to this case recently. I assume the syntax works as expected. See sample and result.
{% include {{ page.baaz }} %}
However in your case it might be the page name could not be put in a variable as the error stated:
Error: Invalid syntax for include tag:
File contains invalid characters or sequences
Valid syntax:
***% include file.ext param='value' param2='value' %***
So to come out from the problem I would suggest you to inventory all file names and choose it:
{% case page.baaz %}
{% when 'test.html' %}
{% capture foo %}{% include test.html %}{% endcapture %}
{% when 'othertest.html' %}
{% capture foo %}{% include othertest.html %}{% endcapture %}
{% else %}
This is not a test
{% endcase %}
I had a similar issue... I have found a very usable work-around. Allow me to share my experience and solution. I hope it helps you to find a suitable solution for your problem.
What I wanted to build
I wanted to make a page with multiple sections. The sections should be reusable, be able to contain includes and they should be easy to manage in the CloudCannon CMS.
What I came up with
I ended up using the following front matter:
---
title: Lorem ipsum
description: Lorem ipsum
image: /img/default.jpg
section_blocks:
- section: sectionwithinclude
- section: anothersection
- section: andyetanothersection
---
... and the following tempate:
{% for item in page.section_blocks %}
{% for section in site.sections %}
{% if item.section == section.slug %}
<div class="section {{ item.section }}">
{{ section.content }}
</div>
{% endif %}
{% endfor %}
{% endfor %}
Within the _sections folder/collection I have a file called sectionwithinclude.md that looks like this:
---
---
{% include mycustominclude.html %}
Why this is great
When you edit your page, CloudCannon will show the section_blocks as an array with reorder buttons. Additionally, CloudCannon will automagically recognize section as a collection and show the options in a dropdown. Therefore adding a section is a matter of adding an empty item to the array, selecting a section from the dropdown and reordering it with the array buttons. On the same time, the inline editing option of CloudCannon still works. So management of text can be WYSIWYG, while block management can be done in the front matter array.
Super easy and powerful for (you and) your editors.
PS. You might find out that you will have some 'scope' issues, because page no longer relates to the actual page, but to the section. To solve this you can/should alter the loop in the template. You can let the loop manage the include instead of the section.

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 }}

Shopify Liquid conditionally include sections

So the current Shopify implementation of sections leaves a lot to be desired. The majority of the functionality is relegated to the homepage.
I'm trying to skirt around that to a certain degree but basically chucking all the section functionality (that would normally be split into multiple sections) into one section file, and then duplicating it for each product in the store, reusing the handle of each product as the section name.
E.g.: example-product-handle --> sections/example-product-handle.liquid
My idea was then to create, in the main product.liquid file, a simple routing system that would conditionally include a section if one exists that matches with the handle. This SO answer got my creative juices flowing.
The ideal result would look like...
{% assign current_page = product.handle %}
{% capture snippet_exists %}{% section current_page %}{% endcapture %}
{% unless snippet_exists contains "Liquid error" %}
{% section current_page %}
{% endunless %}
This works beautifully for snippets. Replace section with include in that code, and the routing system performs perfectly.
With sections however?
Liquid syntax error: Error in tag 'section' - Valid syntax: section '[type]'
Is there no way around this? Do section names have to be explicitly stated?
This isn't possible. It is purposefully not possible. Try instead using the section to dynamically include snippets.
{% for block in section.blocks %}
{% case block.type %}
{% when 'layout1' %}
{% include 'layout1' %}
{% endfor %}

django-registration: Cannot translate email and subject

Any .html template for django-registration module works fine with {% blocktrans %} and {% trans %} template blocks. With {% load i18n %} in place, of course.
But I cannot make use of i18n tags in activation_email.txt and activation_email_subject.txt templates. Strings marked for translation just don't appear in .po file after makemessages.
Also, when wrapping a text with {% blocktrans %}{% endblocktrans %}, all variables such as {{ site.domain }} and {{ site.name }} are not processed.
Can you suggest what I am doing wrong?
That was my bad, I just improperly used makemessages. By default it processes only .html files.
In my case
django-admin.py makemessages -a -e html,txt
does all the work.
As for variables, {% blocktrans %}{% endblocktrans %} cannot process variables inside object, so we have to retrieve them before translation:
{% blocktrans with site.name as site_name and site.domain as site_domain %}
Good examples of templates for django-registration are given here.

Resources