Calling roles in a loop using dictionaries from an array as vars - ansible

My question is somehow similar to the one posted here, but that doesn't quite answer it.
In my case I have an array containing multiple vars: entries, which I loop over when calling a certain role. The following examples shows the idea:
some_vars_file.yml:
redis_config:
- vars:
redis_version: 6.0.6
redis_port: 6379
redis_bind: 127.0.0.1
redis_databases: 1
- vars:
redis_version: 6.0.6
redis_port: 6380
redis_bind: 127.0.0.1
redis_databases: 1
playbook.yml:
...
- name: Install and setup redis
include_role:
name: davidwittman.redis
with_dict: "{{ dictionary }}"
loop: "{{ redis_config }}"
loop_control:
loop_var: dictionary
...
As far as I understand, this should just set the dictionary beginning with the vars node on every iteration, but it somehow doesn't. Is there any chance to get something like this to work, or do I really have to redefine all properties at the role call, populating them using with_items?

Given the role
shell> cat roles/davidwittman_redis/tasks/main.yml
- debug:
var: dictionary
Remove with_dict. The playbook
shell> cat playbook.yml
- hosts: localhost
vars_files:
- some_vars_file.yml
tasks:
- name: Install and setup redis
include_role:
name: davidwittman_redis
loop: "{{ redis_config }}"
loop_control:
loop_var: dictionary
gives
shell> ansible-playbook playbook.yml
PLAY [localhost] **********************************************
TASK [Install and setup redis] ********************************
TASK [davidwittman_redis : debug] *****************************
ok: [localhost] =>
dictionary:
vars:
redis_bind: 127.0.0.1
redis_databases: 1
redis_port: 6379
redis_version: 6.0.6
TASK [davidwittman_redis : debug] ******************************
ok: [localhost] =>
dictionary:
vars:
redis_bind: 127.0.0.1
redis_databases: 1
redis_port: 6380
redis_version: 6.0.6
Q: "Might there be an issue related to the variable population on role call?"
A: Yes. It can. See Variable precedence. vars_files is precedence 14. Any higher precedence will override it. Decide how to structure the data and optionally use include_vars (precedence 18). For example
shell> cat playbook.yml
- hosts: localhost
tasks:
- include_vars: some_vars_file.yml
- name: Install and setup redis
include_role:
name: davidwittman_redis
loop: "{{ redis_config }}"
loop_control:
loop_var: dictionary
Ultimately, command line --extra-vars would override all previous settings
shell> ansible-playbook playbook.yml --extra-vars "#some_vars_file.yml"
Q: "Maybe it is not possible to set the vars section directly via an external dictionary?"
A: It is possible, of course. The example in this answer clearly proves it.

Related

Is there a way to re-set the "hosts" variable within a playbook?

I am tasked with creating a playbook, where I perform the following operations:
Fetch information from a YAML file (the file contains details on VLANs)
Cycle through the YAML objects and verify which subnet contains the IP, then return the object
The object contains also a definition of the inventory_hostname where to run the Ansible playbook
At the moment, I have the following (snippet):
Playbook:
- name: "Playbook"
gather_facts: false
hosts: "localhost"
tasks:
- name: "add host"
add_host:
name: "{{ vlan_target }}"
- name: "debug"
debug:
msg: "{{ inventory_hostname }}"
The defaults file defines the target_host as an empty string "" and then it is evaluated within another role task, like so (snippet):
Role:
- set_fact:
vlan_object: "vlan | trim | from_json | first"
- name: "set facts based on IP address"
set_fact:
vlan_name: "{{ vlan_object.name }}"
vlan_target: "{{ vlan_object.target }}"
delegate_to: localhost
What I am trying to achieve is to change the hosts: variable so that I can use the right target, since the IP/VLAN should reside on a specific device.
I have tried to put the aforementioned task above the add_host, or even putting in the same playbook, like so:
- name: "Set_variables"
hosts: "localhost"
tasks:
- name: "Set target host"
import_role:
name: test
tasks_from: target_selector
- name: "Playbook"
gather_facts: false
hosts: "localhost"
Adding a debug clause to the above playbook sets the right target, but it is not re-used below, making me think that the variable is not set globally, but within that run.
I am looking for a way to set the target, based on a variable that I am passing to the playbook.
Does anyone have any experience with this?
Global facts are not a thing, at best you can assign a fact to all hosts in the play, but since you are looking to use the said fact to add an host, this won't be a solution for your use case.
You can access facts of another hosts via the hostvars special variable, though. It is a dictionary where the keys are the names of the hosts.
The usage of the role is not relevant to your issue at hand, so, in the demonstration below, let's put this aside.
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
## fake object, since we don't have the structure of your JSON
vlan_object:
name: foo
target: bar
- set_fact:
vlan_name: "{{ vlan_object.name }}"
vlan_target: "{{ vlan_object.target }}"
run_once: true
- add_host:
name: "{{ vlan_target }}"
- hosts: "{{ hostvars.localhost.vlan_target }}"
gather_facts: no
tasks:
- debug:
var: ansible_play_hosts
This would yield
PLAY [localhost] *************************************************************
TASK [set_fact] **************************************************************
ok: [localhost]
TASK [set_fact] **************************************************************
ok: [localhost]
TASK [add_host] **************************************************************
changed: [localhost]
PLAY [bar] *******************************************************************
TASK [debug] *****************************************************************
ok: [bar] =>
ansible_play_hosts:
- bar

ansible, execute a task only if another specific host is also in the playbook run

I have a little problem I can't solve. I am writing an update/reboot playbook for my Linux servers and I want to make sure that a task is executed only if another host is in the same playbook run
for example:
stop app on app server when the database server is going to be rebooted
- hosts: project-live-app01
tasks:
- name: stop app before rebooting db servers
systemd:
name: app
state: stopped
when: <<< project-live-db01 is in this ansible run >>>
- hosts: dbservers
serial: 1
tasks:
- name: Unconditionally reboot the machine with all defaults
reboot:
post_reboot_delay: 20
msg: "Reboot initiated by Ansible"
- hosts: important_servers:!dbservers
serial: 1
tasks:
- name: Unconditionally reboot the machine with all defaults
reboot:
post_reboot_delay: 20
msg: "Reboot initiated by Ansible"
I want to use the same playbook to reboot hosts and If i --limit the execution to only some hosts and especially not the dbserver then I don't want to have the app stopped. I try to create a generic playbook for all my projects, which only executes tasks if certain servers are affected by the playbook run.
Is there any way for that?
thank you and have a great day!
cheers, Ringo
It would be possible to create a dictionary with the structure of the project, e.g. in group_vars
shell> cat group_vars/all
project_live:
app01:
dbs: [db01, db09]
app02:
dbs: [db02, db09]
app03:
dbs: [db03, db09]
Put all groups of the DB servers you want to use into the inventory, e.g.
shell> cat hosts
[dbserversA]
db01
db02
[dbserversB]
db02
db03
[dbserversC]
db09
Then the playbook below demonstrates the scenario
shell> cat playbook.yml
---
- name: Stop app before rebooting db servers
hosts: localhost
gather_facts: false
tasks:
- debug:
msg: "Stop app on {{ item.key }}"
loop: "{{ project_live|dict2items }}"
when: item.value.dbs|intersect(groups[dbservers])|length > 0
- name: Reboot db servers
hosts: "{{ dbservers }}"
gather_facts: false
tasks:
- debug:
msg: "Reboot {{ inventory_hostname }}"
For example
shell> ansible-playbook -i hosts playbook.yml -e dbservers=dbserversA
PLAY [Stop app before rebooting db servers] ***********************
msg: Stop app on app01
msg: Stop app on app02
PLAY [Reboot db servers] *******************************************
msg: Reboot db01
msg: Reboot db02
How can I stop services on app* when the play is running on the localhost? Either use delegate_to, or create dynamic group by add_host and use it in the next play, e.g.
shell cat playbook.yml
---
- name: Create group appX
hosts: localhost
gather_facts: false
tasks:
- add_host:
name: "{{ item.key }}"
groups: appX
loop: "{{ project_live|dict2items }}"
loop_control:
label: "{{ item.key }}"
when: item.value.dbs|intersect(groups[dbservers])|length > 0
- name: Stop app before rebooting db servers
hosts: appX
gather_facts: false
tasks:
- debug:
msg: "Stop app on {{ inventory_hostname }}"
- name: Reboot db servers
hosts: "{{ dbservers }}"
gather_facts: false
tasks:
- debug:
msg: "Reboot {{ inventory_hostname }}"
gives
shell> ansible-playbook -i hosts playbook.yml -e dbservers=dbserversA
PLAY [Create group appX] ******************************************
ok: [localhost] => (item=app01)
ok: [localhost] => (item=app02)
skipping: [localhost] => (item=app03)
PLAY [Stop app before rebooting db servers] ************************
msg: Stop app on app01
msg: Stop app on app02
PLAY [Reboot db servers] *******************************************
msg: Reboot db01
msg: Reboot db02

How can I specify hosts when importing a playbook

ansible version: 2.9
Hi.
How can I specify the hosts when I import a playbook with import_playbook?
My code (/project/first_pb.yml)
- import_playbook: /test/pb0.yml
hosts: atlanta
Q: "A method to pass specific family hosts to the imported playbook?"
A: There is no difference between a playbook imported or not. For example,
shell> cat pb-A.yml
- hosts: "{{ my_hosts|default('localhost') }}"
tasks:
- debug:
var: inventory_hostname
shell> ansible-playbook pb-A.yml -e my_hosts=host1
...
inventory_hostname: host1
shell> cat pb-B.yml
- import_playbook: pb-A.yml
shell> ansible-playbook pb-B.yml -e my_hosts=host1
...
inventory_hostname: host1
There are many options on how to pass specific hosts and groups to a playbook. For example, see:
Patterns: targeting hosts and groups
add_host module – Add a host and group to the ansible-playbook in-memory inventory
Inventory plugins (e.g. constructed)
I can filter play plabooks with "when: " condition, for example:
- import_playbook: /test/pb0.yml
when: hostname != host1a*
- import_playbook: /test/pb0.yml
when: '"north" not in hostname'
- import_playbook: /test/pb0.yml
when: '"west" in hostname'

Ansible - dynamic variable with host_vars and string in the name

I'm trying to create a job role in Ansible to run yum install/update of packages, which will be provided by a 3rd party system as a .yml file to vars directory in a role with following convention: server01.yml, server02.yml, serverX.yml with variable in form packageList_serverNumber: 'list of packages'.
This variable will be read using a task:
- name: server update packages from host_vars
yum:
name: "{{ install_pkgs }}"
state: latest
This should point to host_vars file for specific host:
install_pkgs: "{{ packageList_server01 }}"
As this task should only run when the variable is defined, I was trying to use when clause with variable which will point to packageList_serverNumber. When I hardcode it, like below it is working:
when: packageList_server01 is defined
Can you please advise how to make it dynamic?
I was trying with:
when: packageList_{{hostvars[inventory_hostname]}} is defined
But unfortunately this is not working.
Use lookup plugin vars. Run the command below to see the details
shell> ansible-doc -t lookup vars
Given the vars files
shell> cat roles/test4/vars/server01.yml
packageList_server01: [pkg1, pkg2, pkg3]
shell> cat roles/test4/vars/server02.yml
packageList_server02: [pkg4, pkg5, pkg6]
shell> cat roles/test4/vars/server03.yml
packageList_server03: [pkg7, pkg8, pkg9]
read the vars, declare the variable install_pkgs, and use it
shell> cat roles/test4/tasks/main.yml
- include_vars: "vars/{{ inventory_hostname }}.yml"
- set_fact:
install_pkgs: "{{ lookup('vars', 'packageList_' ~ inventory_hostname) }}"
- debug:
msg: "Install {{ install_pkgs }}"
For example the playbook
- hosts: server01,server02,server03
gather_facts: false
roles:
- test4
gives (abridged)
TASK [test4 : debug] ****
ok: [server01] =>
msg: Install ['pkg1', 'pkg2', 'pkg3']
ok: [server03] =>
msg: Install ['pkg7', 'pkg8', 'pkg9']
ok: [server02] =>
msg: Install ['pkg4', 'pkg5', 'pkg6']

Run ansible task only once per each unique fact value

I have a dynamic inventory that assigns a "fact" to each host, called a 'cluster_number'.
The cluster numbers are not known in advance, but there is one or more hosts that are assigned the same number. The inventory has hundreds of hosts and 2-3 dozen unique cluster numbers.
I want to run a task for all hosts in the inventory, however I want to execute it only once per each group of hosts sharing the same 'cluster_number' value. It does not matter which specific host is selected for each group.
I feel like there should be a relatively straight forward way to do this with ansible, but can't figure out how. I've looked at group_by, when, loop, delegate_to etc. But no success yet.
An option would be to
group_by the cluster_number
run_once a loop over cluster numbers
and pick the first host from each group.
For example given the hosts
[test]
test01 cluster_number='1'
test02 cluster_number='1'
test03 cluster_number='1'
test04 cluster_number='1'
test05 cluster_number='1'
test06 cluster_number='2'
test07 cluster_number='2'
test08 cluster_number='2'
test09 cluster_number='3'
test10 cluster_number='3'
[test:vars]
cluster_numbers=['1','2','3']
the following playbook
- hosts: all
gather_facts: no
tasks:
- group_by: key=cluster_{{ cluster_number }}
- debug: var=groups['cluster_{{ item }}'][0]
loop: "{{ cluster_numbers }}"
run_once: true
gives
> ansible-playbook test.yml | grep groups
"groups['cluster_1'][0]": "test01",
"groups['cluster_2'][0]": "test06",
"groups['cluster_3'][0]": "test09",
To execute tasks at the targets include_tasks (instead of debug in the loop above) and delegate_to the target
- set_fact:
my_group: "cluster_{{ item }}"
- command: hostname
delegate_to: "{{ groups[my_group][0] }}"
Note: Collect the list cluster_numbers from the inventory
cluster_numbers: "{{ hostvars|json_query('*.cluster_number')|unique }}"
If you don't mind play logs cluttering, here's a way:
- hosts: all
gather_facts: no
serial: 1
tasks:
- group_by:
key: "single_{{ cluster_number }}"
when: groups['single_'+cluster_number] | default([]) | count == 0
- hosts: single_*
gather_facts: no
tasks:
- debug:
msg: "{{ inventory_hostname }}"
serial: 1 is crucial in the first play to reevaluate when statement on for every host.
After first play you'll have N groups for each cluster with only single host in them.

Resources