Search code examples
ansibleciscopython-textfsm

Ansible regex_replace on values of specific keys in a list of dict


I've been struggling with this for a while. I have a list of dict like this which is created by parsing the Cisco CLI output with neighbors_raw.stdout[0] | ansible.netcommon.parse_cli_textfsm('templates/cisco_ios_show_cdp_neighbors.textfsm'):

[
        {
            "CAPABILITY": "R S I",
            "LOCAL_INTERFACE": "Ten 1/1/1",
            "NEIGHBOR": "switch1.mydomain.com",
            "NEIGHBOR_INTERFACE": "Ten 1/0/4",
            "PLATFORM": "C9500-16X"
        },
        {
            "CAPABILITY": "S I",
            "LOCAL_INTERFACE": "Gig 1/1/2",
            "NEIGHBOR": "switch2.mydomain.com",
            "NEIGHBOR_INTERFACE": "Ten 1/1/1",
            "PLATFORM": "C9200L-48"
        }
]

I want to convert the interface names from Ten 1/1/1 to Te1/1/1 or Gig 1/1/2 to Gi1/1/2.

I have tried the following:

- name: PARSE NEIGHBORS
  debug:
    msg: "{{ neighbors_raw.stdout[0] | ansible.netcommon.parse_cli_textfsm('templates/cisco_ios_show_cdp_neighbors.textfsm') |
      regex_replace('Ten ', 'Te') |
      regex_replace('Gig ', 'Gi') }}"

The output looks correct:

[
        {
            "CAPABILITY": "R S I",
            "LOCAL_INTERFACE": "Te1/1/1",
            "NEIGHBOR": "switch1.mydomain.com",
            "NEIGHBOR_INTERFACE": "Te1/0/4",
            "PLATFORM": "C9500-16X"
        },
        {
            "CAPABILITY": "S I",
            "LOCAL_INTERFACE": "Gi1/1/2",
            "NEIGHBOR": "switch2.mydomain.com",
            "NEIGHBOR_INTERFACE": "Te1/1/1",
            "PLATFORM": "C9200L-48"
        }
]

However, if I pipe this output into any other command it treats it like it is a character array:

- name: PARSE NEIGHBORS
  debug:
    msg: "{{ neighbors_raw.stdout[0] | ansible.netcommon.parse_cli_textfsm('templates/cisco_ios_show_cdp_neighbors.textfsm') |
      regex_replace('Ten ', 'Te') |
      regex_replace('Gig ', 'Gi') | list }}"

Output:

[
        "[",
        "{",
        "'",
        "N",
        "E",
        "I",
        "G",
        "H",
        "B",
        "O",
        "R",
        "'",
        ":",
        " ",
        "'",

The same weird output happens if I use zip instead of list.

I have also tried map('regex_replace', 'Ten (.*)', 'Te\\1') but it only replaces one occurrence and turns each dict in to a string:

- name: PARSE NEIGHBORS
  debug:
    msg: "{{ neighbors_raw.stdout[0] | ansible.netcommon.parse_cli_textfsm('templates/cisco_ios_show_cdp_neighbors.textfsm') |
      map('regex_replace', 'Ten (.*)', 'Te\\1') }}"

Output:

[
        "{'NEIGHBOR': 'switch1.mydomain.com', 'LOCAL_INTERFACE': 'Te1/1/1', 'CAPABILITY': 'R S I', 'PLATFORM': 'C9500-16X', 'NEIGHBOR_INTERFACE': 'Ten 1/0/4'}",
        "{'NEIGHBOR': 'switch2.mydomain.com', 'LOCAL_INTERFACE': 'Gig 1/1/2', 'CAPABILITY': 'S I', 'PLATFORM': 'C9200L-48', 'NEIGHBOR_INTERFACE': 'Te1/1/1'}"
]

I am also concerned about regex_replace on the entire dict treating it like a string. I really only want to apply the conversion to the specific value for LOCAL_INTERFACE and NEIGHBOR_INTERFACE.

How can I go about achieving this?


Solution

  • Given the data

      neighbors:
        - CAPABILITY: R S I
          LOCAL_INTERFACE: Ten 1/1/1
          NEIGHBOR: switch1.mydomain.com
          NEIGHBOR_INTERFACE: Ten 1/0/4
          PLATFORM: C9500-16X
        - CAPABILITY: S I
          LOCAL_INTERFACE: Gig 1/1/2
          NEIGHBOR: switch2.mydomain.com
          NEIGHBOR_INTERFACE: Ten 1/1/1
          PLATFORM: C9200L-48
    

    Create the lists of the updated attributes

      lifc: "{{ neighbors |
                map(attribute='LOCAL_INTERFACE') |
                map('regex_replace', 'Ten ', 'Te') |
                map('regex_replace', 'Gig ', 'Gi') |
                map('community.general.dict_kv', 'LOCAL_INTERFACE') }}"
      nifc: "{{ neighbors |
                map(attribute='NEIGHBOR_INTERFACE') |
                map('regex_replace', 'Ten ', 'Te') |
                map('regex_replace', 'Gig ', 'Gi') |
                map('community.general.dict_kv', 'NEIGHBOR_INTERFACE') }}"
    

    gives

      lifc:
      - LOCAL_INTERFACE: Te1/1/1
      - LOCAL_INTERFACE: Gi1/1/2
    
      nifc:
      - NEIGHBOR_INTERFACE: Te1/0/4
      - NEIGHBOR_INTERFACE: Te1/1/1
    

    zip the lists and combine items

      update: "{{ lifc | zip(nifc) | map('combine') }}"
    

    gives

      update:
      - LOCAL_INTERFACE: Te1/1/1
        NEIGHBOR_INTERFACE: Te1/0/4
      - LOCAL_INTERFACE: Gi1/1/2
        NEIGHBOR_INTERFACE: Te1/1/1
    

    zip the update and combine items

      result: "{{ neighbors | zip(update) | map('combine') }}"
    

    gives what you want

      result:
      - CAPABILITY: R S I
        LOCAL_INTERFACE: Te1/1/1
        NEIGHBOR: switch1.mydomain.com
        NEIGHBOR_INTERFACE: Te1/0/4
        PLATFORM: C9500-16X
      - CAPABILITY: S I
        LOCAL_INTERFACE: Gi1/1/2
        NEIGHBOR: switch2.mydomain.com
        NEIGHBOR_INTERFACE: Te1/1/1
        PLATFORM: C9200L-48
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        neighbors:
        - CAPABILITY: R S I
          LOCAL_INTERFACE: Ten 1/1/1
          NEIGHBOR: switch1.mydomain.com
          NEIGHBOR_INTERFACE: Ten 1/0/4
          PLATFORM: C9500-16X
        - CAPABILITY: S I
          LOCAL_INTERFACE: Gig 1/1/2
          NEIGHBOR: switch2.mydomain.com
          NEIGHBOR_INTERFACE: Ten 1/1/1
          PLATFORM: C9200L-48
    
        lifc: "{{ neighbors |
                  map(attribute='LOCAL_INTERFACE') |
                  map('regex_replace', 'Ten ', 'Te') |
                  map('regex_replace', 'Gig ', 'Gi') |
                  map('community.general.dict_kv', 'LOCAL_INTERFACE') }}"
        nifc: "{{ neighbors |
                  map(attribute='NEIGHBOR_INTERFACE') |
                  map('regex_replace', 'Ten ', 'Te') |
                  map('regex_replace', 'Gig ', 'Gi') |
                  map('community.general.dict_kv', 'NEIGHBOR_INTERFACE') }}"
    
        update: "{{ lifc | zip(nifc) | map('combine') }}"
        result: "{{ neighbors | zip(update) | map('combine') }}"
    
      tasks:
    
        - debug:
            var: lifc
        - debug:
            var: nifc
        - debug:
            var: update
        - debug:
            var: result