How do I create a playbook that compare the facts of multiple host in Ansible? I would like to be able to compare hosts against one another to discover one-offs. All my workstations should be the exact same.
- name: Gather Facts
hosts: all
- debug: var=vars
- debug: vars=hostvars[inventory_hostname]
- debug:
msg: "{{ hostvars[inventory_hostname] | difference([inventory_hostname]) }}""
Related
I have the ansible role coded below.
---
- name: Get host facts
set_fact:
serverdomain: "{{ansible_domain}}"
server_ip: "{{ansible_ip_addresses[1]}}"
- name: Host Ping Check
failed_when: false
win_ping:
register: var_ping
- name: Get Host name
debug: msg="{{the_host_name}}"
- name: Set Execution File and parameters
set_fact:
scriptfile: "{{ansible_user_dir}}\\scripts\\host_check.ps1"
params: "-servername '{{the_host_name}}' -response var_ping.failed"
- name: Execute script
win_command: powershell.exe "{{scriptfile}}" "{{params}}"
It works the way it should do but of course unreachable hosts are not touched at all. I would like to generate a list or is there a variable which contains all the unreachable hosts. Is it possible to have this in a comma delimmeted list and stored in a variable ?
Lastly, how/where do i need to set gather_facts: no. I tried several places to no avail.
EDIT 1
- name Unreachable servers
set_fact:
down: "{{ ansible_play_hosts_all | difference(ansible_play_hosts)}}"
- name: Set Execution File and parameters
set_fact:
scriptfile: "{{ansible_user_dir}}\\scripts\\host_check.ps1"
params: "-servername '{{the_host_name}}' -response var_ping.failed -unreachable_hosts {{ down }}"
- name: Execute script
win_command: powershell.exe "{{scriptfile}}" "{{params}}"
when: inventory_hostname == {{ db_server_host }}
Thanks for the answers, I have now been able to use thesame login in my ansible role, and it appears to work.
I do however have some questions. My playbook runs against hosts defined in my inventory, in this case what I want to achieve is a situation where the unreachable hosts is passed onto a powershell script right at the end and at once. So for example, 100 hosts, 10 were unreachable. The playbook should gather the 10 unreachable hosts, and pass the list of hosts in an array format to a powershell script or as a json data type.
All I want to do is be able to process the list of unreachable servers from my powershell script.
At the moment, I get
[
"server1",
"server2"
]
MY script will work with a format like this "server1","server2"
Lastly.The process of logging the unreachable servers at the end, only needs to happen once to a specific server which does the logging to a database. I created a task to do this as seen above.
db_server_host is being passed as a extra variables from ansible tower. The reason i added the when is that I only want it to run on the DB server and not on every host. I get the error The conditional check inventory_hostname == {{ db_server_host }} failed. The error was error while evaluating conditional inventory_hostname == {{ db_server_host }} 'mydatabaseServerName' is undefined
Q: "Get a list of unreachable hosts in an Ansible playbook."
Short answer: Create the difference between the lists below
down: "{{ ansible_play_hosts_all|difference(ansible_play_hosts) }}"
Details: Use Special Variables. Quoting:
ansible_play_hosts_all:
List of all the hosts that were targeted by the play.
ansible_play_hosts:
List of hosts in the current play run, not limited by the serial. Failed/Unreachable hosts are excluded from this list.
For example, given the inventory
shell> cat hosts
alpha
beta
charlie
delta
The playbook
- hosts: alpha,beta,charlie
gather_facts: true
tasks:
- block:
- debug:
var: ansible_play_hosts_all
- debug:
var: ansible_play_hosts
- set_fact:
down: "{{ ansible_play_hosts_all|difference(ansible_play_hosts) }}"
- debug:
var: down
run_once: true
gives (bridged) if alpha is unreachable (see below)
ansible_play_hosts_all:
- alpha
- beta
- charlie
ansible_play_hosts:
- beta
- charlie
down:
- alpha
Host alpha was unreachable
PLAY [alpha,beta,charlie] *************************************************
TASK [Gathering Facts] ****************************************************
fatal: [alpha]: UNREACHABLE! => changed=false
msg: 'Failed to connect to the host via ssh: ssh: connect to host test_14 port 22: No route to host'
unreachable: true
ok: [charlie]
ok: [beta]
...
Note
The dictionary hostvars keeps all hosts from the inventory, e.g.
- hosts: alpha,beta,charlie
gather_facts: true
tasks:
- debug:
var: hostvars.keys()
run_once: true
gives (abridged)
hostvars.keys():
- alpha
- beta
- charlie
- delta
tasks:
- ping:
register: ping_out
# one can include any kind of timeout or other stall related config here
- debug:
msg: |
{% for k in hostvars.keys() %}
{{ k }}: {{ hostvars[k].ping_out.unreachable|default(False) }}
{% endfor %}
yields (when an inventory consisting of only an alive alpha host):
msg: |-
alpha: False
beta: True
charlie: True
Lastly, how/where do i need to set gather_facts: no. I tried several places to no avail.
It only appears one time in the playbook keywords and thus it is a play keyword:
- hosts: all
gather_facts: no
tasks:
- name: now run "setup" for those who you wish to gather_facts: yes
setup:
when: inventory_host is awesome
I have the following playbook (playbook.yaml)
- hosts: myfirsthost[0]
tasks:
- name: Get a token
slurp:
src: /var/mytoken
register: tokenFile
- hosts: myotherhosts
vars:
fileToken: "{{ hostvars[groups['myfirsthost'][0]]['tokenFile']['content'] | b64decode | replace('\n', '') }}"
tasks:
- debug:
msg: The token {{fileToken}}
When I run it for all hosts, it works fine.
But when I run it against a single host contained in the group myotherhosts (not in group myfirsthosts)
ansible playbook.yaml --limit thesinglehost
It doesn't play the first task, then the variable can not be initialized, and that is expected.
Do you have any idea how I can force the task "Get a token" for all hosts, even if they are not in myfirsthost?
Thanks
The problem
When you use --limit in your ansible-playbook command, you cannot play any tasks on hosts out of this limit. This includes gathering facts (i.e. play the setup module automatically or explicitly) and set_fact (i.e. create/update a fact manually for the host). With ansible default settings (memory facts cache), you will not be able to query any hostvars on those hosts inside your playbook because there are no keys for their inventory_hostname in that dict.
Facts cache to the rescue
A solution is to enable a non ephemeral facts cache in ansible.cfg. The default cache goes to memory and dies at the end of the playbook.
Enable the cache
As a start, you can enable and store the cache in json files on disk with the following settings in ansible.cfg:
[defaults]
fact_caching = jsonfile
fact_caching_connection = /path/to/cache/folder
For more info about this feature and all the possible cache backends, you can look at the comments for the relevant parameters in the default ansible.cfg file and check the cache plugin documentation
Populate the cache
Once you have a non ephemeral cache, you can gather facts and set_fact for all relevant hosts. If you only need facts queried from the host, you can easily do this with an ad-hoc command:
ansible -i your/inventory my_hosts -m setup
In your case, it is a bit more complicated, as you want to push to the cache the result of a task. You will need to create a first playbook that you will run on all hosts you need to use later. I'll call it init_facts_and_tokens.yml:
---
- name: Gather hosts facts and initialize tokens
hosts: my_hosts
# You can uncomment the line below to be explicit but this is on by default
# gather_facts: true
tasks:
- name: Slurp token file token
slurp:
src: /var/mytoken
register: token_file
- name: Register token in facts cache
set_fact:
token: "{{ token_file.content | b64decode | replace('\n', '') }}"
and your run it on all hosts with
ansible-playbook -i your/inventory init_facts_and_tokens.yml
Use the cache
Now that the cache is populated, you can play your other playbook, with a limit or not, and call hostvars for hosts out of the play/limit. If their facts have been correctly cached, you will get the values queried during the last facts gathering or set_fact
In such a scenario, you can probably disable facts gathering on all your plays to save some execution time on your final playbook. And if for any reason (e.g. changing a network interface, adding an lvm volume.....) you need to refresh the facts during the play, you can simply run the setup module. For any other fact not queried from you host, any use of set_fact for the given variable will create/refresh its value
---
- name: Do the jobs with cache facts
hosts: my_hosts
gather_facts: false
vars:
# We will use toker from first server in group,
# even if out of the limit
this_play_token: "{{ hostvars[groups['my_hosts'][0]].token }}"
tasks:
- name: Show token
debug:
msg: "The token for this play is {{ this_play_token }}"
# Examples to illustrate above explanations
- name: This task can use cache
debug:
msg: "OS of somehost.com is: {{ hostvars['somehost.com'].ansible_os_familly }}"
- name: This task would change target, possibly obsoleting gathered facts
debug:
msg: "Warning I might change system facts !"
- name: Refresh facts cache
setup:
- name: Back to normal activity
debug:
msg: "I use latest gathered facts from cache"
- name: This would refresh the cached token for current host
set_fact:
token: "Not so valid token"
You can now launch this second playbook with a limit or not. It will still be able to read facts (queried or user set) for any host even out of the play
# run on all hosts
ansible-playbook -i your/inventory final_playbook.yml
# run only on third host of my_hosts group
ansible-playbook -i your/inventory --limit my_host[2] final_playbook.yml
How can I print variables declared only at group_vars, host_vars without ansible facts?
such code is good:
- name: "Ansible | List all known variables and facts"
debug:
var: hostvars[inventory_hostname]
But I don't need host IPs,disks, etc.
I mean to check all my variables one more time before continue to execute Play.
There are 3 categories of variables: ansible facts, special variables, and user's variables. Remove both ansible facts and special variables from hostvars and what is left are user's variables. The list of the ansible facts is available in the variables ansible_facts. The list of the special variables must be created (I think).
Create a list of special variables
If you run the playbook below you'll see the list of the special variables and user's variables
- hosts: localhost
tasks:
- debug:
msg: "{{ hostvars[inventory_hostname]|
difference(ansible_facts) }}"
Eliminate the user's vars and put the list of the special variables into a file. For example
shell> cat special_vars.yml
special_vars:
- ansible_python_interpreter
- ansible_connection
- inventory_hostname
...
This list of special variables might be not complete and will serve the purpose of this host only.
Remove ansible facts and special variables from hostvars
- hosts: localhost
vars_files:
- special_vars.yml
tasks:
- set_fact:
user_var1: AAA
- debug:
msg: "{{ hostvars[inventory_hostname]|
difference(ansible_facts)
difference(special_vars) }}"
gives the list of user's variables only
msg:
- user_var1
The user's variables will include also the configuration variables set by the user (e.g. connection variables: ansible_user or priviledge escalation: ansible_become).
Name-space
A better practice is to "name-space" variables. For example
- hosts: localhost
vars:
prj51_var1: AAA
prj51_var2: BBB
tasks:
- debug:
msg: "{{ item }}: {{ query('vars', item)|first }}"
loop: "{{ query('varnames', 'prj51_.+$') }}"
gives
msg: 'prj51_var1: AAA'
msg: 'prj51_var2: BBB'
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.
I'm trying to look for a text pattern in a load balancer host from a worker host, using the following:
- name: A play
hosts: workers
tasks:
- name: Look for text pattern in delegated host
delegate_to: load-balancer-host
find:
paths: "$ENVIRONMENT_VARIABLE/subdir"
file_type: file
patterns: file.pattern
contains: 'text pattern'
register: aVariable
The problem is that I can't found any way to make $ENVIRONMENT_VARIABLE (this variable exists in the load-balancer-host) available for the play (it contains the directory, in load-balancer-host, from where I want to look for). ansible_env is only available for the workers but not for the load-balancer-host
I have tried...
- name: A play
hosts: workers
tasks:
- name: set fact
set_fact:
env_var: "{{ lookup('env', 'ENVIRONMENT_VARIABLE') }}"
delegate_to: load-balancer-host
- name: debug
debug:
msg: "{{ env_var }}"
... too, but it prints an empty string.
For users running Ansible 1.x, see kfreezy's answer.
For users running Ansible 2.x, I have found the following solution:
- hosts: workers
tasks:
- name: gather facts from lb
setup:
delegate_to: load-balancer-host
delegate_facts: false
This task will make $ENVIRONMENT_VARIABLE available in every worker ansible_env var. If you want to make $ENVIRONMENT_VARIABLE available in the load-balancer-host ansible_env, just set delegate_facts to True.
More info in ansible docs
Personally I would simplify your playbook by either adding the $ENVIRONMENT_VARIABLE as a variable in Ansible (probably in the host_vars for load-balancer-host) or running a play against load-balancer-host rather than use delegate_to. It might not make sense depending on what the other tasks are.
Here's a direct answer to your question though.
load-balancer-host's ansible_env will only be defined when the host is included in the playbook. You can add another play against the 'load-balancer-host' that will just gather facts. Then you can reference the facts from 'load-balancer-host' using hostvars in your subsequent plays against 'workers'. He's what it would look like.
- hosts: load-balancer-host
tasks:
- name: print debug message
debug:
msg: "this play is for gathering facts on the LB"
- name: A play
hosts: workers
tasks:
- name: Look for text pattern in delegated host
delegate_to: load-balancer-host
find:
paths: "{{ hostvars['load-balancer-host'].ansible_env.ENVIRONMENT_VARIABLE }}/subdir"
file_type: file
patterns: file.pattern
contains: 'text pattern'
register: aVariable