I am currently working on a Python file that aims to generate a "custom" containerlab topolgy file - yaml format - but I am getting a different output than expected and I am struggling big time to fix it.
Here is an example of aimple Lab topology file Im trying to generate using the mentioned python script:
name: Test
topology:
nodes:
device1:
kind: device_type
image: image
device2:
kind: device_type
image: image
device3:
kind: device_type
image: image
links:
- endpoints: ["device1:eth1", "device2:eth1"]
- endpoints: ["device2:eth2", "device3:eth2"]
Instead, I am getting this:
- name: Test
- topology:
- nodes:
- device1:
kind: device_type
image: image
- device2:
kind: device_type
image: image
- device3:
kind: device_type
image: image
links:
- endpoints:
- device1:eth1
- device2:eth1
- endpoints:
- device2:eth2
- device3:eth2
This is the Python script I have so far:
import ruamel.yaml as yaml
# Create an ordered dictionary representing the lab name
lab_name = yaml.comments.CommentedMap()
lab_name['name'] = yaml.comments.CommentedSeq()
# Create an ordered dictionary representing the topology
topology_dict = yaml.comments.CommentedMap()
topology_dict['topology'] = yaml.comments.CommentedSeq()
topo_dict = yaml.comments.CommentedMap()
topo_dict['nodes'] = yaml.comments.CommentedSeq()
topo_dict['links'] = yaml.comments.CommentedSeq()
# Define the number of nodes
num_nodes = int(input("Enter the number of nodes: "))
# Define the LAB name
# Creating a Function that Verifies Lab valid name or not
def is_valid_lab_name(name):
if name is None:
return False
elif " " in name:
return False
elif not all(char.isalnum() or char == "_" for char in name):
return False
return True
# Prompt users for input until valid name is provided
while True:
lab_name_input = input("Enter the Lab Topology name (No Spaces, only '_' allowed): ")
if is_valid_lab_name(lab_name_input):
lab_name = {"name":str(f"{lab_name_input}")}
break
else:
print("Invalid name. Please try again.")
# Generate nodes and links
for i in range(1, num_nodes + 1):
node_name = str(f"ceos{i}")
node = {
node_name: {
"kind": "ceos",
"image": "device/image"
}
}
topo_dict['nodes'].append(node)
if i > 1:
link = {
"endpoints": str([f"{node_name}:eth1", f"ceos{i-1}:eth1"]),
}
topo_dict['links'].append(link)
# Integrating Nodes List Dictionary into Main Topology Dictionary
topology_dict = {'topology': [{'nodes': topo_dict['nodes']}]}
# Loop through LINKS dictionary and refactor links to CLAB format
for item in topo_dict['links']:
if 'endpoints' in item:
value = item['endpoints']
if isinstance(value, str):
# Remove single quotes and replace square brackets with lists
updated_value = eval(value.replace("'", '"'))
# Update the original dictionary with the modified value
item['endpoints'] = updated_value
# Integrating Nodes List Dictionary into Main Topology Dictionary
topology_dict['links'] = list(topo_dict['links'])
# Append both Dictionaries, Name and Topology into the same YAML file
lab_file = [lab_name, topology_dict]
# Create a YAML file
with open("dynamic_topology.yaml", "w") as yaml_file:
yaml.dump(lab_file, yaml_file, Dumper=yaml.RoundTripDumper, default_flow_style=False)
print("--------------------------------------------")
print("Dynamic topology file created successfully")
print("--------------------------------------------")
You're serializing a list, not a dictionary...so you're get a YAML document that represents a list:
lab_file = [lab_name, topology_dict]
If you want a YAML dictionary as output, you need to provide a dictionary to yaml.dump
. Something like:
topology_dict.update(lab_name)
with open("dynamic_topology.yaml", "w") as yaml_file:
yaml.dump(topology_dict, yaml_file, Dumper=yaml.RoundTripDumper, default_flow_style=False)
Also, note that this:
links:
- endpoints:
- device1:eth1
- device2:eth1
- endpoints:
- device2:eth2
- device3:eth2
And this:
links:
- endpoints: ["device1:eth1", "device2:eth1"]
- endpoints: ["device2:eth2", "device3:eth2"]
Are alternate representations of identical data structures.