Extra spaces appearing in Ansible templates

JerryS picture JerryS · Mar 20, 2017 · Viewed 13.1k times · Source

I am generating config files and I want them to be indented just so. I started with a Jinja2 template that rendered correctly when called from a simple python program. When I call it from ansible, I will get 2 extra spaces on all but the first line of the loop. Generating things like YAML and python has been a real pain. I have taken to putting a comment line as the first line of a for block to fix this...

Here is a really simple example of a YAML generator:

playbook call:

  - name: generate bgp vars file, put in includes directory
    local_action: template src={{ role_dir }}/templates/bgp_vars.j2 dest={{ incvar_dir }}/bgp_vars.yaml
    run_once: true

section of template:

dc_route_reflectors:
{% for dc in SH_dcs %}
# dc is "{{ dc }}"
  {{ dc }}:
  {% for host in groups[bgpgroupname] if dc == hostvars[host].MYDC %}
    - "{{ hostvars[host].MAIN_MYADDR }}"
  {% endfor %}
{% endfor %}

rendered output:

dc_route_reflectors:

# dc is "pnp"
  pnp:
      - "10.100.16.3"
      - "10.100.32.3"
  # dc is "sgs"
  sgs:
      - "10.8.0.3"
      - "10.8.16.3"
  # dc is "cst"
  cst:
      - "10.4.0.3"
      - "10.4.16.3"
  # dc is "dse"
  dse:
      - "10.200.0.3"
      - "10.200.16.3"

Notice how the dc is "pnp" comment is not indented as it is shown in the template, but sgs,cst and dse comments are indented by 2 spaces. All of the array lines of ip addresses are also indented. I have tried various versions of adding "-" to the "%" things as Jinja2 describes, but none have given consistent correct results.

Others must have seen this before. I'm running 2.2.1.0 on CentOS7.

Answer

techraf picture techraf · Mar 20, 2017

For a start, you can just remove the spaces you explicitly added in front of your statements and keep indentation only for the data:

dc_route_reflectors:
{% for dc in SH_dcs %}
# dc is "{{ dc }}"
  {{ dc }}:
{% for host in groups[bgpgroupname] if dc == hostvars[host].MYDC %}
    - "{{ hostvars[host].MAIN_MYADDR }}"
{% endfor %}
{% endfor %}

If you want to keep indentation of the statements, you can set lstrip_blocks option to True (notice: the declaration must be in the first line of the template):

#jinja2:lstrip_blocks: True
dc_route_reflectors:
{% for dc in SH_dcs %}
# dc is "{{ dc }}"
  {{ dc }}:
  {% for host in groups[bgpgroupname] if dc == hostvars[host].MYDC %}
    - "{{ hostvars[host].MAIN_MYADDR }}"
  {% endfor %}
{% endfor %}

Read more about whitespace control in Jinja2.


Ansible runs Jinja2 with trim_blocks enabled and lstrip_blocks disabled.

All the spaces you typed into the template (outside of the statements and expressions) are thus considered a part of the output. No "extra spaces" are added.

Notice how the dc is "pnp" comment is not indented as it is shown in the template, but sgs, cst and dse comments are indented by 2 spaces.

These two spaces are included in your template in the 7th line (before {% endfor %}).

All of the array lines of ip addresses are also indented.

These spaces are defined in your template in the 5th line (in front of the {% for host).