falsePockets
falsePockets

Reputation: 4293

Include files in Jinja, applying template then filtering

Summary

I have a Jinja2 template which I'm running with Ansible.

I would like my template to load another file, as a template (i.e. evaluating {{ var }}), then I'll filter that, and then paste the result in to the top level template.

I think I'm almost there, I just need to find a Jinja2 filter which takes in a string and parses it as a template.

MWE

In this example lets assume the filter I want to apply is just to make the file uppercase. (Obviously this case is so simple I could do it in one template file. But my real use case is more complex.)

Top level template main.yaml.j2:

---
something:
   blah:
      x: {{ y }}
      {%- set names = [ 'John', 'Amy' ] %}
      z: >
        {{ lookup('file', './other-file.j2') | upper | indent(4*2) }}

other-file.j2:

{%- for name in names %}
Hello {{ name }}
{%- endfor %}

Running it with this Ansible playbook:

---
- hosts: localhost
  connection: local
  tasks:
  - name: generate template
    template:
      src: "main.yaml.j2"
      dest: "output.yaml.j2"
      trim_blocks: False
    register: templating
    vars:
      y: 5

Desired output

---
something:
   blah:
      x: 5
      z: >
        HELLO JOHN
        HELLO AMY

Actual Output

---
something:
   blah:
      x: 5
      z: >
        {%- FOR NAME IN NAMES %}
        HELLO {{ NAME }}
        {%- ENDFOR %}

Best Guess

I think I'm almost there. I just need a filter which applies a Jinja2 template to text.

i.e. something like:

{{ lookup('file', './other-file.j2') | template | upper | indent(4*2) }}

(But template is not a real filter. Maybe there's another name?)

What else I've tried

{{ include './other-file.j2' | upper | indent(4*2) }}

doesn't work.

fatal: [127.0.0.1]: FAILED! => {"changed": false, "msg": "AnsibleError: template error while templating string: expected token 'end of print statement', got 'string'. String: ---\nsomething:\n blah:\n x: {{ y }}\n {%- set names = [ 'John', 'Amy' ] %}\n z: >\n {{ include './other-file.j2' | upper | indent(4*2) }}"}

{% include './other-file.j2' | upper | indent(4*2) %}

"TemplateNotFound: ./OTHER-FILE.J2"

doesn't work.

Use Case

For context, my use case is that I have a Jinja2 template generating AWS CloudFormation templates. I'm trying to do it all in YAML, not JSON. (Because YAML can have comments, and you don't have to worry about whether the last item in a list has a trailing comma, and it's generally easier to read and write and debug.) Some CloudFormation resources need literal JSON pasted into the YAML file. (e.g. CloudWatch Dashboard bodies). So I want to have another file in YAML, which Jinja2 converts to json, and pastes into my overall YAML template. I want this dashboard to be generated with a for loop, and to pass in variables. I would like to have a separate

Upvotes: 1

Views: 4711

Answers (1)

Vladimir Botka
Vladimir Botka

Reputation: 68074

Instead of file plugin

lookup('file', './other-file.j2')

use template plugin

lookup('template', './other-file.j2')

Note that the scope of the variable {% set names = ['John', 'Amy'] %} is the template main.yaml.j2. If this variable is used in the template other-file.j2 the command lookup('template', './other-file.j2') will crash with the error:

"AnsibleUndefinedVariable: 'names' is undefined"

Solution

Declare the variable in the scope of the playbook. For example

  - template:
      src: "main.j2"
      dest: "output.txt"
    vars:
      names: ['John', 'Amy']

main.j2

{{ lookup('template', './other-file.j2') }}

other-file.j2

{% for name in names %}
Hello {{ name }}
{% endfor %}

give

shell> cat output.txt 
Hello John
Hello Amy

Upvotes: 2

Related Questions