Jinja templating of lists in Ansible vars files without 'max recursion depth reached' or blank items

80 Views Asked by At

I'm having the following problem templating an ansible vars file that's included in my role in the two following specific ways and I'm wondering if there's a correct way to do this, or if it's just something ansible can't do out of the box due to yaml parser limitations. Best thoughts are appreciated.

Please note the constraint that I'm working entirely with a vars include for this, I am well aware I could include tasks and build the logic that way, but that is sub-optimal for my design.

Issue #1: Dynamically setting content with jinja templating in a vars file.

In regard to strings, something like this works really well:

fqdn: "{% if condition %}
          .mydomain.io
        {% else %}
          .blah.net
        {% endif %}"

However, the issue comes into play when attempting this same logical flow with a list, such as:

network_dns: 
  - {% if condition %}8.8.8.8{% endif %}
  - {% if condition %}8.8.4.4{% endif %}

This DOES work, but it's messy and it leaves empty list items where the logic isn't met. Not entirely an isse because I tend to go overboard makign sure I trim my list inputs in my roles, but messy. Sadly, it's not always a this or that flow either, and I have some items that would set 3-4 values in a list, meaning that I need 3-4 like conditional lines since I can't set multiple list items in a single list item without creating a sub list. Really looking for what the best method is here.

Issue #2: list recursion 'max recursion depth reached' More ideally would be something like this:

network_dns: "{{ network_dns | d([]) + {% if condition %}['8.8.8.8','8.8.4.4']{% else %}[]{% endif %} }}"

or even (but slightly less ideal still) assign a temp var to condition and add multiple values would be better:

temp_vars_net_dns: "{% if condition %}['8.8.8.8','8.8.4.4']{% else %}[]{% endif %}"
network_dns: "{{ network_dns | d([]) + temp_vars_net_dns }}"

However, whenever I attempt to combine lists like this in a vars file, I always run into a 'max recursion depth reached' issue, because the parser doesn't fully unpack the list var before it appends items, so it recursively iterates through the appended items forever and ever and ever and ever and... well, you get the idea.

Again, not looking for the perfect infallible solution (unless you have it), just looking for something to make this a bit easier to manage in terms of vars organization.

2

There are 2 best solutions below

4
Alexander Pletnev On

This DOES work

It actually should not because it's not a valid YAML. This is a reason why it's recommended to always surround the Jinja templates with quotes.

but it's messy and it leaves empty list items where the logic isn't met

Easy - there's ternary filter designed specifically for this case:

vars:
  fqdn: "{{ condition | ternary('.mydomain.io', '.blah.net') }}"

The same can be applied to network_dns variable. I would recommend, however, to define the list of IPs separately to ease the things:

vars:
  network_dns_default:
    - 8.8.8.8
    - 8.8.4.4
  network_dns: "{{ condition | ternary(network_dns_default, []) }}"

So, no need for if-else - these usually go against Ansible principles of simplicity.

0
U880D On

Whereby I strongly recommend to accept either the already here given answer with ternary filter because it is a much less complex one, or investing in research into How to build your inventory, the following demonstrate how it would look like with Jinja2 Template and YAML.

For a Jinja2 Template called var.file.j2 with content of

network_dns: "{{ network_dns | d([]) + ['8.8.8.8','8.8.4.4'] if CONDITION else [] }}"

a minimal example playbook

---
- hosts: localhost
  become: false
  gather_facts: false

  vars:

    network_dns:
      - 1.1.1.1
      - 9.9.9.9
    CONDITION: True

  tasks:

  - template:
      src: var.file.j2
      dest: var.file

  - include_vars:
      file: var.file
      name: NET_CONFIG

  - debug:
      var: NET_CONFIG.network_dns

will result into an output file var.file with content of

network_dns: "[u'1.1.1.1', u'9.9.9.9', '8.8.8.8', '8.8.4.4']"

and an output of

TASK [debug] ************
ok: [localhost] =>
  NET_CONFIG.network_dns:
  - 1.1.1.1
  - 9.9.9.9
  - 8.8.8.8
  - 8.8.4.4

As one can see just from this small example, it is already very complex, error prone, not readable as it is not clear where becomes defined what and how, very bad to maintain, and so on ...

So don't do it that way even if technically possible and invest time into research regarding How to build your inventory.