Skip to content

Instantly share code, notes, and snippets.

@kolbashj
Created May 10, 2025 04:52
Show Gist options
  • Save kolbashj/86c89a90965c7eb60b66a37606383078 to your computer and use it in GitHub Desktop.
Save kolbashj/86c89a90965c7eb60b66a37606383078 to your computer and use it in GitHub Desktop.
FIXED: Improper Ansible BGP autonomous system format parsing = BAD --- ansible.netcomon
# 🚧 Debugging and Fixing AS-DOT Parsing in Ansible’s Network Engine
## Summary
While automating BGP configuration parsing with Ansible, I discovered that 32-bit ASNs in **AS-DOT format** (e.g. `65000.1000`) were being silently converted to **incorrect float values** like `65000.1`. This completely broke config pushes, idempotency, and most simply, Ansible facts rendered subsequent operations on BGP data inaccurate.
I commonly use the `cisco_ios` module, and after tracing it down the 'rabbit hole' and into Ansible’s shared `ansible.netcommon` Jinja2 core template logic, I submitted a patch that preserves string fidelity during Jinja2 templating and prevents unintended data loss.
---
## 🔍 The Problem
When collecting facts using `cisco.ios.ios_facts`, a config like this:
```
router bgp 65000.1000
```
…was being parsed as:
```yaml
ansible_network_resources:
- bgp_global:
as_number: "65000.1" # ❌ Wrong!
```
The root cause? A line of internal Ansible code was silently converting strings like `"65000.1000"` into floats via `ast.literal_eval()`:
```python
ast.literal_eval('65000.1000') # → 65000.1
```
---
## 🔬 The Hunt
I traced the data flow:
1. ✅ Regex parser captured `'65000.1000'` as a string.
2. ✅ It entered the Jinja2 template engine untouched.
3. ❌ But after rendering, Ansible called `ast.literal_eval()`, interpreting it as a float.
```
>>> import ast
>>> ast.literal_eval('65000.1000')
65000.1 # <- b00mShakaLaka!
```
That one line dropped the trailing precision, breaking automation and comparisons downstream.
---
## 🛠 The Fix
I submitted a patch to the **`ansible.netcommon`** collection’s "plugins/module_utils/network/common/utils.py" Template() method to detect AS-DOT patterns and skip the float conversion:
```python
if isinstance(rendered, str) and re.fullmatch(r"\d+\.\d{3,}", rendered):
return rendered # Preserve AS-DOT string
```
This short-circuits float evaluation while leaving all other types (e.g., lists, integers, bools) intact.
---
## 🧪 Test Coverage
To ensure accuracy, I added this unittest coverage:
```python
class TestNetworkTemplate(unittest.TestCase):
def setUp(self):
self.template = Template()
def test_asdot_string_preserved(self):
"""Ensure AS-DOT strings like '65000.1000' are not coerced to float."""
result = self.template("{{ as_number }}", {"as_number": "65000.1000"})
self.assertEqual(result, "65000.1000")
def test_normal_string_eval(self):
"""Ensure normal integer strings still evaluate correctly."""
result = self.template("{{ as_number }}", {"as_number": "65000"})
self.assertEqual(result, 65000)
def test_list_literal_eval(self):
"""Ensure literal_eval still works for lists."""
result = self.template("{{ value }}", {"value": "[1, 2, 3]"})
self.assertEqual(result, [1, 2, 3])
```
This test confirms AS-DOT values are not corrupted during fact parsing or config generation and protects subsequent values from being compromised.
I suppose that the following question exists:
- If you're able to compromise a system and execute Ansible, is there a
---
## 📬 The Contribution
- ✅ PR submitted to `ansible.netcommon`
- 🌍 Fix is vendor-neutral (impacts all Ansible network collections)
- 🧪 Includes automated test coverage
- 🔒 Fully backward-compatible
**<>**
---
## 💡 Takeaways
- Even established automation platforms can mishandle subtle formats like AS-DOT
- Implicit type casting (e.g., via `literal_eval`) is dangerous when precision matters
- Fixing the upstream root cause leads to cleaner, safer automation for everyone
- Contributing back ensures long-term maintainability across all users and vendors
---
## 🎉 Outcome
This patch ensures Ansible can correctly parse and round-trip 32-bit ASNs in AS-DOT format, preserving accuracy in fact collection and configuration generation across Cisco, Juniper, Arista, and other supported platforms.
No more broken idempotency. No more mangled ASN values.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment