Loop variable is not appending file - ansible

When i use blockinfile loop to append /etc/environment file it only adds last key and value of item from loop variable rather then adding all of it.
I am trying to modify files using Blockinfile module in roles main.yml:
- name: Add proxy to global /etc/environments
blockinfile:
path: "/etc/environment"
block: |
export {{item.key}}={{item.value}}
loop: "{{proxy_details}}"
my vars/main.yaml looks like this:
proxy_details:
- key: http_proxy
value: "http://"{{ProxyHost}}":"{{ProxyPort}}""
- key: https_proxy
value: "http://"{{ProxyHost}}":"{{ProxyPort}}""
my group_vars/all looks like this:
ProxyHost: test.com
ProxyPort: 9999

See the last example in the documentaion at https://docs.ansible.com/ansible/latest/modules/blockinfile_module.html. You need to use a custom marker for each item so Ansible knows where each one is in the file to replace it.
Per documentation note:
When using ‘with_*’ loops be aware that if you do not set a unique mark the block will be overwritten on each iteration.
The example is:
- name: Add mappings to /etc/hosts
blockinfile:
path: /etc/hosts
block: |
{{ item.ip }} {{ item.name }}
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.name }}"
loop:
- { name: host1, ip: 10.10.1.10 }
- { name: host2, ip: 10.10.1.11 }
- { name: host3, ip: 10.10.1.12 }
You could modify yours to be:
- name: Add proxy to global /etc/environments
blockinfile:
path: "/etc/environment"
marker: "# {mark} ANSIBLE MANAGED BLOCK FOR {{item.key}}"
block: |
export {{item.key}}={{item.value}}
loop: "{{proxy_details}}"

Related

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

How to use ansible blockinfile on multiple files?

hi I would like to use the blockinfile function on multiple files going to change blocks of text in each of them.
unfortunately blockinfile does not support this function. can someone help me?
To use blockinfile on multiple files to change blocks of text ...
you might want to create templates and loop the blockinfile module.
- blockinfile:
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.template }}"
create: yes
path: "{{ item.file }}"
block: "{{ lookup('template', item.template) }}"
loop: "{{ files_templates }}"
You can do it like this:
- name: Add same block of text in multiple files/paths
blockinfile:
path: "{{ item.path }}"
marker: "###### {mark} Ansible Config #####"
insertafter: EOF
state: present
block: |
# Some random text comment
Some random command1
Some random command2
with_items:
- {path: '/your/path/one'}
- {path: '/your/path/two'}

Ansible blockinfile re-print

I am using Ansible's blockinfile feature to insert user's public keys into authorized_keys file. Keys are stored as variable in group_vars.
Inserting works fine, but is there a way to ask blockinfile to print blocks from the beginning every time? What I mean is, if I remove one key from variable, and run playbook it still exists in authorized file, because blockinfile is only printing once.
Can I make it print whole variable again everytime?
Playbook:
- name: Add root authorized keys
blockinfile:
state: present
path: /root/.ssh/authorized_keys
block: |
{{ item.key }} {{ item.label }}
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.label }}"
backup: yes
with_items: "{{ root_keys }}"
This is how variable looks like:
root_keys:
- { label: somelabel, key: somekey }
- { label: somelabel2, key: somekey2 }
So what I am trying to achieve is that when I remove somekey2 from root_keys variable, it will disappear from authorized_keys file.
Can this be done?
You should use a native Ansible authorized_key for these operations.
As for the question itself if you plan to reorganise root_keys in the following way:
root_keys:
- { label: somelabel, key: somekey }
- { label: somelabel2 }
You can redefine the task to:
- name: Add root authorized keys
blockinfile:
state: "{{ (item.key is defined ) | ternary('present', 'absent') }}"
path: /root/.ssh/authorized_keys
block: |
{{ item.key | default(omit) }} {{ item.label }}
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.label }}"
backup: yes
with_items: "{{ root_keys }}"
If you want to remove the whole element { label: somelabel2, key: somekey2 }, then you should store all possible label-values in a separate list and iterate over that list checking if an element is present in a union or difference of that all-attributes-list and root_keys|map(attribute='label') to determine if a value should be included or not;
A bad practice-variation would be to parse the file to create this list by parsing the destination file in search for ANSIBLE MANAGED BLOCK. But that, on the other hand would be the only reason you might want to use blockinfile instead of authorized_key module.

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

Ansible loop over variables

i am using ansible to update configuration file of newly added NIC
for that i have defined some variables in separate yml file
/tmp/ip.yml
#first interface
interface1: eth1
bootproto1: static
ipaddress1: 192.168.211.249
netmask1: 255.255.255.0
gateway: 192.168.211.2
DNS1: 192.168.211.2
#second interface
interface2: eth2
bootproto2: static
ipaddress2: 10.0.0.100
netmask2: 255.0.0.0
Playbook
- include_vars: /tmp/ip.yml
- name: configuring interface
lineinfile:
state=present
create=yes
dest=/etc/sysconfig/network-scripts/ifcfg-{{interface1}}
regexp="{{ item.regexp }}"
line="{{ item.line }}"
with_items:
- { regexp: '^BOOTPROTO=.*', line: 'BOOTPROTO={{interface1}}' }
- { regexp: '^IPADDR=.*', line: 'IPADDR={{ipaddress1}' }
- { regexp: '^NETMASK=.*', line: 'NETMASK={{netmask1}}' }
- { regexp: '^GATEWAY=.*', line: 'GATEWAY={{gateway}}' }
- { regexp: '^PEERDNS=.*', line: 'PEERDNS=no' }
- { regexp: '^DNS1=.*', line: 'DNS1={{DNS1}}' }
- { regexp: '^ONBOOT=.*', line: 'ONBOOT={{onboot}}' }
when: bootproto1 == 'static'
- name: configuring for DHCP
lineinfile:
state=present
create=yes
dest=/etc/sysconfig/network-scripts/ifcfg-{{interface1}}
regexp="{{ item.regexp }}"
line="{{ item.line }}"
with_items:
- { regexp: '^BOOTPROTO=.*',line: 'BOOTPROTO={{bootproto1}}' }
- {regexp: '^PEERDNS=.*',line: 'PEERDNS=yes' }
- { regexp: '^ONBOOT=.*', line: 'ONBOOT={{onboot}}' }
when: bootproto1 == 'dhcp'
similarly repeated for second interface.
Even Though this method works for 2 NIC,this is too difficult to manage ,that is for each new NIC added i need to modify playbook and update corresponding variable in /tmp/ip.yml.
Is there a way to add variables to /tmp/ip.yml and may be using some separator parse it to playbook with out modifying playbook each time for plugging in new NIC.
There is a lot to say here.
First, try to avoid lineinfile like plague. It is really a last-resort solution. lineinfile makes it hard to write consistent and idempotents playbooks.
Now, since you're trying to populate RH style interface files, it is quite easy to do.
Organize your variables
The first thing to do is to have a proper structure for your variables. You'll want to loop over your interfaces so you have to make stuff 'loopable'. Having interface1, interface2 ... interfaceN is not scalable as you mentioned.
Here is a suggestion :
interfaces_ipv4:
- name: eth0
bootproto: static
ipaddress: 192.168.211.249
netmask: 255.255.255.0
gateway: 192.168.211.2
dns: 192.168.211.2
- name: eth2
bootproto: static
ipaddress: 10.0.0.100
netmask: 255.0.0.0
Write your template
Now that you have your data, you need a template to create your OS config file.
BOOTPROTO={{item.bootproto}}
IPADDR={{item.ipaddress}}
NETMASK={{item.netmask}}
{% if item.gateway is defined %}
GATEWAY={{item.gateway}}
{% endif %}
PEERDNS=no
DNS1={{item.dns}}
ONBOOT={{item.onboot|default('no')}}
I included two variations : you can skip outputting a line when it's not set ({% if ... %} construct) or provide default values (for instance {{item.onboot|default('no')}}).
Your mileage may vay, depending if you want to use a default or to skip with the if construct.
Create a task
Finally, here is a task that will create interface configuration files for each interface :
- name: Push template
template:
src=/path/to/the/above/template.j2
dest=/etc/sysconfig/network-scripts/ifcfg-{{item.name}}.cfg
with_items:
- "{{ interfaces_ipv4 }}"
This should do it all.
Of course, best way to use this task is to add it to some "network" role, and call it from a playbook.
Good luck.

Resources