Skip to content

Provide examples of nested Jinja2 transforms#46

Draft
ogenstad wants to merge 1 commit intomainfrom
pog-nested-transforms
Draft

Provide examples of nested Jinja2 transforms#46
ogenstad wants to merge 1 commit intomainfrom
pog-nested-transforms

Conversation

@ogenstad
Copy link
Contributor

@ogenstad ogenstad commented Jun 9, 2025

This PR provides an example of how you would be able to have nested transforms for Jinja where a main template includes information of a sub template but it's possible to run the same subtemplates in isolation.

Using the device_startup transform I've added two sub transforms below it. device_startup_bgp as well as device_startup_interfaces each of these new templates could be executed in isolation.

Some examples of commands that expect that the base models and example data from the Infrahub repo are loaded:

This command works exactly as before:

infrahubctl render device_startup device=atl1-edge1

The BGP template

In order to generate the BGP config we can run this sub template within isolation

❯ infrahubctl render device_startup_bgp device=atl1-edge1
router bgp 64496
   router-id
   neighbor IX_DEFAULT peer group
   neighbor IX_DEFAULT local-as 64496
   neighbor POP_GLOBAL peer group
   neighbor POP_GLOBAL local-as 64496
   neighbor POP_INTERNAL peer group
   neighbor POP_INTERNAL local-as 64496
   neighbor POP_INTERNAL remote-as 64496
   neighbor UPSTREAM_ARELION peer group
   neighbor UPSTREAM_ARELION local-as 64496
   neighbor UPSTREAM_ARELION remote-as 1299
   neighbor UPSTREAM_DEFAULT peer group
   neighbor UPSTREAM_DEFAULT local-as 64496
!

Alternatively with additional filters on the peer group:

❯ infrahubctl render device_startup_bgp device=atl1-edge1 bgp_group=POP_INTERNAL
router bgp 64496
   router-id
   neighbor POP_INTERNAL peer group
   neighbor POP_INTERNAL local-as 64496
   neighbor POP_INTERNAL remote-as 64496
!

The one for interfaces works in the same way:

❯ infrahubctl render device_startup_interfaces device=atl1-edge1

interface Ethernet1
   description Connected to atl1-edge2::Ethernet1
   mtu 1500
!
interface Ethernet10
   description role: spare
   mtu 1500
!
interface Ethernet11
   description role: server
   mtu 1500
!
interface Ethernet12
   description role: server
   mtu 1500
!
interface Ethernet2
   description Connected to atl1-edge2::Ethernet2
   mtu 1500
!
interface Ethernet3
   description Backbone: Connected to ord1-edge1 via DUFF-1543451
   mtu 9216
   ip address 10.1.0.20/31
   no switchport
   ip ospf network point-to-point
!
interface Ethernet4
   description Backbone: Connected to jfk1-edge1 via DUFF-6535773
   mtu 9216
   ip address 10.1.0.24/31
   no switchport
   ip ospf network point-to-point
!
interface Ethernet5
   description Connected to Arelion via DUFF-194a158e6da4
   mtu 1515
   ip address 203.111.0.1/29
   no switchport
!
interface Ethernet6
   description Connected to Colt Technology Services via DUFF-81ff87d03491
   mtu 1515
   ip address 203.111.0.9/29
   no switchport
!
interface Ethernet7
   description role: spare
   mtu 1500
!
interface Ethernet8
   description role: spare
   mtu 1500
!
interface Ethernet9
   description Connected to Equinix via DUFF-60776f11feeb
   mtu 1500
   ip address 203.111.0.17/29
   no switchport
!
interface port-channel1
   description role: server
   mtu 1500
!
!
interface Management0
!
interface Loopback0
!

Or when we provide a filter for the interface name:

❯ infrahubctl render device_startup_interfaces device=atl1-edge1 interface=Ethernet1

interface Ethernet1
   description Connected to atl1-edge2::Ethernet1
   mtu 1500
!
!
interface None
!
interface None
!

Note The loopback and management interfaces come back as None here (as they weren't identified). It would be trivial to fix this within the template but left it here to highlight the need to apply some additional logic when using this approach as there might be variations with regards to how these templates would behave depending on how they are executed.

The GraphQL query tied to the BGP config is a sub section of the regular query where as the query for the interfaces differs from the original one. It would still be required that they request the same information.

In all of these examples I've used a device as the key. It would be possible to run the interfaces query without specifying a device but it would also provide output for all devices on the system if that happened (also the variables to set loopback and management would be incorrect as each device would override the variable for the previous one.

Given that artifacts themselves would be tied to a single starting point (the member of a target group). I don't know if it's relevant at this stage to look at providing entrypoints outside of the device in this case, but there might be examples that I haven't thought about yet.

@ogenstad ogenstad marked this pull request as draft June 9, 2025 12:21
@@ -0,0 +1,38 @@
query device_startup_info ($device: String!, $bgp_group: String) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Require device but allow the filtering of a group to be optional

{% endfor %}
{% endif %}
{% endfor %}
{% set device_interfaces = data.InfraDevice.edges[0].node.interfaces.edges %}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we had a section that set the loopback and management interface at the top of the startup config template. There we'd iterate over data.InfraDevice.edges[0].node.interfaces.edges as we now have another template where we don't query for InfraDevice but instead for InfraInterface we need to manage these different scenarios. For this reason we set the device_interfaces variable within the main template and use a conditional statement in the imported device_startup_interfaces.j2 for the alternative.

{% endif %}

{% set ns = namespace(loopback_intf_name=none, loopback_ip=none, management_intf_name=none, management_ip=none) %}
{% for intf in device_interfaces %}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference when this file is moved is that we iterate over device_interfaces instead of data.InfraDevice.edges[0].node.interfaces.edges

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant