ansibleyaml

How to convert complex YAML dictionary to another format?


I have an Ansible role that uses a rather complex configuration in form of a dictionary. The old config format wasn't beneficial in many ways so I rewrote the role to be a lot easier to configure.

As the role is already in broad use I'd like to stay compatible with existing inventories and playbooks that use the old config format. In order to achieve this I thought I could migrate the old config into the new format (all data is there and migratable, so technically it's possible) before running the role.

Sample data

Old config format:

petshop_old:
  petshop_data:
    name: awesome_petshop
  employee_data:
    - name_of_employee: sara
      date_of_birth: 2000
    - name_of_employee: john
      date_of_birth: 1995
  task_data:
    - name: clean_dogs
      time: morning
      by: john
    - name: clean_cats
      time: evening
      by: john
    - name: feed_birds
      time: evening
      by: sara

New config format:

petshop_new:
  name: awesome_petshop
  employees:
    - name: sara
      birth_year: 2000
      tasks:
        - name: feed_birds
          time: evening
    - name: john
      birth_year: 1995
      tasks:
        - name: clean_dogs
          time: morning
        - name: clean_cats
          time: evening

tasks/main.yml:

- name: Migrate the old config data into the new format
  when: (petshop_old is defined) and (petshop_new is not defined)
  block:
    [...] Here come the migration tasks [...]

- name: This tasks prints the config, even when originally petshop_old was given to the role
  ansible.builtin.debug:
    var: petshop_new
[...] do more stuff with the data [...]

What would be the best approach to migrate the old config into the new format before running the actual tasks that perform the role?


Solution

  • For the given old.conf YAML file, using a quick and lazy loop approach with Jinja2 Templating, a minimal example playbook

    ---
    - hosts: localhost
      become: false
      gather_facts: false
    
      tasks:
    
        - include_vars:
            file: old.conf
            name: old
    
        - debug:
            var: old
    
        - debug:
            msg: |
              petshop_new:
                name: {{ old.petshop_old.petshop_data.name }}
                employees:
              {% for employee in old.petshop_old.employee_data %}
                  - name: {{ employee.name_of_employee }}
                    birth_year: {{ employee.date_of_birth }}
                    tasks:
              {% for task in old.petshop_old.task_data %}
              {% if employee.name_of_employee == task.by %}
                      - name: {{ task.name }}
                        time: {{ task.time }}
              {% endif %}
              {% endfor %}
              {% endfor %}
    

    will result into an output of

    TASK [debug] *****************
    ok: [localhost] =>
      old:
        petshop_old:
          employee_data:
          - date_of_birth: 2000
            name_of_employee: sara
          - date_of_birth: 1995
            name_of_employee: john
          petshop_data:
            name: awesome_petshop
          task_data:
          - by: john
            name: clean_dogs
            time: morning
          - by: john
            name: clean_cats
            time: evening
          - by: sara
            name: feed_birds
            time: evening
    
    TASK [debug] *****************
    ok: [localhost] =>
      msg: |-
        petshop_new:
          name: awesome_petshop
          employees:
            - name: sara
              birth_year: 2000
              tasks:
                - name: feed_birds
                  time: evening
            - name: john
              birth_year: 1995
              tasks:
                - name: clean_dogs
                  time: morning
                - name: clean_cats
                  time: evening
    

    It can become easily written out via

        - copy:
            dest: new.conf
            content: |
              pet...