Home > Software engineering >  Get starting dictionary
Get starting dictionary

Time:01-06

I have a problem with the usage of Jinja templating in Ansible. I have this nested dictionary:

 "fruits": {
        "summer": {
            "june": {
                "fruit": {
                  "cherry": "cherry"
                },
                "vars": {}
            },
            "july": {
                "fruit": {
                  "peach": "peach"
                },
                "vars": {}
            },
            "august": {
                "fruit": {
                  "watermelon": "watermelon",
                  "strawberry": "strawberry"
                },
                "vars": {}
            }
     }}

and I want to remove the object "peach". I have tried with pop method, and It works, but I want to use the filter rejectattr

I tried:

tasks:
- set_fact:
    test: |
      {%- for x,y in fruits.items() -%}
        {%- for j,k in y.items() -%}
          {{ k.fruit | dict2items | rejectattr ('key', 'eq', 'peach') | items2dict }}
        {%- endfor -%}
      {%- endfor -%}`

It gives me the following:

 "test": "{
    "cherry": "cherry"}
    {}
    {"watermelon": "watermelon", "strawberry": "strawberry"}"

But I want to get the starting structure, less "peach" object.

Any suggestion? Thank You

CodePudding user response:

Ansible isn't a great tool for modifying deeply nested data structures (or really for modifying data structures in general). While I'm sure someone is going to drop by with a clever json_query filter or something, this is trivial to solve by writing a custom filter plugin. If we put the following in filter_plugins/badfruit.py:

def filter_badfruit(v, *exclude):
    for months in v.values():
        for data in months.values():
            data["fruit"] = {k: v for k, v in data["fruit"].items() if k not in exclude}

    return v


class FilterModule(object):
    filter_map = {"badfruit": filter_badfruit}

    def filters(self):
        return self.filter_map

Then we can write a playbook like this:

- hosts: localhost
  gather_facts: false
  vars:
    "fruits": {
           "summer": {
               "june": {
                   "fruit": {
                     "cherry": "cherry"
                   },
                   "vars": {}
               },
               "july": {
                   "fruit": {
                     "peach": "peach"
                   },
                   "vars": {}
               },
               "august": {
                   "fruit": {
                     "watermelon": "watermelon",
                     "strawberry": "strawberry"
                   },
                   "vars": {}
               }
        }}
  tasks:
    - debug:
        var: >-
          fruits|badfruit('peach')

And get this output:

PLAY [localhost] ***************************************************************

TASK [debug] *******************************************************************
ok: [localhost] => {
    "fruits|badfruit('peach')": {
        "summer": {
            "august": {
                "fruit": {
                    "strawberry": "strawberry",
                    "watermelon": "watermelon"
                },
                "vars": {}
            },
            "july": {
                "fruit": {},
                "vars": {}
            },
            "june": {
                "fruit": {
                    "cherry": "cherry"
                },
                "vars": {}
            }
        }
    }
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

The filter is written so that we can provide multiple keys to exclude:

    - debug:
        var: >-
          fruits|badfruit('peach', 'strawberry')

CodePudding user response:

Declare exclude and the template below

  exclude: ['peach']
  update: |
    summer:
    {% for i in fruits.summer|dict2items %}
      {{ i.key }}:
        fruit:
    {% for k,v in i.value.fruit.items() %}
    {% if k not in exclude %}
          {{ k }}: {{ v }}
    {% endif %}
    {% endfor %}
    {% endfor %}

The above template is a clever Jinja variant of the Python code

    for months in v.values():
        for data in months.values():
            data["fruit"] = {k: v for k, v in data["fruit"].items() if k not in exclude}

gives

  update: |-
    summer:
      august:
        fruit:
          strawberry: strawberry
          watermelon: watermelon
      july:
        fruit:
      june:
        fruit:
          cherry: cherry

Combine the dictionaries

  test: "{{ [fruits, update|from_yaml]|combine(recursive=True) }}"

gives the expected result

  test:
    summer:
      august:
        fruit:
          strawberry: strawberry
          watermelon: watermelon
        vars: {}
      july:
        fruit: null
        vars: {}
      june:
        fruit:
          cherry: cherry
        vars: {}

Example of a complete playbook for testing

- hosts: localhost

  vars:

    fruits:
      summer:
        august:
          fruit:
            strawberry: strawberry
            watermelon: watermelon
          vars: {}
        july:
          fruit:
            peach: peach
          vars: {}
        june:
          fruit:
            cherry: cherry
          vars: {}

    exclude: ['peach']
    update: |
      summer:
      {% for i in fruits.summer|dict2items %}
        {{ i.key }}:
          fruit:
      {% for k,v in i.value.fruit.items() %}
      {% if k not in exclude %}
            {{ k }}: {{ v }}
      {% endif %}
      {% endfor %}
      {% endfor %}
    test: "{{ [fruits, update|from_yaml]|combine(recursive=True) }}"

  tasks:

    - debug:
        var: fruits
    - debug:
        var: update
    - debug:
        var: test
  • Related