I have the following Ansible variable:
environment:
CONFIG: |
external_url 'https://www.example.com'
external_port '9090'
USERNAME: root
ROOT_PASSWORD: verysecret
Ansible template file:
environment:
{% for key, value in environment.items() %}
{{ key }}: {{ value }}
{% endfor %}
The output should be:
environment:
CONFIG: |
external_url 'https://www.example.com'
external_port '9090'
USERNAME: root
ROOT_PASSWORD: verysecret
But the output is:
environment:
CONFIG: external_url 'https://www.example.com'
external_port '9090'
USERNAME: root
ROOT_PASSWORD: verysecret
It seems the |
character is not literally used. It also breaks the indentation. Which filter should I use in my template?
Added my playbook and more since it was asked for:
template.j2:
---
- hosts: localhost
vars:
docker_compose_projects:
- name: project_1
services:
- service_name: some_service
image: some_image:latest
environment:
CONFIG: |
external_url 'https://www.example.com'
external_port '9090'
USERNAME: root
ROOT_PASSWORD: verysecret
tasks:
- name: template
ansible.builtin.template:
src: template.yml.j2
dest: "/home/username/{{ item.name }}"
loop: "{{ docker_compose_projects }}"
template.yml.j2:
services:
{% for service in item.services %}
{{ service.service_name }}
image: {{ service.image }}
{% if service.environment is defined %}
environment:
{% for key, value in service.environment.items() %}
{{ key }}: {{ value }}
{% endfor %}
{% endif %}
{% endfor %}
Output (project_1):
services:
some_service:
image: some_image:latest
environment:
CONFIG: external_url 'https://www.example.com'
external_port '9090'
USERNAME: root
ROOT_PASSWORD: verysecret
Expected output:
services:
some_service:
image: some_image:latest
environment:
CONFIG: |
external_url 'https://www.example.com'
external_port '9090'
USERNAME: root
ROOT_PASSWORD: verysecret
You need to put |
to the template itself to keep the multiline formatting, and render the lines of multiline strings in a nested loop:
{{ key }}: |
{% for line in value.splitlines() %}
{{ line }}
{% endfor %}
But since your variables could be both single-line and multiline, you will have to keep a default option, too:
{% for key, value in service.environment.items() %}
{% if '\n' in value %}
{{ key }}: |
{% for line in value.splitlines() %}
{{ line }}
{% endfor %}
{% else %}
{{ key }}: "{{ value }}"
{% endif %}
{% endfor %}
You will also have to manually control the whitespaces. Given that your template already has different indents for keys of the same level, it will be hard to achieve that individually for each block. To keep the template readable and editable, you can set #jinja2: lstrip_blocks:True, trim_blocks:True
globally, and maintain the desired indentation within the template directly (see the next chapter).
It probably could be optimized and beautified further, but I think it's enough to show the idea. Note that I also fixed some syntax problems of your original template such as missed :
after {{ service.service_name }}
and unquoted image name:
#jinja2: lstrip_blocks:True, trim_blocks:True
---
services:
{% for service in item.services %}
{{ service.service_name }}:
image: "{{ service.image }}"
{% if service.environment is defined %}
environment:
{% for key, value in service.environment.items() %}
{% if '\n' in value %}
{{ key }}: |
{% for line in value.splitlines() %}
{{ line }}
{% endfor %}
{% else %}
{{ key }}: "{{ value }}"
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
Let's take this simplified playbook as a minimal reproducible example (of course, the variables should reside in group or host vars, and the secrets should be encrypted or injected):
---
- hosts: localhost
gather_facts: false
vars:
docker_compose_projects:
- name: project_1
services:
- service_name: some_service_1
image: "some_image_1:latest"
environment:
CONFIG: |
external_url 'https://www.example.com'
external_port '9090'
USERNAME: root
ROOT_PASSWORD: verysecret1
- service_name: some_service_2
image: "some_image_2:latest"
environment:
USERNAME: root
ROOT_PASSWORD: verysecret2
- service_name: some_service_3
image: "some_image_3:latest"
tasks:
- name: Render the template
template:
src: template-trimmed.yml.j2
dest: "target_dir/{{ item.name }}.yml"
loop: "{{ docker_compose_projects }}"
The template from the previous chapter will generate this beatiful YAML file:
---
services:
some_service_1:
image: "some_image_1:latest"
environment:
CONFIG: |
external_url 'https://www.example.com'
external_port '9090'
USERNAME: "root"
ROOT_PASSWORD: "verysecret1"
some_service_2:
image: "some_image_2:latest"
environment:
USERNAME: "root"
ROOT_PASSWORD: "verysecret2"
some_service_3:
image: "some_image_3:latest"