'Convert ansible response to yaml format

Im trying to convert a response from a device to yaml format. I tried using with_together, seems like its the closest I can get to the desired output. I also tried with Jinja2 template, though I am having trouble with parsing the data from the response into Jinja2. Seems like Im missing another loop to iterate over the prefixes.

Tasks:

- name: Gather Junos prefix-lists
  junipernetworks.junos.junos_prefix_lists:
    state: gathered
  register: prefix_response

- name: parse info
  set_fact:
    prefix_name: "{{ prefix_response | to_json | from_json | json_query(prefix_name_query) }}"
    prefix: "{{ prefix_response | to_json | from_json | json_query(prefix_query) }}"
  vars:
    prefix_name_query: 'gathered[].name'
    prefix_query: 'gathered[]."address_prefixes"'

- name: Write to file
  ansible.builtin.lineinfile:
    path: "output.yaml"
    state: present
    line: |
      - prefix_list_name: {{ item.0 }}
        prefix:
          - {{ item.1 }}
  with_together:
    - "{{ prefix_name }}"
    - "{{ prefix }}"

prefix_response:

[
        {
            "address_prefixes": [
                "10.0.0.0/8",
                "172.16.0.0/12",
                "192.168.0.0/16"
            ],
            "name": "test1"
        },
        {
            "address_prefixes": [
                "1.1.1.1/32",
                "2.2.2.2/32",
                "3.3.3.3/32"
            ],
            "name": "test2"
        }
]

Current return:

- prefix_list_name: RFC_1918
  prefix:
    - 10.0.0.0/8
- prefix_list_name: test2
  prefix:
    - 1.1.1.1/32

Desired return:

- prefix_list_name: TEST1
  prefix:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16

- prefix_list_name: TEST2
  prefix:
    - 1.1.1.1/32
    - 2.2.2.2/32
    - 3.3.3.3/32


Solution 1:[1]

Create a dictionary and use it in the template below

    - template:
        src: output.yaml.j2
        dest: output.yaml
      vars:
        prefix_response_dict: "{{ prefix_response.gathered|
                                  items2dict(key_name='name',
                                             value_name='address_prefixes') }}"
shell> cat output.yaml.j2
{% for k,v in prefix_response_dict.items() %}
- prefix_list_name: {{ k|upper }}
  prefix:
    {{ v|to_nice_yaml|indent(4) }}
{%- endfor %}

gives

shell> cat output.yaml
- prefix_list_name: TEST1
  prefix:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
- prefix_list_name: TEST2
  prefix:
    - 1.1.1.1/32
    - 2.2.2.2/32
    - 3.3.3.3/32

If you want to create the structure first convert the names to uppercase

prefix_response_keys: "{{ prefix_response.gathered|
                          map(attribute='name')|
                          map('upper')|
                          map('community.general.dict_kv', 'name')|list }}"

gives

prefix_response_keys:
  - name: TEST1
  - name: TEST2

Then, update the attribute name in the list

prefix_response_uppr: "{{ prefix_response.gathered|
                          zip(prefix_response_keys)|
                          map('combine')|list }}"

gives

prefix_response_uppr:
  - address_prefixes:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    name: TEST1
  - address_prefixes:
    - 1.1.1.1/32
    - 2.2.2.2/32
    - 3.3.3.3/32
    name: TEST2

The expected result, what you want, is the list above with changed names of the attributes. There are only two attributes (name and address_prefixes), therefore, let's convert it to a dictionary

prefix_response_dict: "{{ prefix_response_uppr|
                          items2dict(key_name='name',
                                     value_name='address_prefixes') }}"

gives

prefix_response_dict:
  TEST1:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
  TEST2:
    - 1.1.1.1/32
    - 2.2.2.2/32
    - 3.3.3.3/32

Then, let's convert it back to a list and change the names of the attributes

prefix_response_list: "{{ prefix_response_dict|
                          dict2items(key_name='prefix_list_name',
                                     value_name='prefix') }}"

gives

prefix_response_list:
  - prefix:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    prefix_list_name: TEST1
  - prefix:
    - 1.1.1.1/32
    - 2.2.2.2/32
    - 3.3.3.3/32
    prefix_list_name: TEST2

Now, write it to a file. Use filter to_nice_yaml to format the YAML output

shell> cat output.yaml.j2
{{ prefix_response_list|to_nice_yaml }}

Put the declarations of the variables as appropriate. For example, the task below

    - template:
        src: output.yaml.j2
        dest: output.yaml
      vars:
        prefix_response_keys: "{{ prefix_response.gathered|
                                  map(attribute='name')|
                                  map('upper')|
                                  map('community.general.dict_kv', 'name')|list }}"
        prefix_response_uppr: "{{ prefix_response.gathered|
                                  zip(prefix_response_keys)|
                                  map('combine')|list }}"
        prefix_response_dict: "{{ prefix_response_uppr|
                                  items2dict(key_name='name',
                                             value_name='address_prefixes') }}"
        prefix_response_list: "{{ prefix_response_dict|
                                  dict2items(key_name='prefix_list_name',
                                             value_name='prefix') }}"

will create the file

shell> cat output.yaml
-   prefix:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    prefix_list_name: TEST1
-   prefix:
    - 1.1.1.1/32
    - 2.2.2.2/32
    - 3.3.3.3/32
    prefix_list_name: TEST2

Solution 2:[2]

You seem to make it more complex that it should be:

  • Using JMESPath, you can reconstruct items changing their keys easily, using multiselect.
    So, in your case a simple query like this should do:
    gathered[].{prefix_list_name: name, prefix: address_prefixes}
    
  • You are not even forced to use JMESPath for this, you could get away with filters like map, zip and dict2items to reconstruct your list as you wish.
  • Ansible is very YAML capable, so you can dump a whole YAML snippet in a file in one go, with the filter to_nice_yaml.

So, in one task:

- copy:
    content: >-
      {{
        dict(
          prefix_response.gathered
          | map(attribute="name")
          | zip(
            prefix_response.gathered
            | map(attribute="address_prefixes")
          )
        )
        | dict2items(key_name='prefix_list_name', value_name='prefix')
        | to_nice_yaml
      }}
    dest: output.yaml

Even shorter, possibly, with JMESPath:

- copy:
    content: >-
      {{
        prefix_response
        | to_json
        | from_json
        | json_query('gathered[].{
            prefix_list_name: name, 
            prefix: address_prefixes
          }')
        | to_nice_yaml
      }}
    dest: output.yaml

Those will both create a file output.yml containing:

-   prefix:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    prefix_list_name: test1
-   prefix:
    - 1.1.1.1/32
    - 2.2.2.2/32
    - 3.3.3.3/32
    prefix_list_name: test2

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2