Rendering different template to each host in Ansible - ansible

Suppose I have a template file, containing, among others, a /24 IP range:
...
podCIDR: 192.168.<x>.0/24
...
Now, using Ansible, I want to render this template, with a running number from 1 to the number of hosts in my inventory, so that each host will have a different range. The first will have 192.168.1.0/24, the second 192.168.2.0/24, etc.
How do I do this?

Declare the index variable in the group_vars/all and use it to create the network
shell> cat group_vars/all
my_index1: "{{ ansible_play_hosts_all.index(inventory_hostname) + 1 }}"
podCIDR: "192.168.{{ my_index1 }}.0/24"
Then, given the inventory
shell> cat hosts
host_1
host_2
host_3
the playbook below
shell> cat pb.yml
- hosts: all
tasks:
- debug:
var: my_index1
- debug:
var: podCIDR
gives abridged
TASK [debug] *********************************************************************************
ok: [host_1] =>
my_index1: '1'
ok: [host_2] =>
my_index1: '2'
ok: [host_3] =>
my_index1: '3'
TASK [debug] *********************************************************************************
ok: [host_1] =>
podCIDR: 192.168.1.0/24
ok: [host_2] =>
podCIDR: 192.168.2.0/24
ok: [host_3] =>
podCIDR: 192.168.3.0/24
See:
Special Variables
Variable precedence: Where should I put a variable?
Finding the index of an item in a list

Related

Ansible get all hosts in subgroup

I have the following hosts structure in the inventory:
all:
children:
sc:
hosts:
sc-finder01a.com:
sc-finder01b.com:
vars:
default_port: 5679
version: 0.4.2-RELEASE
ms:
hosts:
ms-finder01a.com:
ms-finder01a.com:
vars:
default_port: 5679
version: 0.4.2-RELEASE
I'm running on all hosts, where for each host I'd like to access the other one in the subgroup (sc/ms) in order to check a condition before executing it on the current host, but I'm struggling to find the syntax. Also, I have to prevent Ansible from executing the command on two hosts in the same subgroup in parallel.
Ideas?
Have a look at ansible's special variables. For controlling execution, check playbook strategies.
You can access the current host's groups with the group_names variable. If you want to be in control of host execution order, delegate_to and run_once may help, where you assign a run_once controlling server and delegate tasks looping over the group members list.
Q: "Access the other hosts in the subgroup (sc/ms) in order to check a condition before executing it on the current host."
A: Iterate the list of the hosts in the subgroup(s), e.g.
- debug:
msg: "Check a condition on {{ item }}"
loop: "{{ group_names|map('extract', groups)|flatten|unique|
difference([inventory_hostname]) }}"
gives
TASK [debug] *******************************************************
ok: [sc-finder01b.com] => (item=sc-finder01a.com) =>
msg: Check a condition on sc-finder01a.com
ok: [sc-finder01a.com] => (item=sc-finder01b.com) =>
msg: Check a condition on sc-finder01b.com
ok: [ms-finder01b.com] => (item=ms-finder01a.com) =>
msg: Check a condition on ms-finder01a.com
ok: [ms-finder01a.com] => (item=ms-finder01b.com) =>
msg: Check a condition on ms-finder01b.com
Debug
The inventory
shell> cat hosts
all:
children:
sc:
hosts:
sc-finder01a.com:
sc-finder01b.com:
vars:
default_port: 5679
version: 0.4.2-RELEASE
ms:
hosts:
ms-finder01a.com:
ms-finder01b.com:
vars:
default_port: 5679
version: 0.4.2-RELEASE
Display all hosts
- debug:
var: groups
run_once: true
gives
TASK [debug] ***************************************************************
ok: [sc-finder01a.com] =>
groups:
all:
- sc-finder01a.com
- sc-finder01b.com
- ms-finder01a.com
- ms-finder01b.com
ms:
- ms-finder01a.com
- ms-finder01b.com
sc:
- sc-finder01a.com
- sc-finder01b.com
ungrouped: []
Multiple subgroups
The code works also if a host is a member of multiple subgroups, e.g.
shell> cat hosts
all:
children:
sc:
hosts:
sc-finder01a.com:
ms:
hosts:
ms-finder01a.com:
foo:
hosts:
foo-bar.com:
sc-finder01a.com:
ms-finder01a.com:
gives
TASK [debug] ******************************************************
ok: [sc-finder01a.com] => (item=foo-bar.com) =>
msg: Check a condition on foo-bar.com
ok: [sc-finder01a.com] => (item=ms-finder01a.com) =>
msg: Check a condition on ms-finder01a.com
ok: [foo-bar.com] => (item=sc-finder01a.com) =>
msg: Check a condition on sc-finder01a.com
ok: [ms-finder01a.com] => (item=foo-bar.com) =>
msg: Check a condition on foo-bar.com
ok: [foo-bar.com] => (item=ms-finder01a.com) =>
msg: Check a condition on ms-finder01a.com
ok: [ms-finder01a.com] => (item=sc-finder01a.com) =>
msg: Check a condition on sc-finder01a.com

Running ansible playbook in serial over hostgroups

Im trying to run a playbook for n host groups serially (but 100% parallel within the host group). How do I achieve this?
I've tried things like:
- name: Test
hosts: group1:group2
serial: 100%
and even
- name: Test
hosts: group1:group2
serial: 1
Thinking it would do group by group, however these do not work.
How do I get it to run over all of group1, then after, all of group2 (but fail if anything in group1 fails)?
Also, how do I get it to run over n groups? (There are many hostgroups, which might be tough to define in the hosts key)
You can't control a playbook from another playbook. You'll have to control the playbook from outside, for example by a script. Given the inventory
shell> cat hosts-497
[group1]
srv1
[group2]
srv2
srv3
[group3]
srv4
srv5
srv6
and the playbook
shell> cat test-497.yml
- name: Test
hosts: all
gather_facts: false
tasks:
- debug:
msg: "{{ '%H:%M:%S'|strftime }}: {{ inventory_hostname }}"
the debug task is executed in parallel by all hosts
shell> ansible-playbook -i hosts-497 test-497.yml
PLAY [Test] ***************************************************************
TASK [debug] **************************************************************
ok: [srv3] =>
msg: '20:51:30: srv3'
ok: [srv1] =>
msg: '20:51:30: srv1'
ok: [srv4] =>
msg: '20:51:30: srv4'
ok: [srv2] =>
msg: '20:51:30: srv2'
ok: [srv5] =>
msg: '20:51:30: srv5'
ok: [srv6] =>
msg: '20:51:30: srv6'
If you want to control the hosts create a script and iterate the groups, e.g.
shell> cat test-497.sh
#!/usr/bin/sh
for i in group1 group2 group3; do
ansible-playbook -i hosts-497 --limit $i test-497.yml
done
gives (abridged)
shell> ./test-497.sh
PLAY [Test] *************************************************************
TASK [debug] ************************************************************
ok: [srv1] =>
msg: '20:56:41: srv1'
PLAY [Test] *************************************************************
TASK [debug] ************************************************************
ok: [srv3] =>
msg: '20:56:45: srv3'
ok: [srv2] =>
msg: '20:56:45: srv2'
PLAY [Test] *************************************************************
TASK [debug] ************************************************************
ok: [srv5] =>
msg: '20:56:52: srv5'
ok: [srv6] =>
msg: '20:56:52: srv6'
ok: [srv4] =>
msg: '20:56:53: srv4'

How to skip a hostname from ansible inventory file?

My ansible inventory file has the following entry
[non_prod_servers]
oracle[1:13]
How can I eliminate hosts "oracle7" and "oracle10" from the above specification, without having to create the following entries?
[non_prod_servers]
oracle[1:6]
oracle[8:9]
oracle[11:13]
Essentially, looking for an elegant solution than what I have come up with.
Thanks in advance.
FR
IMHO, the functionality, you're looking for, is not available. You can use Python slicing inside the inventory. The inventory patterns do not apply inside the inventory file.
If you need it for automation, i.e. you want to control the process by a couple of variables the inventory and playbook below create the group dynamically
shell> cat hosts
[non_prod_servers]
localhost
[non_prod_servers:vars]
_name=oracle
_from=1
_to=13
_deny=[7,10]
shell> cat playbook.yml
---
- hosts: non_prod_servers
gather_facts: false
tasks:
- add_host:
hostname: "{{ _name }}{{ item }}"
groups: non_prod_servers_dyn
loop: "{{ range(_from, _to + 1)|difference(_deny) }}"
- hosts: non_prod_servers_dyn
gather_facts: false
tasks:
- debug:
var: ansible_play_hosts_all
run_once: true
gives
shell> ansible-playbook -i hosts playbook.yml
PLAY [non_prod_servers] *************************************************
TASK [add_host] *********************************************************
ok: [localhost] => (item=1)
ok: [localhost] => (item=2)
ok: [localhost] => (item=3)
ok: [localhost] => (item=4)
ok: [localhost] => (item=5)
ok: [localhost] => (item=6)
ok: [localhost] => (item=8)
ok: [localhost] => (item=9)
ok: [localhost] => (item=11)
ok: [localhost] => (item=12)
ok: [localhost] => (item=13)
PLAY [non_prod_servers_dyn] **********************************************
TASK [debug] *************************************************************
ok: [oracle1] =>
ansible_play_hosts_all:
- oracle1
- oracle2
- oracle3
- oracle4
- oracle5
- oracle6
- oracle8
- oracle9
- oracle11
- oracle12
- oracle13

Executing same task on two different inventory groups based on conditionals

I have a task in Ansible which needs to be executed on two different inventory groups based on certain condition.
Say if a specific flag is set, I want to execute it on one host. If the flag is disabled, it needs to be executed on the other. Is it possible to use conditionals for determining inventory set in which the task should run in Ansible?
Inventories:
[group_A]
A
a
[group_B]
B
b
Task:
-name: Stop component
roles:
-stop
hosts:
-group_A, when: flag|bool
-group_B, when: not flag|bool
Thanks in advance!
For example
shell> cat playbook
- hosts: "{{ flag|default(true)|bool|ternary('group_A', 'group_B') }}"
tasks:
- debug:
var: inventory_hostname
gives
shell> ansible-playbook -e flag=True playbook.yml
ok: [A] =>
inventory_hostname: A
ok: [a] =>
inventory_hostname: a
shell> ansible-playbook -e flag=False playbook.yml
ok: [B] =>
inventory_hostname: B
ok: [b] =>
inventory_hostname: b

Ansible increment variable globally for all hosts

I have two servers in my inventory (hosts)
[server]
10.23.12.33
10.23.12.40
and playbook (play.yml)
---
- hosts: all
roles:
web
Inside web role in vars directory i have main.yml
---
file_number : 0
Inside web role in tasks directory i have main.yml
---
- name: Increment variable
set_fact: file_number={{ file_number | int + 1 }}
- name: create file
command: 'touch file{{ file_number }}'
Now i expect that in first machine i will have file1 and in second machine i will have file2 but in both machines i have file1
So this variable is local for every machine, how could i make it global for all machines.
My file structure is:
hosts
play.yml
roles/
web/
tasks/
main.yml
vars/
main.yml
Now i expect that in first machine i will have file1 and in second machine i will have file2 but in both machines i have file1
You need to keep in mind that variables in Ansible aren't global. Variables (aka 'facts') are applied uniquely to each host, so file_number for host1 is different than file_number for host2. Here's an example based loosely on what you posted:
roles/test/vars/main.yml:
---
file_number: 0
roles/test/tasks/main.yml:
---
- name: Increment variable
set_fact: file_number={{ file_number | int + 1 }}
- name: debug
debug: msg="file_number is {{ file_number }} on host {{ inventory_hostname }}"
Now suppose you have just two hosts defined, and you run this role multiple times in a playbook that looks like this:
---
- hosts: all
roles:
- { role: test }
- hosts: host1
roles:
- { role: test }
- hosts: all
roles:
- { role: test }
So in the first play the role is applied to both host1 & host2. In the second play it's only run against host1, and in the third play it's again run against both host1 & host2. The output of this playbook is:
PLAY [all] ********************************************************************
TASK: [test | Increment variable] *********************************************
ok: [host1]
ok: [host2]
TASK: [test | debug] **********************************************************
ok: [host1] => {
"msg": "file_number is 1 on host host1"
}
ok: [host2] => {
"msg": "file_number is 1 on host host2"
}
PLAY [host1] **************************************************
TASK: [test | Increment variable] *********************************************
ok: [host1]
TASK: [test | debug] **********************************************************
ok: [host1] => {
"msg": "file_number is 2 on host host1"
}
PLAY [all] ********************************************************************
TASK: [test | Increment variable] *********************************************
ok: [host1]
ok: [host2]
TASK: [test | debug] **********************************************************
ok: [host1] => {
"msg": "file_number is 3 on host host1"
}
ok: [host2] => {
"msg": "file_number is 2 on host host2"
}
So as you can see, the value of file_number is different for host1 and host2 since the role that increments the value ran against host1 more times than it did host2.
Unfortunately there really isn't a clean way making a variable global within Ansible. The entire nature of Ansible's ability to run tasks in parallel against large numbers of hosts makes something like this very tricky. Unless you're extremely careful with global variables in a parallel environment you can easily trigger a race condition, which will likely result in unpredictable (inconsistent) results.
I haven't found a solution with ansible although i made work around using shell to make global variable for all hosts.
Create temporary file in /tmp in localhost and place in it the starting count
Read the file for every host and increment the number inside the file
I created the file and initialized it in the playbook (play.yml)
- name: Manage localhost working area
hosts: 127.0.0.1
connection: local
tasks:
- name: Create localhost tmp file
file: path={{ item.path }} state={{ item.state }}
with_items:
- { path: '/tmp/file_num', state: 'absent' }
- { path: '/tmp/file_num', state: 'touch' }
- name: Managing tmp files
lineinfile: dest=/tmp/file_num line='0'
Then in web role in main.tml task i read the file and increment it.
- name: Get file number
local_action: shell file=$((`cat /tmp/file_num` + 1)); echo $file | tee /tmp/file_num
register: file_num
- name: Set file name
command: 'touch file{{ file_num.stdout }}'
Now i have in first host file1 and in second host file2
You can use Matt Martz's solution from here.
Basically your task would be like:
- name: Set file name
command: 'touch file{{ play_hosts.index(inventory_hostname) }}'
And you can remove all that code for maintaining global var and external file.

Resources