1+ {#- ============================================================================
2+ Containerlab Topology Generator Template
3+
4+ Generates a Containerlab topology YAML file from Infrahub TopologyDeployment data.
5+ Supports Arista cEOS devices with management network configuration and
6+ automated link generation between devices.
7+ ============================================================================ -#}
8+ {#- Define default container images for each device kind -#}
19{% set default_images = {
210 "arista_ceos" : "registry.opsmill.io/external/ceos-image:4.29.0.2F"
311} -%}
12+
13+ {# Iterate through all topology deployments #}
414{% for topology in data .TopologyDeployment .edges -%}
515
616
717---
18+ {# ============================================================================
19+ Basic Topology Configuration
20+ ============================================================================ #}
821name: {{ topology.node.name.value }}
922prefix: ""
1023
24+ {# Management network configuration #}
1125mgmt:
1226 network: {{ topology.node.name.value | lower() }}
1327 ipv4-subnet: {{ topology.node.management_subnet.node.prefix.value }}
1428
1529{% if topology .node .devices .edges is defined -%}
1630
1731topology:
32+ {# ============================================================================
33+ Device Kind Definitions
34+ Configure base images and startup commands for each device type
35+ ============================================================================ #}
1836 kinds:
1937 arista_ceos:
2038 image: "{{ default_images.arista_ceos }}"
@@ -23,52 +41,73 @@ topology:
2341 - FastCli -p 15 -c 'security pki key generate rsa 4096 eAPI.key'
2442 - FastCli -p 15 -c 'security pki certificate generate self-signed eAPI.crt key eAPI.key generate rsa 4096 validity 30000 parameters common-name eAPI'
2543
44+ {# ============================================================================
45+ Node Definitions
46+ Define individual device nodes with their configuration
47+ ============================================================================ #}
2648 nodes:
27- {% - for device in topology .node .devices .edges %}
49+ {% for device in topology .node .devices .edges %}
50+ {#- Only include devices that are Arista cEOS platform #}
2851{% - if device .node .platform .node .containerlab_os .value == "arista_ceos" %}
2952 {{ device.node.name.value }}:
3053 kind: {{ device.node.platform.node.containerlab_os.value }}
3154 type: {{ device.node.device_type.node.name.value }}
55+ {#- Add custom OS version if specified #}
3256{% - if device .node .os_version .value %}
3357 image: {{ device.node.os_version.value }}
3458{% - endif %}
59+ {#- Extract and set management IP (strip subnet mask) #}
3560{% - if device .node .primary_address .node .address is defined %}
3661{% - set mgmt_ip = device .node .primary_address .node .address .value .split ('/' ) %}
3762 mgmt-ipv4: {{ mgmt_ip[0] }}
3863{% - endif %}
3964 startup-config: ../devices/{{ device.node.name.value }}.cfg
4065{% - endif %}
41- {% - endfor %}
66+ {% endfor %}
4267
4368
69+ {# ============================================================================
70+ Link Definitions
71+ Generate physical connections between devices from interface connectors.
72+ Deduplicates bidirectional links and normalizes interface naming.
73+ ============================================================================ #}
4474 links:
45- {% - set processed_endpoints = [] %}
75+ {% set processed_endpoints = [] %}
76+ {#- Build a mapping of device names to their platform kinds for interface name normalization -#}
4677{% - set device_kinds = {} %}
4778{% - for device in topology .node .devices .edges %}
4879{% - set _ = device_kinds .update ({device .node .name .value : device .node .platform .node .containerlab_os .value }) %}
4980{% - endfor %}
81+ {#- Iterate through all devices and their interfaces to find connections -#}
5082{% - for device in topology .node .devices .edges %}
5183{% - for interface in device .node .interfaces .edges %}
84+ {#- Only process interfaces with valid connectors (skip console/management) -#}
5285{% - if interface .node .connector is defined and interface .node .connector .node is not none and interface .node .role .value not in ["console" , "management" ] %}
5386{% - set connected_endpoint = interface .node .connector .node %}
87+ {#- Build endpoint identifiers (device:interface) -#}
5488{% - set endpoint 1 = device .node .name .value + ":" + interface .node .name .value %}
5589{% - set endpoint 2 = connected_endpoint .hfid | join (":" ) %}
90+ {#- Create sorted tuple to ensure consistent ordering for deduplication -#}
5691{% - set endpoint_tuple = [endpoint 1, endpoint 2] | sort %}
5792{% - set endpoint_key = endpoint_tuple | join ("|" ) %}
93+ {#- Only process each unique link once (prevents duplicates for bidirectional connections) -#}
5894{% - if endpoint_key not in processed_endpoints %}
5995{% - set _ = processed_endpoints .append (endpoint_key ) %}
96+ {#- Normalize interface naming: "Ethernet" -> "eth" for containerlab -#}
6097{% - set endpoint 1 = endpoint 1.replace ("Ethernet" , "eth" ) %}
6198{% - set endpoint 2 = endpoint 2.replace ("Ethernet" , "eth" ) %}
99+ {#- Extract device names from endpoints -#}
62100{% - set device 1_name = endpoint 1.split (":" )[0] %}
63101{% - set device 2_name = endpoint 2.split (":" )[0] %}
102+ {#- Arista cEOS: Strip subinterface notation (e.g., "eth1/1/1" -> "eth1/1") -#}
64103{% - if device_kinds .get (device 1_name ) == "arista_ceos" %}
65104{% - set endpoint 1 = endpoint 1.split ("/" )[0] %}
66105{% - endif %}
67106{% - if device_kinds .get (device 2_name ) == "arista_ceos" %}
68107{% - set endpoint 2 = endpoint 2.split ("/" )[0] %}
69108{% - endif %}
70109 - endpoints: ["{{ endpoint1 }}", "{{ endpoint2 }}"]
71- {% - endif %}
110+ {% endif %}
72111{% - endif %}
73112{% - endfor %}
74113{% - endfor %}
0 commit comments