Category: Ansible
… updated on Thursday, March 7, 2024 16:05 +0100
Multiline Expressions in Ansible Playbooks
Another week, another Ansible quirk 🤷♂️ Imagine you have a long Jinja2 expression, and you want to wrap it into multiple lines to improve readability. Using multiline YAML format seems to be the ideal choice:
---
- name: Test playbook
hosts: localhost
tasks:
- set_fact:
a: >
{{ 123 == 345 or
123 > 345 }}
It works every time 50% of the time (this time depending on your Ansible version).
Ansible Set Operations Do Not Preserve List Order
Here’s another Ansible quirk, this time caused by Python set behavior.
When I created the initial device configuration deployment playbook in netlab, I wanted to:
- Be able to specify a list of modules to provision.1
- Provision just the modules used in the topology and specified in the list of modules.
This allows you to use netlab initial
to deploy all configuration modules used in a lab topology or netlab initial -m ospf
to deploy just OSPF while surviving netlab initial -m foo
(which would do nothing).
Precedence of Ansible Extra Variables
I stay as far away from Ansible as possible these days and use it only as a workflow engine to generate device configurations from Jinja2 templates and push them to lab devices. Still, I manage to trigger unexpected behavior even in these simple scenarios.
Ansible has a complex system of variable (fact) precedence, which mostly makes sense considering the dozen places where a variable value might be specified (or overwritten). Ansible documentation also clearly states that the extra variables (specified on the command line with the -e
keyword) have the highest precedence.
Now consider these simple playbooks. In the first one, we’ll set a fact (variable) and then print it out:
Beware: Ansible Reorders List Values in Loops
TL&DR: Ansible might decide to reorder list values in a loop parameter, resulting in unexpected order of execution and (in my case) totally borked device configuration.
A bit of a background first: I’m using an Ansible playbook within netlab to deploy initial device configurations. Among other things, that playbook deploys configuration snippets for numerous configuration modules, and the order of deployment is absolutely crucial. For example, you cannot activate BGP neighbors in Labeled Unicast (BGP-LU) address family (mpls module) before configuring BGP neighbors (bgp module).
How Ansible Configuration Parsing Made Me Pull My Hair Out
Yesterday I wrote a frustrated tweet after wasting an hour trying to figure out why a combination of OSPF and IS-IS routing worked on Cisco IOS but not on Nexus OS. Having to wait for a minute (after Vagrant told me SSH on Nexus 9300v was ready) for NX-OS to “boot” its Ethernet module did’t improve my mood either, and the inconsistencies in NX-OS interface naming (Ethernet1/1 is uppercase while loopback0 and mgmt0 are lowercase) were just the cherry on top of the pile of ****. Anyway, here’s what I wrote:
Can’t tell you how much I hate Ansible’s lame attempts to do idempotent device configuration changes. Wasted an hour trying to figure out what’s wrong with my Nexus OS config… only to find out that “interface X” cannot appear twice in the configuration you want to push.
Not unexpectedly, I got a few (polite and diplomatic) replies from engineers who felt addressed by that tweet, and less enthusiastic response from the product manager (no surprise there), so it’s only fair to document exactly what made me so angry.
Update 2020-12-23: In the meantime, Ganesh Nalawade already implemented a fix that solves my problem. Thanks you, awesome job!
Updated: Getting Network Device Operational Data with Ansible
Recording the same content for the third time because software developers decided to write code before figuring out what needs to be done is disgusting… so it took me a long long while before I collected enough willpower to rewrite and retest all the examples and re-record the Getting Operational Data section of Ansible for Networking Engineers webinar.
The new videos explain how to consume data generated by show commands in JSON or XML format, and how to parse the traditional text-based show printouts. I dropped mentions of (semi)failed experiments like Ansible parse_cli and focused on things that work well: TextFSM, in particular with ntc-templates library, pyATS/Genie, and TTP. On the positive side, I liked the slick new cli_parse module… let’s hope it will stay that way for at least a few years.
On a totally unrelated topic, I realized (again) that fail fast, fail often sounds great in a VC pitch deck, and sucks when you have to deal with its results.
New Ansible for Networking Engineers Content
When restructuring our online courses we decided to make the video content that was previously part of Ansible online course available with Standard ipSpace.net Subscription.
If you haven’t enrolled into our automation online course (which always included the extra bits) you’ll find the following additional content in our Ansible for Networking Engineers webinar:
Growing Beyond Ansible host_vars and group_vars
One of the attendees of my Building Network Automation Solutions online course quickly realized a limitation of Ansible (by far the most popular network automation tool): it stores all the information in random text files. Here’s what he wrote:
I’ve been playing around with Ansible a lot, and I figure that keeping random YAML files lying around to store information about routers and switches is not very uh, scalable. What’s everyone’s favorite way to store all the things?
He’s definitely right (and we spent a whole session in the network automation course discussing that).
Building BGP Route Reflector Configuration with Ansible/Jinja2
One of our subscribers sent me this email when trying to use ideas from Ansible for Networking Engineers webinar to build BGP route reflector configuration:
I’m currently discovering Ansible/Jinja2 and trying to create BGP route reflector configuration from Jinja2 template using Ansible playbook. As part of group_vars YAML file, I wish to list all route reflector clients IP address. When I have 50+ neighbors, the YAML file gets quite unreadable and it’s hard to see data model anymore.
Whenever you hit a roadblock like this one, you should start with the bigger picture and maybe redefine the problem.
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.
Transforming XML Data With Ansible
Some network devices return structured data in either text- or XML format (but cannot spell JSON). Ansible prefers getting JSON-formatted data, and has a number of filters to process text printouts… but what could you do if you want to work with XML documents within Ansible? I described a few solutions in Transforming XML Data in Ansible.
AWS Rarely Kills a Service. What About Your Vendor?
Here’s an interesting tidbit from “Last Week in AWS” blog:
From a philosophical point of view, AWS fundamentally considers an API to be a promise. Services that aren’t promoted anymore are still available […] Think about that for a second - a service launched 13 years ago is still actively supported to the point where you can use it today.
Compare that to Killed By Google graveyard, and you might understand why I’m a bit reluctant to cover GCP in my webinars.
Update: Using pyATS in Network Automation
A month ago I described how Paddy Kelly used pyATS to get VRF data from a Cisco router to create per-VRF connectivity graphs.
Recently he also wrote a short article describing how to get started with pyATS and Ansible. Thank you, Paddy!
Executing a Jinja2 Loop for a Subset of Elements
Imagine you want to create a Jinja2 report that includes only a select subset of elements of a data structure… and want to have header, footer, and element count in that report.
Traditionally we’d solve that challenge with an extra variable, but as Jinja2 variables don’t survive loop termination, the code to do that in Jinja2 gets exceedingly convoluted.
Fortunately, Jinja2 provides a better way: using a conditional expression to select the elements you want to iterate over.
Optimizing Environment Setup in Ansible Playbooks
Have you ever seen an Ansible playbook where 90% of the code prepares the environment, and then all the work is done in a few template and assemble modules? Here’s an alternative way of getting that done. Is it better? You tell me ;)