Managing the Complexity of Jinja2 Templates in Ansible

One of the first roadblocks you’ll hit in your “let’s master Ansible” journey will be a weird error deep inside a Jinja2 template. Can we manage that complexity somehow… or as one of the participants in our Building Network Automation Solutions online course asked:

Is there any recommendation/best practices on Jinja templates size and/or complexity, when is it time to split single template into function portions, what do you guys do? And what is better in terms of where to put logic - into jinja or playbooks

One of my friends described the challenge as “Debugging Ansible is one of the most terrible experiences one can endure…” and debugging Jinja2 errors within Ansible playbooks is even worse, but there are still a few things you can do.

To start with, I would strongly recommend keeping Jinja2 templates (and Ansible playbooks) as simple as possible, and I found two ways to manage large Jinja2 templates:

  • Split them into smaller templates and include those, or use multiple templates and Ansible assemble module. That helps readability (and your sanity) but does nothing to abstract the complexity;
  • Extract common code into Jinja2 macros, potentially building a library of those macros that can be included into other templates.

Also, don’t debug Jinja2 templates in Ansible, because Ansible passes templates into Jinja2 engine as strings, resulting in error messages along the lines of “there’s something wrong with this template”. It’s much better to write a simple Python renderer that pushes YAML data through a Jinja2 file template. See also: debugging Jinja2 templates (part of our Ansible and Network Automation online courses)

Finally, anything reasonably complex deserves a custom Python plugin - you’ll find simple examples in our Ansible course and the corresponding Github repo. If you’re not fluent in Python start with macros, and when you know what needs to be done pass the specs to a decent Python programmer (“decent” just to make sure you don’t get spaghetti code back).

Mat Wood described exactly the dilemma described in this blog post in his Managed Configurations presentation. They decided to go for a custom Python filter instead of a Jinja2 macro every time they found something that had to be done several times in multiple places, or something that was too complex for Jinja2 (like my horrific write-only code that generated don’t-care subnet bits).

3 comments:

  1. Hi Ivan, I'm going through your Ansible lectures. One thing I'm not sure about and wanted to ask you: tasks: - template: src=/home/ht/NetOpsworkshop/Ansible/Includes/ios/common.j2 dest=/etc/ansible/configs/{{ inventory_hostname }}.txt

    Why can I not have the src & dest as a list of dictionaries? - src: /src_path/ - dest: /dest_path/

    What logic am I missing? Overall, I think your training sessions are beyond anything I have ever come across. So, a big thank you!

  2. When posting the comment above, the format was butchered...technically, the - src & - dest are properly formatted and the syntax passes yamllint. Where to use dict of dicts vs list of dicts. Or am I even asking the right question? I will stop obsessing and wait. Thanks.

  3. The template module takes a single template and creates a single output file. Why? Because it was designed that way ;)

    If you want to render a bunch of templates in a directory then you have to attach a loop to the template module, either looping through a predefined list of templates or using a file pattern iterator.

    As to "where to use lists or dictionaries", it's one of those "it depends" questions... in this case on what you want to have in your data model, and what's the best way to structure it. You might find this article useful: https://www.ipspace.net/kb/DataModels/

Add comment
Sidebar