Use Dict in Vars with Templates in Ansible - ansible

I'm trying to use templates with different sets of variables for each itteration of a determined set of tasks. For example, in one of the tasks I'd like to set specific values for postgres:
- name: Define values for postgres-ds
template:
src: postgres-ds.xml.j2
dest: /opt/ear_{{ instance_control.value }}/postgres-ds.xml
vars: "{{ postgres_desenv }}"
notify: Restart Service
In role/vars/main.yaml, I defined:
postgres_desenv:
var1: somevalue
var2: someothervalue
...
Still, I get the following error:
fatal: [rmt]: FAILED! => {
"failed": true,
"reason": "Vars in a Task must be specified as a dictionary, or a list of dictionaries
...
When I try to use the same variable in another context, it works fine:
- debug:
msg: "{{ item.key }} - {{ item.value }}"
with_dict: "{{ postgres_desenv }}"
I tried following the answers to this question but I'm still stuck.
My next step is to use a variable to call the variable inside vars, something like:
- name: Define values for postgres-ds
template:
src: postgres-ds.xml.j2
dest: /opt/ear_{{ instance_control.value }}/postgres-ds.xml
vars: postgres_{{ another_var }}
notify: Restart Service

You can do something like this:
- name: Define values for postgres-ds
template:
src: postgres-ds.xml.j2
dest: /opt/ear_{{ instance_control.value }}/postgres-ds.xml
vars:
settings: "{{ postgres_desenv }}"
notify: Restart Service
Then within the template you could refer to, e.g.,
{{ settings.var1 }}

In my case, following the answer above, all i had to do is using {{ item.value.(mydictkey) }} and that's it
In my case i defined a global variable like so:
vars:
vhosts:
web1
port: 8080
dir: /mywebsite
web2:
...
Then in the task I used:
- name: Render template
template:
src: "../templates/httpd.vhost.conf.j2" # Local template
dest: "/etc/httpd/conf.d/{{ item.key }}.conf" # Remote destination
owner: root
group: root
mode: 644
with_dict: "{{ vhosts }}"
In the template I used:
<VirtualHost *:{{ item.value.port }}>
DocumentRoot /var/www/{{ item.value.dir }}
</VirtualHost>

If postgres_desenv is defined in vars/main.yml that will be loaded automatically and be available to the role and rest of the playbook. Why do you have to specify that again using "vars" option in the template module task?

Related

Data from vault with with_items groups

I have a task that generates my configuration from jinja2 to conf.
- name: check password
set_fact:
my_secrets: "{{ lookup('hashi_vault', 'secret=kv/{{ stage }}.d/{{ app }}/{{ item }}/secrets token={{ token }} url={{ url }} validate_certs={{ validate_certs }}')}}"
with_items: "{{ groups['ns'] }}"
- name: copy config powerdns_auth pdns.local.gmysql.conf
template:
src: ../../../update/ns/templates/etc/powerdns/pdns.d/pdns.local.gmysql.conf.j2
dest: ../../../config/{{ stage }}/{{ item }}/etc/powerdns/pdns.d/pdns.local.gmysql.conf
mode: '0644'
with_items: "{{ groups['ns'] }}"
in pdns.local.gmysql.conf.j2
gmysql-password={{ my_secrets.user_password_mysql }}
I have a problem because it saves me the from vault password from the last host to a file.
Is it possible to set the fact depending on the host?
Don't loop over groups, use the "natural" play loop on hosts and delegate the needed tasks to localhost.
Note: I kept your relative paths in the template tasks but it looks ugly and will break one day or an other.
Note2: "moustaches don't stack" => I fixed your code (there are other ways to fix it...) where it was incorrect when fetching from hashicorp vault.
- name: Create config files per hosts
hosts: ns
gather_facts: false
vars:
# All your needed vars that I will not define here for this example
tasks:
- name: check password
vars:
secret: "kv/{{ stage }}.d/{{ app }}/{{ inventory_hostname }}/secrets"
hashi_string: "secret={{ secret }} token={{ token }} url={{ url }} validate_certs={{ validate_certs }}"
set_fact:
my_secrets: "{{ lookup('hashi_vault', hashi_string) }}"
- name: copy config powerdns_auth pdns.local.gmysql.conf
template:
src: ../../../update/ns/templates/etc/powerdns/pdns.d/pdns.local.gmysql.conf.j2
dest: ../../../config/{{ stage }}/{{ inventory_hostname }}/etc/powerdns/pdns.d/pdns.local.gmysql.conf
mode: '0644'
delegate_to: localhost

Ansible conditionally loop through with_items?

Is it possible to loop through a list of items if a string is defined in a variable i will specify.
Essentially i want to have a list of variables defined and utilized the aws_s3 module to download the files only if they are defined when running the playbook
e.g
say i have the list "var1,var2"
and I have the following variables defined:
apps_location:
- { name: 'vars1', src: 'vars1.tgz', dest: '/tmp/vars1_file.tgz' }
- { name: 'vars2', src: 'vars2.tgz', dest: '/tmp/vars2_file.tgz' }
- { name: 'vars3', src: 'vars3.tgz', dest: '/tmp/vars3_file.tgz' }
Task:
- name: "Splunk Search Head | Download Splunk Apps from S3"
aws_s3:
bucket: "{{ resource_bucket_name }}"
object: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: get
with_items: "{{ apps_location }}"
I want to run the command:
ansible-playbook -i inventory -e "var1,var2"
and download only var1 and var2 on that specific run.
I tried utilizing "lookups" but couldnt get the syntax right. Im not entirely sure if this best way of doing this, but i want to have a predefined list of file locations and only download the ones that i'm passing during runtime.
Note the only reason "name" exists in apps_location is to see if i can do a lookup and only install that one but i couldnt get the syntax right.
Define a variable containing a list of defined apps. I'm trying:
- name: "Set Fact"
set_fact:
dict: "{{ apps_location[item].dest }}"
with_items: "{{ my_vars|default([]) }}"
However whenever I output dict I only get the last value.
Any help would be appreciated :)
The extra-vars must be an assignment of a variable and value. For example
shell> ansible-playbook -i inventory -e "my_vars=['vars1','vars2']"
A more convenient structure of the data would be a dictionary for this purpose. For example
apps_location:
vars1:
src: 'vars1.tgz'
dest: '/tmp/vars1_file.tgz'
vars2:
src: 'vars2.tgz'
dest: '/tmp/vars2_file.tgz'
vars3:
src: 'vars3.tgz'
dest: '/tmp/vars3_file.tgz'
Then the loop might look like
- aws_s3:
bucket: "{{ resource_bucket_name }}"
object: "{{ apps_location[item].src }}"
dest: "{{ apps_location[item].dest }}"
mode: get
loop: "{{ my_vars|default([]) }}"
Q: "Define a variable containing a list of defined apps."
A: Try this
- set_fact:
my_list: "{{ my_list(default([]) +
[apps_location[item].dest] }}"
loop: "{{ my_vars|default([]) }}"
(not tested)

Replace host variables in Jijnja template file in ansible

I need to replace a file with the variables defined in the host file.
Following is the host variables defined in "host_vars/abc.1234.com"
env: acc
abcserverName:
- name: abc1
- name: abc2
The playbook file has the following contents
- hosts: "abc.1234.com"
become: yes
tasks:
- name: deploy abc control file
template:
src: abc-control.j2
dest: /etc/init.d/{{ env }}-{{ item.name }}
with_items:
- "{{ abcservername }}"
- name: start abcserver
command: /etc/init.d/control-{{ env }}-{{ item.name }} start
with_items:
- "{{ abcserverName }}"
This will copy 2 files in init.d, which is the following:
/etc/init.d/control-acc-abc1
/etc/init.d/control-acc-abc2
Requirement:
Inside each of the above control files, I also need to get the correct "abcserverName"
For example in "/etc/init.d/control-acc-abc1", I want
SERVER_NAME=abc1
and in For example in "/etc/init.d/control-acc-abc2"
SERVER_NAME=abc2
I don't have much knowledge about jinja templates and google shows me complex examples. Any help to achieve this is appreciated.
Take this jinja2 code for example:
SERVER_NAME={{ server_name }}
server_name is not actually defined, and to use it you will need to pass it as a variable to your template.
Using your task:
- name: deploy abc control file
template:
src: abc-control.j2
dest: /etc/init.d/{{ env }}-{{ item.name }}
with_items:
- "{{ abcservername }}"
vars:
server_name: {{ item.name }}
Now "control-acc-abc1" will have server_name set to abc1 and "control-acc-abc2" will have server_name set to abc2

Pass dictionary to jinja2 template from task loop

I pass dictionary from play to task. I use loop to call another task from separate yml file again passing the dictionary. From there I call Jinja2 template and pass the dictionary again. I cannot access the dictionary values from Jinja2.
I tried to pass the dictionary to template with_items and with_dict. Still the same problem.
play:
- role: example
vars:
brands:
brand_1:
name: "brand1"
brand_2:
name: "brand2"
brand_3:
name: "brand_3"
task in role with loop:
- name: Loop through configuration files
include_tasks: generate_config_files.yml
loop: "{{ lookup('dict', brands) }}"
loop_control:
loop_var: outer_item
generate_config_files.yml
- name: Generate the configuration files
template:
src: "consumer.properties.j2"
dest: "{{ kafka_location }}/{{ item.key }}/consumer.properties"
owner: "{{ kafka_user }}"
group: "{{ kafka_group }}"
mode: 0644
with_dict: "{{ outer_item }}"
consumer.properties.j2
{% for item in outer_item %}
Name: "{{ item.name }}"
{% endfor %}
I expect to access the dictionary value in template and generate the same file with different values based on number of brands in dictionary. So if there are 3 brands I expect to generate 3 files with different Name: inside.
Unfortunately I am getting:
"msg": "AnsibleUndefinedVariable: 'str object' has no attribute 'name'"
Any ideas?
1) Indentation of vars: is wrong.
2) The single loop does the job.
3) Iteration in the template is not necessary.
4) Numeric mode must be quoted mode: '0644'.
The playbook below
- hosts: localhost
roles:
- role: example
vars:
kafka_user: admin
kafka_group: admin
kafka_location: /scratch
brands:
brand_1:
name: "brand1"
brand_2:
name: "brand2"
brand_3:
name: "brand_3"
with tasks
$ cat roles/example/tasks/main.yml
- include_tasks: generate_config_files.yml
, with the included task
$ cat roles/example/tasks/generate_config_files.yml
- name: Generate the configuration files
template:
src: "consumer.properties.j2"
dest: "{{ kafka_location }}/{{ item.key }}/consumer.properties"
owner: "{{ kafka_user }}"
group: "{{ kafka_group }}"
mode: '0644'
loop: "{{ brands|dict2items }}"
, and with the template
$ cat roles/example/templates/consumer.properties.j2
Name: "{{ item.value.name }}"
gives
$ tree /scratch/brand_*
/scratch/brand_1
└── consumer.properties
/scratch/brand_2
└── consumer.properties
/scratch/brand_3
└── consumer.properties
$ cat /scratch/brand_*/consumer.properties
Name: "brand1"
Name: "brand2"
Name: "brand_3"
Is this what you're looking for?

How to use template module with different set of variables?

My use case is the following :
I have a template file, and I would like to create 2 different files from that template, with the variables being filled by a different set of variables for each file.
For example, lets say I want to template the file containing the line:
mkdir -p {{myTemplateVariable}}
I would like to find a proper way to get this variable filled by "File1" and "File2". Something like :
- name: template test 1
template:
src=myTemplateFile
dest=result1
- name: template test 2
template:
src=myTemplateFile
dest=result2
where I could specify for the first templating that the variable to use is a = "File1" and for the second, b = "File2".
With Ansible 2.x you can use vars: with tasks.
Template test.j2:
mkdir -p {{myTemplateVariable}}
Playbook:
- template: src=test.j2 dest=/tmp/File1
vars:
myTemplateVariable: myDirName
- template: src=test.j2 dest=/tmp/File2
vars:
myTemplateVariable: myOtherDir
This will pass different myTemplateVariable values into test.j2.
For Ansible 2.x:
- name: template test
template:
src: myTemplateFile
dest: result1
vars:
myTemplateVariable: File1
- name: template test
template:
src: myTemplateFile
dest: result2
vars:
myTemplateVariable: File2
For Ansible 1.x:
Unfortunately the template module does not support passing variables to it, which can be used inside the template. There was a feature request but it was rejected.
I can think of two workarounds:
1. Include
The include statement supports passing variables. So you could have your template task inside an extra file and include it twice with appropriate parameters:
my_include.yml:
- name: template test
template:
src=myTemplateFile
dest=destination
main.yml:
- include: my_include.yml destination=result1 myTemplateVariable=File1
- include: my_include.yml destination=result2 myTemplateVariable=File2
2. Re-define myTemplateVariable
Another way would be to simply re-define myTemplateVariable right before every template task.
- set_fact:
myTemplateVariable: File1
- name: template test 1
template:
src=myTemplateFile
dest=result1
- set_fact:
myTemplateVariable: File2
- name: template test 2
template:
src=myTemplateFile
dest=result2
You can do this very easy, look my Supervisor recipe:
- name: Setup Supervisor jobs files
template:
src: job.conf.j2
dest: "/etc/supervisor/conf.d/{{ item.job }}.conf"
owner: root
group: root
force: yes
mode: 0644
with_items:
- { job: bender, arguments: "-m 64", instances: 3 }
- { job: mailer, arguments: "-m 1024", instances: 2 }
notify: Ensure Supervisor is restarted
job.conf.j2:
[program:{{ item.job }}]
user=vagrant
command=/usr/share/nginx/vhosts/parclick.com/app/console rabbitmq:consumer {{ item.arguments }} {{ item.job }} -e prod
process_name=%(program_name)s_%(process_num)02d
numprocs={{ item.instances }}
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/{{ item.job }}.stderr.log
stdout_logfile=/var/log/supervisor/{{ item.job }}.stdout.log
Output:
TASK [Supervisor : Setup Supervisor jobs files] ********************************
changed: [loc.parclick.com] => (item={u'instances': 3, u'job': u'bender', u'arguments': u'-m 64'})
changed: [loc.parclick.com] => (item={u'instances': 2, u'job': u'mailer', u'arguments': u'-m 1024'})
Enjoy!
This is a solution/hack I'm using:
tasks/main.yml:
- name: parametrized template - a
template:
src: test.j2
dest: /tmp/templateA
with_items: var_a
- name: parametrized template - b
template:
src: test.j2
dest: /tmp/templateB
with_items: var_b
vars/main.yml
var_a:
- 'this is var_a'
var_b:
- 'this is var_b'
templates/test.j2:
{{ item }}
After running this, you get this is var_a in /tmp/templateA and this is var_b in /tmp/templateB.
Basically you abuse with_items to render the template with each item in the one-item list. This works because you can control what the list is when using with_items.
The downside of this is that you have to use item as the variable name in you template.
If you want to pass more than one variable this way, you can dicts as your list items like this:
var_a:
-
var_1: 'this is var_a1'
var_2: 'this is var_a2'
var_b:
-
var_1: 'this is var_b1'
var_2: 'this is var_b2'
and then refer to them in your template like this:
{{ item.var_1 }}
{{ item.var_2 }}
I did it in this way.
In tasks/main.yml
- name: template test
template:
src=myTemplateFile.j2
dest={{item}}
with_dict: some_dict
and in vars/main.yml
some_dict:
/path/to/dest1:
var1: 1
var2: 2
/path/to/dest2:
var1: 3
var2: 4
and in templates/myTemplateFile.j2
some_var = {{ item.value.var1 }}
some_other_var = {{ item.value.var2 }}
Hope this solves your problem.
I had a similar problem to solve, here is a simple solution of how to pass variables to template files, the trick is to write the template file taking advantage of the variable. You need to create a dictionary (list is also possible), which holds the set of variables corresponding to each of the file. Then within the template file access them.
see below:
the template file: test_file.j2
# {{ ansible_managed }} created by xbalaji#gmail.com
{% set dkey = (item | splitext)[0] %}
{% set fname = test_vars[dkey].name %}
{% set fip = test_vars[dkey].ip %}
{% set fport = test_vars[dkey].port %}
filename: {{ fname }}
ip address: {{ fip }}
port: {{ fport }}
the playbook
---
#
# file: template_test.yml
# author: xbalaji#gmail.com
#
# description: playbook to demonstrate passing variables to template files
#
# this playbook will create 3 files from a single template, with different
# variables passed for each of the invocation
#
# usage:
# ansible-playbook -i "localhost," template_test.yml
- name: template variables testing
hosts: all
gather_facts: false
vars:
ansible_connection: local
dest_dir: "/tmp/ansible_template_test/"
test_files:
- file_01.txt
- file_02.txt
- file_03.txt
test_vars:
file_01:
name: file_01.txt
ip: 10.0.0.1
port: 8001
file_02:
name: file_02.txt
ip: 10.0.0.2
port: 8002
file_03:
name: file_03.txt
ip: 10.0.0.3
port: 8003
tasks:
- name: copy the files
template:
src: test_file.j2
dest: "{{ dest_dir }}/{{ item }}"
with_items:
- "{{ test_files }}"
- name: copy vhosts
template: src=site-vhost.conf dest=/etc/apache2/sites-enabled/{{ item }}.conf
with_items:
- somehost.local
- otherhost.local
notify: restart apache
IMPORTANT: Note that an item does not have to be just a string, it can be an object with as many properties as you like, so that way you can pass any number of variables.
In the template I have:
<VirtualHost *:80>
ServerAdmin me#example.org
ServerName {{ item }}
DocumentRoot /vagrant/public
ErrorLog ${APACHE_LOG_DIR}/error-{{ item }}.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Another real world example using a list
an extract for a template for php.ini
{% if 'cli/php.ini' in item.d %}
max_execution_time = 0
memory_limit = 1024M
{% else %}
max_execution_time = 300
memory_limit = 512M
{% endif %}
This is the var
php_templates:
- { s: 'php.ini.j2', d: "/etc/php/{{php_version}}/apache2/php.ini" }
- { s: 'php.ini.j2', d: "/etc/php/{{php_version}}/cli/php.ini" }
Then i deploy with this
- name: push templated files
template:
src: "{{item.s}}"
dest: "{{item.d}}"
mode: "{{item.m | default(0644) }}"
owner: "{{item.o | default('root') }}"
group: "{{item.g | default('root') }}"
backup: yes
with_items: "{{php_templates}}"

Resources