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

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.

Related

saltstack, multi-line pillar items interpolated into template

I have a pillar that looks like this:
inline_blocks:
the_seven: |
dog cat horse cow
ardvaark beatle snail
which I then want to insert into a file
{% set inline_block = pillar['inline_blocks'].get(val, '') %}
/etc/animals.conf:
file.managed:
- source: salt://farm/animals.conf
- user: root
- group: root
- mode: 644
- template: jinja
- defaults:
extras: {{ inline_block }}
and then in animals.conf,
{{ extras }}
I expect that if the key val is in inline_blocks, then its value will be interpolated in. If it's not, an empty string will be interpolated in.
Indeed, that's what happens if I write the defaults statement explicitly:
- defaults:
extras: |
dog cat horse cow
ardvaark beatle snail
but as written above, I get the error could not find expected ':'.
As a reality check, pillar.items happily retrieves the pillar entry, so (1) the pillar entry can be retrieved, and (2) the value can be interpolated, but (X) the multi-line value in the .sls file is causing problems.
Any pointers what the right syntax is to do this?
This issue was discussed in a bug as well, but for the content parameter. It seems to apply to multi-line YAML blocks passed as defaults or context also.
Since you are using a Jinja template file as a source, we can easily fetch pillar data from template itself (as one of the comments suggests in above link).
Considering pillar as:
inline_blocks:
val: |
dog cat horse cow
ardvaark beatle snail
Then the animals.conf.j2 template as:
{{ salt.pillar.get('inline_blocks:val', default="foo") }}
Note: If this pillar data is assured to be always defined, we might even use pillar['inline_blocks']['val'] in the template.
Rendered with a state like:
create-animals-conf:
file.managed:
- name: /tmp/animals.conf
- source: salt://animals.conf.j2
- mode: 0664
- template: jinja
Should yield the template as you expect:
$ cat /tmp/animals.conf
dog cat horse cow
ardvaark beatle snail
What happens is that
- defaults:
extras: {{ inline_block }}
is processed into
- defaults:
extras: dog cat horse cow
ardvaark beatle snail
So YAML tries to parse the second line as another top-level key. However, the : marking the end of this key never comes, hence the error.
To fix it, do this:
- defaults:
extras: |
{{ inline_block | indent(10) }}
indent doesn't indent the first line, but will add 10 spaces to every subsequent line.

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 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

Combine 2 dictionary in ansible / jinja2 (simple)

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"
}
}

NUNJUKS: For loop to create list of variables, but instead creates strings

I'm using nunjucks to create a JSON export file. I have a list of variables that have the same name except for an incrementing number on the end. I'm using a for loop in the following way:
{% for i in range(1, 6) -%}
{% set items = ["{{ answer",i, " }}"] %}
"Solution{{ i }}" : "{{ items | join }}",
{%- endfor %}
I want answer1 to grab the variable answer1, but instead it is giving me a string "{{ anwser1 }}" .
Any idea how to use the for loop to point to each variable (answer1, answer2, answer3, etc)?
You can add some global function or filter to get access to a context (vars scope) by name.
const nunjucks = require('nunjucks');
const env = new nunjucks.Environment();
env.addGlobal('getContext', function (prop) {
return prop ? this.ctx[prop] : this.ctx;
});
const tpl = `{% for i in range(1, 4)%} {{ getContext('a' + i) }} {% endfor %}`;
const output = env.renderString(tpl, {a1: 10, a2:20, a3: 30, b: 1, c: 2});
console.log(output);

Resources