Search code examples
python-3.xansibleyamlpyyamlruamel.yaml

Changing specific variable in YAML with Python


I have a YAML Variable file for use with Ansible. I'm trying to build a python script to change specific variables within the YAML file.

YAML File accsw01.yml looks something like this:

interface_vars:
- {interface: Ethernet1/1, description: Test_interface_1, access_vlan: 10}
- {interface: Ethernet1/2, description: Test_interface_2, access_vlan: 20}

I use a while loop to find the right interface var in the YAML file:

interface = input("Enter interface: ")
yaml_file_printout = pprint.PrettyPrinter(indent=2)
with open("/ansible/inventories/test/host_vars/accsw01.yml", "r") as yaml_file:
    yaml_read = yaml.load(yaml_file, Loader=yaml.FullLoader)
    interface_count = 0
    while True:
        output_interface = yaml_read["interface_vars"][interface_count]["interface"]
        if interface != output_interface:
            interface_count += 1
        elif interface == output_interface:
            yaml_file_printout.pprint(yaml_read["interface_vars"][interface_count])
            break

How can I get python to change say, the access_vlan on interface Ethernet1/2 while leaving the rest of the file intact?

I've tried:

Appending the file with PyYAML but that just sticks the block of new variables on top of the YAML file..

Changing the variables with ruamel.yaml, but although it changes the variables, it removes all formatting from the YAML file and does not display the variables as a dict


Solution

  • First of all you cannot normally append to a YAML file, you have to read the file into a data structure, change (append, remove) the data structure, and then dump it back.

    Second, you should not leave a file open unnecessarily while you go and do other things. After doing yaml.load() (whether in ruamel.yaml or in PyYAML) the data is read, so you should exit the with statement, by dedenting your code

    Your program doesn't change the data read into yaml_read, so I tried some explicit changes:

    import sys
    import pprint
    import ruamel.yaml
    
    yaml = ruamel.yaml.YAML()
    # yaml.indent(mapping=4, sequence=4, offset=2)
    yaml.preserve_quotes = True
    yaml.default_flow_style=None
    
    interface = 'Ethernet1/1'
    yaml_file_printout = pprint.PrettyPrinter(indent=2)
    with open("interface.yaml", "r") as yaml_file:
        data = yaml.load(yaml_file)  
    interface_count = 0
    while True:
        output_interface = data["interface_vars"][interface_count]["interface"]
        if interface != output_interface:
            interface_count += 1
        elif interface == output_interface:
            yaml_file_printout.pprint(data["interface_vars"][interface_count])
            break
    
    print('------')
    yaml.dump(data, sys.stdout)
    print('------')
    data['interface_vars'][1]['access_vlan'] = 30
    data['interface_vars'].append(dict(interface='Ethernet1/3', description='Test_interface_3', access_vlan='5'))
    yaml.dump(data, sys.stdout)
    

    which gives:

    { 'access_vlan': 10,
      'description': 'Test_interface_1',
      'interface': 'Ethernet1/1'}
    ------
    interface_vars:
    - {interface: Ethernet1/1, description: Test_interface_1, access_vlan: 10}
    - {interface: Ethernet1/2, description: Test_interface_2, access_vlan: 20}
    ------
    interface_vars:
    - {interface: Ethernet1/1, description: Test_interface_1, access_vlan: 10}
    - {interface: Ethernet1/2, description: Test_interface_2, access_vlan: 30}
    - {interface: Ethernet1/3, description: Test_interface_3, access_vlan: '5'}
    

    Instead of globally making leaf nodes flow-style (using yaml.default_flow_style = None) you can also wrap the dict that is appended in a ruamel.yaml.comments.CommentedMap() type for finer control over how the data is dumped (there are some details on how to do that here)

    Also note that the officially recommended extension for YAML files has been .yaml for over 13 years now.