Search code examples
python-3.xautomationyamlpyyamlruamel.yaml

Python Script trying to generate a YAML file (Containerlab topology file) as output but getting the wrong format


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("--------------------------------------------")

Solution

  • 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.