Ansible - Replace values in a dictionary by looping through another dictionary - dictionary

I have two dictionaries as below
"fictional_characters": {
"male": [
"Donkey",
"Humpty",
"Piranha"
],
"female": [
"Fiona",
"Kitty_Softpaws",
"Diane_Foxington"
]
}
And
"movie_names": {
"Donkey": "Shrek",
"Humpty": "Puss_in_Boots",
"Piranha": "The_Bad_Guys",
"Fiona": "Shrek",
"Kitty_Softpaws": "Puss_in_Boots",
"Diane_Foxingtin": "The_Bad_Guys"
}
I would like to change the dictionary values in "fictional_characters" to the values in "movie_names", e.g.
"fictional_characters": {
"male": [
"Shrek",
"Puss_in_Boots",
"The_Bad_Guys"
],
"female": [
"Shrek",
"Puss_in_Boots",
"The_Bad_Guys"
]
}
I started by converting the "fictional_characters" dictionary into a list
- name: Convert fictional_characters to a list
set_fact:
fictional_characters_list: "{{fictional_characters | dict2items }}"
That gave me
"fictional_characters_list": [
{
"key": "male",
"value": [
"Donkey",
"Humpty",
"Piranha"
],
},
"key": "female",
"value": [
"Fiona",
"Kitty_Softpaws",
"Diane_Foxington"
]
}
]
Next, some Jinja
- name: Using Jinja to swap dict values
set_fact:
fict_char_movies: |
{% for e in fictional_characters_list %}
{{ e.key }}:
{% for char in e.value %}
{% if char in movie_names %}
- {{ movie_names[char]|split(',') %}
{% endif %}
{% endfor %}
{% endfor %}
- name: Print the result
debug:
msg: "{{ fict_char_movies | from_yaml }}"
the above returns the following result
"male": [
[
"Shrek"
],
[
"Puss_in_Boots"
],
[
"The_Bad_Guys"
],
"female": [
[
"Shrek"
],
[
"Puss_in_Boots"
],
[
"The_Bad_Guys"
]
]
How do I get rid of this nested list so that I get the below structure ?
"fictional_characters": {
"male": [
"Shrek",
"Puss_in_Boots",
"The_Bad_Guys"
],
"female": [
"Shrek",
"Puss_in_Boots",
"The_Bad_Guys"
]
}

The declarations below
fict_char_movies_str: |
{% for k,v in fictional_characters.items() %}
{{ k }}:
{% for char in v %}
{% if char in movie_names %}
- {{ movie_names[char] }}
{% endif %}
{% endfor %}
{% endfor %}
fict_char_movies: "{{ fict_char_movies_str|from_yaml }}"
The template can be simplified
fict_char_movies_str: |
{% for k,v in fictional_characters.items() %}
{{ k }}:
{{ v|map('extract', movie_names) }}
{% endfor %}
give what you want
fict_char_movies:
female:
- Shrek
- Puss_in_Boots
- The_Bad_Guys
male:
- Shrek
- Puss_in_Boots
- The_Bad_Guys
If you want to iterate set_fact the task below gives the same result
- set_fact:
fict_char_movies: "{{ fict_char_movies|d({})|combine(_dict|from_yaml) }}"
loop: "{{ fictional_characters|dict2items }}"
vars:
_dict: "{ {{ item.key }}: {{ item.value|map('extract', movie_names) }} }"
Example of a complete playbook for testing
- hosts: localhost
vars:
fictional_characters:
female:
- Fiona
- Kitty_Softpaws
- Diane_Foxington
male:
- Donkey
- Humpty
- Piranha
movie_names:
Diane_Foxington: The_Bad_Guys
Donkey: Shrek
Fiona: Shrek
Humpty: Puss_in_Boots
Kitty_Softpaws: Puss_in_Boots
Piranha: The_Bad_Guys
fict_char_movies_str: |
{% for k,v in fictional_characters.items() %}
{{ k }}:
{{ v|map('extract', movie_names) }}
{% endfor %}
fict_char_movies: "{{ fict_char_movies_str|from_yaml }}"
tasks:
- debug:
var: fict_char_movies

Vladimir's solution is probably cleaner, but it's also possible to get to the same place without using a Jinja template. For example, we can write:
- hosts: localhost
gather_facts: false
vars:
fictional_characters:
male:
- Donkey
- Humpty
- Piranha
female:
- Fiona
- Kitty_Softpaws
- Diane_Foxington
- Meilin_Lee
movie_names:
Donkey: Shrek
Humpty: Puss_in_Boots
Piranha: The_Bad_Guys
Fiona: Shrek
Kitty_Softpaws: Puss_in_Boots
Diane_Foxington: The_Bad_Guys
Meilin_Lee: Turning_Red
tasks:
- set_fact:
new_fictional_characters: >-
{{
new_fictional_characters|combine(
{
'male': item.0|ternary(new_fictional_characters.male + [movie_names[item.0]], new_fictional_characters.male),
'female': item.1|ternary(new_fictional_characters.female + [movie_names[item.1]], new_fictional_characters.female),
}
)
}}
loop: >-
{{ fictional_characters.male|zip_longest(fictional_characters.female) }}
vars:
new_fictional_characters:
male: []
female: []
- set_fact:
fictional_characters: "{{ new_fictional_characters }}"
- debug:
var: fictional_characters
Note that I've added an additional character here to demonstrate that this works when the lists are of different lengths. Running this produces:
PLAY [localhost] ***************************************************************
TASK [set_fact] ****************************************************************
ok: [localhost] => (item=['Donkey', 'Fiona'])
ok: [localhost] => (item=['Humpty', 'Kitty_Softpaws'])
ok: [localhost] => (item=['Piranha', 'Diane_Foxington'])
ok: [localhost] => (item=[None, 'Meilin_Lee'])
TASK [set_fact] ****************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"fictional_characters": {
"female": [
"Shrek",
"Puss_in_Boots",
"The_Bad_Guys",
"Turning_Red"
],
"male": [
"Shrek",
"Puss_in_Boots",
"The_Bad_Guys"
]
}
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
An even simpler solution is to drop the following into filter_plugins/swap_names.py:
def filter_swap_names(val, name_map):
return {k: [name_map[name] for name in v] for k, v in val.items()}
class FilterModule:
def filters(self):
return dict(swap_names=filter_swap_names)
And then write the playbook like this:
- hosts: localhost
gather_facts: false
vars:
fictional_characters:
male:
- Donkey
- Humpty
- Piranha
female:
- Fiona
- Kitty_Softpaws
- Diane_Foxington
- Meilin_Lee
movie_names:
Donkey: Shrek
Humpty: Puss_in_Boots
Piranha: The_Bad_Guys
Fiona: Shrek
Kitty_Softpaws: Puss_in_Boots
Diane_Foxington: The_Bad_Guys
Meilin_Lee: Turning_Red
tasks:
- set_fact:
fictional_characters: "{{ fictional_characters|swap_names(movie_names) }}"
- debug:
var: fictional_characters

Related

Nested Unless tags break Liquid/Jekyll

I've been working with generating some JSON data from liquid tags, it seems that excluding the last comma in this JSON data is not happening when nesting 'unless' tags.
Please see answer below, this initial issue and question was not the problem.
<script id="suggest-json" type="application/json">
[
{%- for item in site[page.collection] -%}
{%- unless item.url == page.url -%}
{% assign images = item.media | where: "type", "image" %}
{% assign image = images.first %}
{
"url": "{{site.baseurl}}{{ item.url }}",
"title": "{{ item.title }}",
"subject": "{{ item.game }}",
"image": "{{ image.thumbnail }}",
"alt": "{{ image.thumbnail_alt }}"
} {%- unless forloop.last -%},{%- endunless -%}
{%- endunless -%}
{%- endfor -%}
]
</script>
Undesired Output (The last comma is invalidating the Json):
[
{
"url": "/portfolio/levels/low-life/",
"title": "Low-Life",
"subject": "Final Verdikt: Source",
"image": "https://picsum.photos/445/296?random=1",
"alt": "Image 1"
},
{
"url": "/portfolio/levels/prospekt/",
"title": "Prospekt",
"subject": "Final Verdikt: Source",
"image": "https://picsum.photos/445/296?random=1",
"alt": "Image 1"
},
{
"url": "/portfolio/levels/station/",
"title": "Station",
"subject": "Half-Life 2: Opposition",
"image": "https://picsum.photos/445/296?random=1",
"alt": "Image 1"
},
{
"url": "/portfolio/levels/dredge/",
"title": "Dredge",
"subject": "Firearms: Source",
"image": "https://picsum.photos/445/296?random=1",
"alt": "Image 1"
},
{
"url": "/portfolio/levels/gravitas/",
"title": "Gravitas",
"subject": "Firearms: Source",
"image": "https://picsum.photos/445/296?random=1",
"alt": "Image 1"
},
{
"url": "/portfolio/levels/navarro/",
"title": "Navarro",
"subject": "Firearms: Source",
"image": "https://picsum.photos/445/296?random=1",
"alt": "Image 1"
},
{
"url": "/portfolio/levels/deadlock/",
"title": "Deadlock",
"subject": "Half-Life 2: Opposition",
"image": "https://picsum.photos/445/296?random=1",
"alt": "Image 1"
},]
I've tried If statements, assigning booleans for isLast index, spacing and other formatting but nothing works...
Answer
The main issue here was separating my array into a new suggestions array minus the filtered item, which makes forloop.last actually happen. The other way was just ignoring the filtered item, but considering it still part of the array, which is why forloop.last was never true.
I also use {% raw %} tag to escape the JSON brackets for safe measure. I thought this might be causing Jekyll to try and process them as a tag, leading to an error with no actual error output in the --verbose build logs. Although I think the main culprit was the need to create a separate array to loop through.
{% assign suggestions = "" | split: ',' %}
{%- for item in site[page.collection] -%}
{%- unless item.url == page.url -%}
{% assign suggestions = suggestions | push: item %}
{%- endunless -%}
{%- endfor -%}
<script id="suggest-json" type="application/json">
[
{%- for item in suggestions -%}
{% assign images = item.media | where: "type", "image" %}
{% assign image = images.first %}
{% raw %}
{
{% endraw %}
"url": "{{ site.baseurl }}{{ item.url }}",
"title": {{ item.title | jsonify }},
"subject": {{ item.game | jsonify }},
"image": {{ image.thumbnail | jsonify }},
"alt": {{ image.thumbnail_alt | jsonify }}
{% raw %}
}
{% endraw %}
{%- unless forloop.last -%},{%- endunless -%}
{%- endfor -%}
]
</script>

Ansible: print elements from joined list of dictionaries

I am trying to access elements from both lists: lista1 and lista2.
First join them into one dictionary and then to be able to do something like this:
Vegetable {{ lista1.fruits.name }} has taste {{ lista1.fruits.taste }}
This is what I got so far, but it's really bad,and I would like to have dictionary like access instead of regex_search.
---
- name: demo how register works
hosts: localhost
vars:
lista1:
fruits:
- name: tomato
taste: tomato-like
- name: lemon
taste: sour
lista2:
vegetables:
- name: carrot
taste: sweet
tasks:
- name: debug
debug:
var: item
loop:
- lista1.fruits
- lista2.vegetables
register: echo
- name: show register results
debug:
msg: "Food named: {{ item| map(attribute='name')|list|join(', ') |regex_search('tomato') }} tastes: {{ item| map(attribute='taste')|list|join(', ')|regex_search('tomato-like') }}"
loop: "{{ lista1.fruits|zip(lista2.vegetables)|list }} "
A debug task to show the "food" with its "taste" can be displayed by accessing item.name and item.taste by looping through lista1.fruits and lista2.vegetables.
Example:
- name: show register results
debug:
msg: "Food named: {{ item.name }} tastes: {{ item.taste }}"
with_items:
- "{{ lista1.fruits }}"
- "{{ lista2.vegetables }}"
Update
Instead of with_items, loop can be used as:
- name: show register results
debug:
msg: "Food named: {{ item.name }} tastes: {{ item.taste }}"
loop: "{{ lista1.fruits + lista2.vegetables }}"

How can I get the data of an object in datatables?

I am getting data from a json file into my datatable.
"columns": [
{% for key, value in columns %}
{
"data": "{{ key }}"},
{% endfor %}
]
Like this I get the following output:
id name slug icon
2 Mitarbeiter members [object Object]
3 Angebote offers [object Object]
4 Produkte products [object Object]
5 Felder fields [object Object]
To recieve the data of the object, I changed my code to this:
"columns": [
{% for key, value in columns %}
{ "data": "{{ key }}.name",
"defaultContent": "{{ key }}"},
{% endfor %}
]
This is working well for the object, but now my other fields do not show the value anymore, the show the label of the column:
id name slug icon
id name slug icon
id name slug anchor
id name slug adjust
id name slug cloud
dump of columns:
array:5 [▼
"id" => ReflectionProperty {#6092 ▶}
"name" => ReflectionProperty {#6094 ▶}
"slug" => ReflectionProperty {#6096 ▶}
"icon" => ReflectionProperty {#6097 ▶}
]
Another approach is this:
"columns": [
{% for key, value in columns %}
{% if key is iterable %}
{"data": "{{ key }}"},
{% else %}
{"data": "{{ key }}.name"},
{% endif %}
{% endfor %}
]
But here I get only the output of the icons row...
The json file is this:
[{"id":2,"name":"Mitarbeiter","icon":{"id":2,"name":"anchor"},"slug":"members"},{"id":3,"name":"Angebote","icon":{"id":1,"name":"adjust"},"slug":"offers"},{"id":4,"name":"Produkte","icon":{"id":1,"name":"adjust"},"slug":"products"},{"id":5,"name":"Felder","icon":{"id":1,"name":"cloud"},"slug":"fields"}]
Finally found a solution:
"columnDefs": [
{
"render": function (data, type, row) {
var type = typeof data;
if(type == "object"){
return data.name;
} else {
return data;
}
},
"targets": "_all"
}
],
"columns": [
{% for key, value in columns %}
{ "data": "{{ key }}"},
{% endfor %}
]
Truy to use: of_type('object')
"columns": [
{% for key, value in columns %}
{
"data": "{% if key is of_type('object') %}{{ key }}.name{% else %}{{ key }}{% endif %}"},
{% endfor %}
{ "data": "id" }
]

How can I set a variable with twig?

I want to set the variable key before using it
"columns": [
{% for key, value in columns %}
{"data": "{{ key }}"},
{% endfor %}
{ "data": "id" }
]
My approach:
"columns": [
{% for key, value in columns %}
{% set result = '{{ key }}' %}
{"data": "{{ result }}"},
{% endfor %}
{ "data": "id" }
]
But it is not working. I get the error message:
Requested unknown parameter '{{ key }}'
{% set result = key %}
From what I see, you want to set the value of result variable, result of key is already set, you just didn't access it correctly.

accessinig current pillar items in a loop from another template?

I have snippet like this in the init.sls:
{% for server, args in pillar.get('servers', {}).items() %}
software-server#{{ server }}
service.running:
- enable: true
- require:
- pkg: software_pkgs
- watch:
- file: software_config
/etc/software/{{server}}.json:
file.managed:
- source: salt://software/files/config.json.j2
- template: jinja
{% endfor %}
config.json.j2:
{
listen: {{server}}:{{listen_addr}}
}
and in the pillar:
software.servers:
server1:
listen_addr:10.0.0.1
server2:
listen_addr:127.0.01
in each of the {{server}}.json the listen_addr is different. I don't know if saltstack has something like a scope for current loop, or is there a workaround for this.
You probably need to use context or defaults options in file.managed:
file.managed
In your example it would like like :
/etc/software/{{server}}.json:
file.managed:
- source: salt://software/files/config.json.j2
- template: jinja
- context:
server: {{ server }}
listen_addr: {{ server['listen_addr'] }}

Resources