Skip to content

Commit

Permalink
hack in ansible templars
Browse files Browse the repository at this point in the history
  • Loading branch information
grizz committed Jul 28, 2023
1 parent 6c3bfea commit c7e4b1d
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 15 deletions.
70 changes: 58 additions & 12 deletions src/netom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,43 @@
__version__ = get_distribution("netom").version


from ansible.parsing.dataloader import DataLoader
from ansible.template import Templar


class NetomTemplar(Templar):
"""A custom Templar class which includes additional jinja2 filters."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.environment.filters["address_to_mask"] = filters.address_to_mask

for name in filters.__all__:
self.environment.filters[name] = getattr(filters, name)


class TemplarEngine:
"""Engine for Ansible's Templar system for lazy vars."""

def __init__(self, *args, **kwargs):
# The DataLoader is used to load and parse YAML or JSON objects
# overload for search_path
self.dataloader = DataLoader()

def _render_str_to_str(self, instr, data):
# Instantiate Templar with the data loader and variable data
templar = NetomTemplar(loader=self.dataloader, variables=data)

# Use the templar object to render the template string
return templar.template(instr)


class Render:
"""
Renders data to defined type.
"""

def __init__(self, model_version, model_type, search_path=None):
def __init__(self, model_version, model_type, search_path=None, engine="jinja2"):
"""
Create a render object.
Expand All @@ -32,6 +63,7 @@ def __init__(self, model_version, model_type, search_path=None):

self.version = model_version
self.type = model_type
self._engine_type = engine

if not search_path:
# FIXME use pkg resources
Expand All @@ -41,18 +73,13 @@ def __init__(self, model_version, model_type, search_path=None):
self.set_search_path(search_path)

def set_search_path(self, search_path):
self.engine = tmpl.get_engine("jinja2")(search_path=search_path)
self.engine.engine.filters["make_variable_name"] = filters.make_variable_name
self.engine.engine.filters["ip_version"] = filters.ip_version
self.engine.engine.filters["address_to_mask"] = filters.address_to_mask
self.engine.engine.filters["address_to_wildcard"] = filters.address_to_wildcard
self.engine.engine.filters["line_to_mask"] = filters.line_to_mask
self.engine.engine.filters["line_to_wildcard"] = filters.line_to_wildcard
self.engine.engine.filters["ip_to_ipv4"] = filters.ip_to_ipv4
# self.engine.search_path = os.path.dirname(search_path)
if self._engine_type == "templar":
self.engine = TemplarEngine()
else:
self.engine = tmpl.get_engine(self._engine_type)(search_path=search_path)

for name in filters.__all__:
self.engine.engine.filters[name] = getattr(filters, name)
for name in filters.__all__:
self.engine.engine.filters[name] = getattr(filters, name)

def _render(self, filename, data, fobj):
# engine.engine.undefined = IgnoreUndefined
Expand All @@ -65,6 +92,9 @@ def render_string(self, filename, data):
self._render(filename, data, fobj)
return fobj.getvalue()

def render_from_string(self, instr, data):
return self.engine._render_str_to_str(instr, data)

def bgp_neighbors(self, data, fobj, validate=True):
"""
Renders BGP neighbors using `template/{{ typ }}/bgp/neighbors`.
Expand All @@ -83,6 +113,22 @@ def bgp_neighbors(self, data, fobj, validate=True):
return self._render(filename, groups, fobj)


class TemplarRender(Render):

def __init__(self, *args, **kwargs):
kwargs["engine"] = "templar"
super().__init__(*args, **kwargs)

def render_string(self, filename, data):
with open(os.path.join(filename), 'r') as file:
template_string = file.read()
return self.render_from_string(template_string, data)

def render_from_string(self, instr, data):
return self.engine._render_str_to_str(instr, data)



def validate(model, data, strict=True):
schema.apply_defaults(model, data)
success, errors, warnings = schema.validate(model, data, log=print)
Expand Down
9 changes: 7 additions & 2 deletions src/netom/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
r_ip_nn = re.compile("[\d.]{7,15}/\d{1,2}")

__all__ = [
"make_variable_name",
"render_template",
"address_to_mask",
"address_to_wildcard",
"ip_address",
"ip_interface",
"ip_to_ipv4",
"ip_version",
"line_to_mask",
"line_to_wildcard",
"make_variable_name",
"render_template",
]


Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
@pytest.fixture
def this_dir():
return os.path.dirname(__file__)

@pytest.fixture
def data_dir(this_dir):
return os.path.join(this_dir, "data")
1 change: 1 addition & 0 deletions tests/data/netom0/platform/hello_world.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ hello }} {{ world }}
1 change: 0 additions & 1 deletion tests/test_filters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

from netom.filters import address_to_mask, address_to_wildcard, line_to_mask, line_to_wildcard
import pytest


def test_address_to_mask():
Expand Down
92 changes: 92 additions & 0 deletions tests/test_render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import os

import netom

from ansible.parsing.dataloader import DataLoader
from ansible.template import Templar

from netom.filters import (
address_to_mask,
address_to_wildcard,
line_to_mask,
line_to_wildcard,
)

from jinja2 import Environment


j2env = Environment()
j2env.filters['address_to_mask'] = address_to_mask


class NetomTemplar(Templar):
'''A custom Templar class which includes additional jinja2 filters.'''

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.environment.filters["address_to_mask"] = address_to_mask


def ansible_render(template_string, variables={}):
# The DataLoader is used to load and parse YAML or JSON objects
dataloader = DataLoader()

# Instantiate Templar with the data loader and variable data
templar = NetomTemplar(loader=dataloader, variables=variables)

# Use the templar object to render the template string
rendered = templar.template(template_string)
return rendered


filter_data = dict(ip4="10.0.0.1/24")
vars = {
"hello": "Hello",
"world": "World",
"hello_world": "{{ hello }} {{ world }}",
"ugly_variable_name": "$so%ugly^^",
"ip4": "192.168.0.1/4",
"recursed_ip4": "{{ ip4 }}",
}


def test_init():
assert netom.Render("test", "12")


def test_render(data_dir):
expected = "Hello World\n"
print(f"this_dir {data_dir}")
ip4_mask = "192.168.0.1 240.0.0.0"

netomr = netom.Render("netom0", "platform", data_dir)
render_template = netomr.render_from_string

rendered = netomr.render_string("netom0/platform/hello_world.j2", vars)
assert rendered == expected
assert expected == render_template("{{ hello }} {{ world }}\n", vars)
assert ip4_mask == render_template("{{ ip4 | address_to_mask }}", vars)

render_template = ansible_render

assert expected == render_template("{{ hello }} {{ world }}\n", vars)
assert expected == render_template("{{ hello_world }}\n", vars)
assert ip4_mask == render_template("{{ ip4 | address_to_mask }}", vars)

netomr = netom.TemplarRender("netom0", "platform", data_dir)
render_template = netomr.render_from_string
assert expected == render_template("{{ hello }} {{ world }}\n", vars)
assert expected == render_template("{{ hello_world }}\n", vars)
assert ip4_mask == render_template("{{ ip4 | address_to_mask }}", vars)


def test_ansible():
render_template = ansible_render
template_string = "{{ hello }} {{ world }}\n"
recurvsive = "{{ hello_world }}\n"

rendered_string = render_template(template_string, vars)
rendered2 = render_template(recurvsive, vars)
print(rendered_string) # Output: Hello World
assert rendered_string == "Hello World\n"
assert rendered_string == rendered2

0 comments on commit c7e4b1d

Please sign in to comment.