Combine 2 dictionary in ansible / jinja2 (simple) - ansible

I want to use ansible to merge below 2 dictionary to become 1.
I have been stucking for few days already. Headache.
I have 2 variables now.
1. variable "my_directories":
{
"directoryA": [
"/bar/foo/file1.txt",
"/bar/foo/file2.txt"
],
"directoryB": [
"/bar/baz/file3.txt",
"/bar/baz/file4.txt",
"/bar/baz/file5.txt"
]
}
2. variable "my_filecontents":
{
"/bar/foo/file1.txt": "file1Content",
"/bar/foo/file2.txt": "file2Content",
"/bar/baz/file3.txt": "file3Content",
"/bar/baz/file4.txt": "file4Content",
"/bar/baz/file5.txt": "file5Content"
}
i want to merge it to become:
Result:
variable my_result
{
"directoryA": {
"/bar/foo/file1.txt": "file1Content",
"/bar/foo/file2.txt": "file2Content"
},
"directoryB": {
"/bar/baz/file3.txt": "file3Content",
"/bar/baz/file4.txt": "file4Content",
"/bar/baz/file5.txt": "file5Content"
}
}
The directories and files could be dynamic.
I tried so many codes but still did not work.
Thank you!

The task below does the job
- set_fact:
my_result: "{{ my_result|default({})|
combine({item.0.key: {item.1: my_filecontents[item.1]}},
recursive=True) }}"
with_subelements:
- "{{ my_directories|dict2items }}"
- value
- debug:
var: my_result
gives
"my_result": {
"directoryA": {
"/bar/foo/file1.txt": "file1Content",
"/bar/foo/file2.txt": "file2Content"
},
"directoryB": {
"/bar/baz/file3.txt": "file3Content",
"/bar/baz/file4.txt": "file4Content",
"/bar/baz/file5.txt": "file5Content"
}
}

Related

Ansible object list from merging a list and a value

Given a string list
list:
- a
- b
and a string
str: c
How can be obtained an object list with the same number of elements as the first list with the following structure?:
new_list:
- list_key: a
str_key: c
- list_key: b
str_key: c
Also, I'm using Ansible 2.8
For example
- set_fact:
new_list: "{{ list|
map('regex_replace', '^(.*)$', '{list_key: \\1}')|
map('from_yaml')|
map('combine', {'str_key': str})|
list }}"

Ansible template from jinja2

Who can tell you how to implement the output of all regions in the name1 group when entering a template named region1a, and when entering a template named region2b, output all regions from the name2 group
I implement it like this:
there is a task that starts template generation:
vars:
AllCountry:
- name1
- name2
name1:
- region1a
- region1b
name2:
- region2a
- region2b
tasks:
- name:
template:
src: "regions.j2"
dest: "{{ item }}.conf"
loop:
- region1a
- region2b
---regions.j2---
regions [{%for count in name1%} "my country = {{count}}", {%end for %}]
this gives the desired output, but only because it is explicitly specified for which name (1 or 2) to output
regions "my country = region1a", "my country = region1b"
For each value specified in the loop, a template configuration file must be generated.
When you specify values in loop region1a and region1b template should generate only one row in the configuration file for region1a.conf
regions "my country = region1a", "my country = region1b"
for region1b generate only one row in the configuration file for region1b.conf
regions "my country = region1a", "my country = region1b"
User β.εηοιτ.βε a more optimal structure was proposed. If convenient, you can use it.
vars:
countries:
country1:
regions:
- region1
- region2
- region3
capital: region1
country2:
regions:
- region4
- region5
capital: region5
Thank you all for your help. Still, I managed to figure it out myself.
Here is the final solution:
{% for country in AllCountry %}
{% if item in lookup('vars', country) %}{% for count in lookup('vars', country) %} "My country = {{ count }}"{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{% endfor %}

Ansible: How to loop through a template and create individual files?

I have to loop through a template and create individual files from it in a systematic manner. For example..
Template sample (eg shop_names.conf.j2)
{% for item in shop_names_csv_list %}
# Delete the id
DELETE root/{{item.shop_id}}
# Create the id with contents
PUT root/{{item.shop_id}}
{
"shop_name": "{{item.shop_name}}"
"shop_state": "{{item.shop_state}}"
}
{% endfor %}
I've a CSV in below format which contains data to populate the template
shop_id,shop_name,shop_state
101,Walmart,NY
102,Cosco,MT
103,Apple,DC
I was able to loop through using Ansible and fill it up the template correctly and outputs as shop_names.conf (single file)
The logic I'm using currently is:
- read_csv:
path: shop_names.csv
register: shop_names_csv_list
- name: "Build shop name API endpoints from Template"
template:
src: "{{ item }}"
dest: "{{buildDir}}/{{ item | replace('.j2', '')}}"
with_lines:
- "find {{base_dir_template}} -type f -name '*.j2'"
So currently my output comes as
DELETE root/101
PUT root/101
{
"shop_name": "Walmart"
"shop_state": "NY"
}
# ------- Next Line ------ #
DELETE root/102
PUT root/102
{
"shop_name": "Cosco"
"shop_state": "MT"
}
# ------- Next Line ------ #
DELETE root/103
PUT root/103
{
"shop_name": "Apple"
"shop_state": "DC"
}
# ------- Next Line ------ #
But I was looking to output it as individual files in format of: {{shop_id}}.{{shop_names}}.conf , so that is each DELETE & PUT into single document each per shop_id
So the output, i'm looking for is 3 files namely
101.Walmart.conf
DELETE root/101
PUT root/101
{
"shop_name": "Walmart"
"shop_state": "NY"
}
102.Cosco.conf
DELETE root/102
PUT root/102
{
"shop_name": "Cosco"
"shop_state": "MT"
}
and so on...
Any help to create the template as individual files would be appreciated.

Ansible if else while iterating over a dictionary

I have a dictionary of dictionaries collecting data from openshift using prometheus. Now I intend to add values in all the dictionaries. But some projects don't have quota and hence some pods don't have request/limit set for cpu and memory. I am trying the following and it fails in case the key:value is not there.
If possible I want to use if else such that, if the variable exists then add the variable else use the value as 0.
- name: Total section for Projects
set_fact:
pod_count_total: "{{ (pod_count_total|int) + (item.value.pod_count|int)}}"
total_cpu_request: "{{ (total_cpu_request|float |round(2,'ceil')) + (item.value.cpu_request|float |round(2,'ceil'))}}"
total_cpu_limit: "{{ (total_cpu_limit|float |round(2,'ceil')) + (item.value.cpu_limit|float |round(2,'ceil'))}}"
total_memory_request: "{{ (total_memory_request|float |round(2,'ceil')) + (item.value.memory_request|float |round(2,'ceil'))}}"
total_memory_limit: "{{ (total_memory_limit|float |round(2,'ceil')) + (item.value.memory_limit|float |round(2,'ceil'))}}"
with_dict: "{{all_project}}"
Dictionary of dictionaries is like
ok: [127.0.0.1] => {
"msg": {
"openshift-web-console": {
"cpu_usage": 0.015,
"memory_used": 0.04,
"cpu_request": 0.301,
"memory_request": 0.293,
"pod_count": 3
},
"srv-test": {
"cpu_usage": 0.013,
"memory_used": 0.02,
"pod_count": 5
},
"test": {
"cpu_usage": 0.001,
"memory_used": 0.0,
"pod_count": 1
},
"openshift-monitoring": {
"cpu_limit": 1.026,
"cpu_request": 0.556,
"cpu_usage": 0.786,
"memory_limit": 1.866,
"memory_request": 1.641,
"memory_used": 0.14,
"pod_count": 98
}
}
}
If possible I want to use if else such that, if the variable exists then add the variable else use the value as 0.
The thing you are looking for is the default filter
total_memory_request: "{{ (
total_memory_request | default(0)
| float | round(2,'ceil')
) + (
item.value.memory_request | default(0)
| float | round(2,'ceil')
) }}"
There's a subtlety in that if the variable exists but is the empty string, you'll need to pass in the 2nd parameter to default to have it act in a python "truthiness" way: {{ "" | default(0, true) | float }} -- that might not apply to you, but if it does, you'll be glad to know what that 2nd param does

How to dynamically select json object to be used in an Ansible task?

Is it possible to select which json object to use based on some dynamic criteria?
I have a yml file:
- name: Get data
uri:
url: "foo/get_data/{{item.name}}"
return_content: yes
with_items: "{{stuff_names}}"
register: app_out
- name: Use data
uri:
url: "foo/use_data/item.json[0].id"
method: POST
with_items: "{{ app_out.results }}"
Where the call to foo/get_data/<name> returns a json array with 1 or 2 items:
Sample from foo/get_data/bar1:
[{"id": 1, "type": "x"},{"id": 2, "type": "y"}]
Sample from foo/get_data/bar2:
[{"id": 1, "type": "x"}]
In the "Use data" task is there a way to specify which json object in the array to use based on if an object exists in item or the size of item etc? Right now its hard coded to the first object item.json[0].
For example,
if item contains an object of type "y" then use that object, otherwise use item of type "x".
or
if size of item is > 1, use item of type "x".
EDIT:
Or perhaps even a separate ansible task to prune the registered app_out?
Q: "if item contains an object of type "y" then use that object, otherwise use item of type "x"
A: The play below implements this logic, I think.
- hosts: localhost
vars:
results:
- {"id": 1, "type": "x"}
- {"id": 2, "type": "y"}
- {"id": 3, "type": "z"}
tasks:
- set_fact:
my_list: "{{ results|
selectattr('type', 'defined')|
selectattr('type', 'equalto','y')|list }}"
- set_fact:
my_list: "{{ results|
selectattr('type', 'defined')|
selectattr('type', 'equalto','x')|list }}"
when: my_list|length == 0
- name: Use type y, otherwise use type x
debug:
var: my_list
gives
"my_list": [
{
"id": 2,
"type": "y"
}
]

Resources