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