How to make Ansible run one certain task only on one host? - ansible

The playbook looks like:
- hosts: all
tasks:
- name: "run on all hosts,1"
shell: something1
- name: "run on all hosts,2"
shell: something2
- name: "run on one host, any host would do"
shell: this_command_should_run_on_one_host
- name: "run on all hosts,3"
shell: something3
I know with command line option --limit, I can limit to one host, is it possible to do it in playbook?

For any host (with defaults it will match the first on the list):
- name: "run on first found host"
shell: this_command_should_run_on_one_host
run_once: true
For a specific host:
- name: "run on that_one_host host"
shell: this_command_should_run_on_one_host
when: ansible_hostname == 'that_one_host'
Or inventory_hostname (hostname as defined in the Ansible inventory) instead of ansible_hostname (hostname as defined on the target machine), depending on which name you want to use.

Techraf's first answer is the exact one for the OP's question.
I just wanted to show a better way to run a task on a specific host:
- name: "run on that_one_host host"
shell: this_command_should_run_on_one_host
run_once: true
delegate_to: that_one_host
If the group the playbook is run on contains many hosts, using when: ansible_hostname == 'that_one_host' or when: ansible_hostname == play_hosts[0] will evaluate the when-clause on all the hosts (which could be long if the when-clause had other more complicated conditions) and result in a long list of skipped hosts in the playbook's output.
Combining run_once and delegate_to, the playbook's output will be cleaner, only showing the task being executed on the chosen host.
- hosts: all
gather_facts: no
tasks:
- name: Run on one specific host | when-clause
debug:
msg: "Hello world"
when: inventory_hostname == play_hosts[0]
- name: Run on one specific host | run_once + delegate_to
debug:
msg: "Hello world"
run_once: true
delegate_to: play_hosts[0]
TASK [Run on one specific host | when-clause] **********************************************************************************
skipping: [host2]
skipping: [host3]
ok: [host1] => {
"msg": "Hello world"
}
skipping: [host4]
TASK [Run on one specific host | run_once + delegate_to] ***********************************************************************
ok: [host2 -> play_hosts[0]] => {
"msg": "Hello world"
}
Whichever solution you choose, never combine these, if you want a task to be run once on a precise host:
run_once: true and
when: inventory_hostname == 'that_one_host'
If you do, it will be impossible to know in advance if the task will be executed or not (I have learnt it the hard way). The reason is that run_once: true will select a random host in the play's group and only later apply the when-clause:
if run_once selected to run the task on 'that_one_host', the task will be executed
if run_once selected 'another_host', the task will be skipped.

True way
- include_tasks: custom-tasks.yml
when: inventory_hostname == item
with_items: "{{ ansible_play_hosts }}"

Related

how does one combine conditionals into one "when" statement?

Ansible 2.10.x
I looked at How to define multiple when conditions in Ansible, and similar posts.
I'm trying to test if 2 different substring are in a variable. I've tried
default/main.yml
----------------
# Default path can be overridden in task
repo_url: "https://someUrl/development"
tasks/main.yml
--------------
- debug:
msg: "URL={{ repo_url }}"
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: ('"development" not in web_version') and
('"feature" not in web_version')
- debug:
msg: "URL={{ repo_url }}"
I use above task like this for example
$ ansible-playbook ... -e web_version=development_ myTask.yml
But I get
TASK [exa-web : debug] *************************************************
ok: [10.227.x.x] => {
"msg": "URL=https://someUrl/development"
}
TASK [exa-web : Override default path] *************************************************
ok: [10.227.x.x]
TASK [exa-web : debug] *************************************************
ok: [10.227.x.x] => {
"msg": "URL=https://someUrl/releases"
}
I don't expect the set_fact task to run, but it does; hence it overrides the default repo_url. So apparently I'm setting my when condition wrong.
I've also tried this to no avail.
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: '"development_" not in web_version and
"feature_" not in web_version'
Essentially, I need the task to run if I execute my playbook like this
$ ansible-playbook ... -e web_version=1.4.44 myTask.yml
What's the correct syntax? TIA
UPDATE
Seems like when doesn't like ()? I just simplified the condition for now, and this works
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: '"development" not in web_version'
but not this?
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: ('"development" not in web_version')
Really???
Your second attempt...
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: '"development_" not in web_version and
"feature_" not in web_version'
...seems syntactically correct. In a playbook like this:
- hosts: localhost
gather_facts: false
vars:
repo_url: "https://someUrl/development"
tasks:
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: '"development_" not in web_version and
"feature_" not in web_version'
- debug:
msg: "URL={{ repo_url }}"
If we run it like this:
ansible-playbook -e web_version=development_ playbook.yaml
We see as output:
TASK [Override default path] ****************************************************************************
skipping: [localhost]
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"msg": "URL=https://someUrl/development"
}
And if we run it like this:
ansible-playbook -e web_version=1.4.44 playbook.yaml
We see:
TASK [Override default path] ****************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"msg": "URL=https://someUrl/releases"
}
That seems to do exactly what you want. Note that you're looking for the string development_ (with a trailing underscore) in your when statement, rather than development as in the first example, but that's an easy fix.
While your code works just fine, I find it helpful to use one of YAML's quote operators for writing multi-line when statements, since it avoids me getting confused by nested quotes in the expression:
- hosts: localhost
gather_facts: false
vars:
repo_url: "https://someUrl/development"
tasks:
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: >-
"development_" not in web_version and
"feature_" not in web_version
- debug:
msg: "URL={{ repo_url }}"
Re: your update, this doesn't work...
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: ('"development" not in web_version')
...because of bad quoting. You are effectively writing:
when: ("a string")
And a non-empty string evaluates as true in a boolean expression. Always put the quotes at the beginning of the expression. E.g., this works just fine:
when: >-
("development" not in web_version)
As does the syntactically identical:
when: '("development" not in web_version)'

Use skip_reason as a condition in a task

Is it possible to use the skip_reason as condition to another task?
Here is the task:
- name: PSP Validation
script: roles/OS_minor_upgrade/files/PSP_validation.sh
ignore_errors: true.
register: PSP_VAL
when: >
not 'VMware' in HWMODEL.stdout
Which output:
TASK [OS_minor_upgrade : PSP Postwork] ******************************************************************************************************************************************************
task path: /home/ansible/linuxpatching_OS_Upgrade/roles/OS_minor_upgrade/tasks/upgrade.yml:264
skipping: [server123] => {
"changed": false,
"skip_reason": "Conditional result was False"
}
Now I want to use the above as condition to execute another task, I tried with the task below but it seem like it is not working.
- name: OSupgrade done
shell: echo {{ inventory_hostname }} "OS Upgrade Done" > OUTGOING-OSUPGRADE-PATCHCOMPLETION/inventory_{{ inventory_hostname }}_{{ '%y%m%d%H%M%S' | strftime }}_Offlineoutput
delegate_to: localhost
when: >
fs_check.rc == 0 and val_etrust.rc == 0 and 'PSP Installation is successfully completed' in PSP_VAL.stdout or 'Conditional result was False' in PSP_VAL.skip_reason
How can this be achieved?
Technically, you can use 'skip_reason' as any other variable, but I STRONGLY suggest you not to.
The reason is that person, reading your code (you, 1 week later) would be in a total loss over such decision.
If you have important information about your host, you can use set_fact module to update your host information. Further tasks can use this to make decisions.
- name: Update vmware info
set_fact:
vmware_flag: ('VMware' in HWMODEL.stdout)
- name: PSP Validation
script: roles/OS_minor_upgrade/files/PSP_validation.sh
failed_when: false
register: PSP_VAL
when: not vmware_flag
- name: OSupgrade done
delegate_to: localhost
copy:
dest: OUTGOING-OSUPGRADE-PATCHCOMPLETION/inventory_{{ inventory_hostname }}_{{ '%y%m%d%H%M%S' | strftime }}_Offlineoutput
content: '{{ inventory_hostname }} "OS Upgrade Done"'
when: (some other conditions) or vmware_flag
There are specific ansible tests to verify the registered result of a task
In your specific case:
when: <...all other conditions...> or PSP_VAL is skipped

When condition is just skipping

I have an Ansible playbook as shown below. The only problem is every time I run it is just skipping/ignoring the task.
Can you please help me figure out what is the problem?
- name: Register Host to Dynamic Inventory
hosts: localhost
gather_facts: false
tasks:
- add_host:
name: "{{ myhost }}"
- name: Enabling the services
hosts: "{{ myhost }}"
gather_facts: true
tasks:
- name: Make the service available persistently after a reboot for SLES11
command: systemctl enable after.local
with_items: "{{ ansible_distribution_major_version }}"
when: ansible_distribution_major_version == 11
- name: Make the service available persistently after a reboot for SLES12
command: systemctl enable after.local
with_items: "{{ ansible_distributioni_major_version }}"
when: ansible_distribution_major_version == 12
TASK [add_host] ****************************************************************03:22:06
changed: [localhost]
PLAY [Enabling the services] ***************************************************03:22:06
TASK [Gathering Facts] *********************************************************03:22:06
ok: [hostname]
TASK [Make the service available persistently after a reboot for SLES11] ******03:22:10
skipping: [hostname] => (item=12)
TASK [Make the service available persistently after a reboot for SLES12] ******03:22:10
skipping: [hostname]
The tasks get skipped because ansible_distribution_major_version is a string and you compare it to an integer value.
You should either fix your conditions to:
when: ansible_distribution_major_version == "12"
Or cast the value:
when: ansible_distribution_major_version | int == 12
Having fixed that, the remaining code makes little sense and will produce a syntax error.

Dynamic ansible host_vars

I have a use case for a play where the installation path for tomcat changes based on the hostname and the value of a variable. Not sure how to handle this. For example, I have the following inventory:
[servers]
server1
server2
server3
I have a global_var that specifies the type of platform for my install like so:
platform: training
My platform variable could be set to training, production, development
Based on the value of platform and the hostname, my tomcat installation path will be different, so I can't just have:
host_vars/server1.yml
tomcat_path: /somepath1
host_vars/server2.yml
tomcat_path: /somepath2
host_vars/server3.yml
tomcat_path: /somepath3
I'm looking to do something akin too:
server1.yml
tomcat_path: /somepath1
when: "{{ platform }} == training"
tomcat_path: /somepath2
when: "{{ platform }} == production"
tomcat_path: /somepath3
when: "{{ platform }} == development"
How do you handle such a case in ansible?
you could define all possible platform-tomcat_path options in a dictionary variable, and then select the desired combination with several ways.
check below example (with 3 different ways to reference the variables):
- hosts: localhost
gather_facts: false
vars:
tomcat_path: { training: /somepath1, production: /somepath2, development: /somepath3 }
your_selected_mode: development
tasks:
- name: print
debug:
var: tomcat_path.training
- name: print
debug:
var: tomcat_path['production']
- name: print
debug:
var: tomcat_path.{{your_selected_mode}}
you can use the your_selected_mode to select the mode you need.
output:
TASK [print] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"tomcat_path.training": "/somepath1"
}
TASK [print] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"tomcat_path['production']": "/somepath2"
}
TASK [print] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"tomcat_path.development": "/somepath3"
}
if needed to further customize it per host, you can have this tomcat_path variable declaration in the host_vars file and alter it to fit your needs.
You could use conditional imports but it requires you to have one variables file per host and platform:
- hosts: localhost
connection: local
vars_files:
- "vars/{{ inventory_hostname }}_{{ platform }}.yml"
tasks:
- name: echo path
debug: msg="Tomcat path is {{ tomcat_path }}"
You would need to define the variable files vars/server1_training.yml, vars/server2_training.yml, vars/server3_training.yml, vars/server1_production.yml, ....

How to set an Ansible variable for all plays/hosts?

This question is NOT answered. Someone mentioned environment variables. Can you elaborate on this?
This seems like a simple problem, but not in ansible. It keeps coming up. Especially in error conditions. I need a global variable. One that I can set when processing one host play, then check at a later time with another host. In a nutshell, so I can branch later in the playbook, depending on the variable.
We have no control over custom software installation, but if it is installed, we have to put different software on other machines. To top it off, the installations vary, depending on the VM folder. My kingdom for a global var.
The scope of variables relates ONLY to the current ansible_hostname. Yes, we have group_vars/all.yml as globals, but we can't set them in a play. If I set a variable, no other host's play/task can see it. I understand the scope of variables, but I want to SET a global variable that can be read throughout all playbook plays.
The actual implementation is unimportant but variable access is (important).
My Question: Is there a way to set a variable that can be checked when running a different task on another host? Something like setGlobalSpaceVar(myvar, true)? I know there isn't any such method, but I'm looking for a work-around. Rephrasing: set a variable in one tasks for one host, then later in another task for another host, read that variable.
The only way I can think of is to change a file on the controller, but that seems bogus.
An example
The following relates to oracle backups and our local executable, but I'm keeping it generic. For below - Yes, I can do a run_once, but that won't answer my question. This variable access problem keeps coming up in different contexts.
I have 4 xyz servers. I have 2 programs that need to be executed, but only on 2 different machines. I don't know which. The settings may be change for different VM environments.
Our programOne is run on the server that has a drive E. I can find which server has drive E using ansible and do the play accordingly whenever I set a variable (driveE_machine). It only applies to that host. For that, the other 3 machines won't have driveE_machine set.
In a later play, I need to execute another program on ONLY one of the other 3 machines. That means I need to set a variable that can be read by the other 2 hosts that didn't run the 2nd program.
I'm not sure how to do it.
Inventory file:
[xyz]
serverxyz[1:4].private.mystuff
Playbook example:
---
- name: stackoverflow variable question
hosts: xyz
gather_facts: no
serial: 1
tasks:
- name: find out who has drive E
win_shell: dir e:\
register: adminPage
ignore_errors: true
# This sets a variable that can only be read for that host
- name: set fact driveE_machine when rc is 0
set_fact:
driveE_machine: "{{inventory_hostname}}"
when: adminPage.rc == 0
- name: run program 1
include: tasks/program1.yml
when: driveE_machine is defined
# program2.yml executes program2 and needs to set some kind of variable
# so this include can only be executed once for the other 3 machines
# (not one that has driveE_machine defined and ???
- name: run program 2
include: tasks/program2.yml
when: driveE_machine is undefined and ???
# please don't say run_once: true - that won't solve my variable access question
Is there a way to set a variable that can be checked when running a task on another host?
No sure what you actually want, but you can set a fact for every host in a play with a single looped task (some simulation of global variable):
playbook.yml
---
- hosts: mytest
gather_facts: no
vars:
tasks:
# Set myvar fact for every host in a play
- set_fact:
myvar: "{{ inventory_hostname }}"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
# Ensure that myvar is a name of the first host
- debug:
msg: "{{ myvar }}"
hosts
[mytest]
aaa ansible_connection=local
bbb ansible_connection=local
ccc ansible_connection=local
result
PLAY [mytest] ******************
META: ran handlers
TASK [set_fact] ******************
ok: [aaa -> aaa] => (item=aaa) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "aaa"}
ok: [aaa -> bbb] => (item=bbb) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "bbb"}
ok: [aaa -> ccc] => (item=ccc) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "ccc"}
TASK [debug] ******************
ok: [aaa] => {
"msg": "aaa"
}
ok: [bbb] => {
"msg": "aaa"
}
ok: [ccc] => {
"msg": "aaa"
}
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#fact-caching
As shown elsewhere in the docs, it is possible for one server to reference variables about another, like so:
{{ hostvars['asdf.example.com']['ansible_os_family'] }}
This even applies to variables set dynamically in playbooks.
This answer doesn't pre-suppose your hostnames, nor how many hosts have a "drive E:". It will select the first one that is reachable that also has a "drive E:". I have no windows boxes, so I fake it with a random coin toss for whether a host does or doesn't; you can of course use your original win_shell task, which I've commented out.
---
- hosts: all
gather_facts: no
# serial: 1
tasks:
# - name: find out who has drive E
# win_shell: dir e:\
# register: adminPage
# ignore_errors: true
- name: "Fake finding hosts with drive E:."
# I don't have hosts with "drive E:", so fake it.
shell: |
if [ $RANDOM -gt 10000 ] ; then
exit 1
else
exit 0
fi
args:
executable: /bin/bash
register: adminPage
failed_when: false
ignore_errors: true
- name: "Dict of hosts with E: drives."
run_once: yes
set_fact:
driveE_status: "{{ dict(ansible_play_hosts_all |
zip(ansible_play_hosts_all |
map('extract', hostvars, ['adminPage', 'rc'] ) | list
))
}}"
- name: "List of hosts with E: drives."
run_once: yes
set_fact:
driveE_havers: "{%- set foo=[] -%}
{%- for dE_s in driveE_status -%}
{%- if driveE_status[dE_s] == 0 -%}
{%- set _ = foo.append( dE_s ) -%}
{%- endif -%}
{%- endfor -%}{{ foo|list }}"
- name: "First host with an E: drive."
run_once: yes
set_fact:
driveE_first: "{%- set foo=[] -%}
{%- for dE_s in driveE_status -%}
{%- if driveE_status[dE_s] == 0 -%}
{%- set _ = foo.append( dE_s ) -%}
{%- endif -%}
{%- endfor -%}{{ foo|list|first }}"
- name: Show me.
run_once: yes
debug:
msg:
- "driveE_status: {{ driveE_status }}"
- "driveE_havers: {{ driveE_havers }}"
- "driveE_first: {{ driveE_first }}"
It's working for me, you can directly use register var no need to use set_fact.
---
- hosts: manager
tasks:
- name: dir name
shell: cd /tmp && pwd
register: homedir
when: "'manager' in group_names"
- hosts: all
tasks:
- name: create a test file
shell: touch "{{hostvars['manager.example.io'['homedir'].stdout}}/t1.txt"
i have used "set_fact" module in the ansible tasks prompt menu, which is helped to pass user input into all the inventory hosts.
#MONITORING PLAYBOOK
- hosts: all
gather_facts: yes
become: yes
tasks:
- pause:
prompt: "\n\nWhich monitoring you want to perform?\n\n--------------------------------------\n\n1. Memory Utilization:\n2. CPU Utilization:\n3. Filesystem Utili
zation:\n4. Exist from Playbook: \n5. Fetch Nmon Report \n \nPlease select option: \n--------------------------------------\n"
register: menu
- set_fact:
option: "{{ menu.user_input }}"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
#1 Memory Monitoring
- pause:
prompt: "\n-------------------------------------- \n Enter monitoring Incident Number = "
register: incident_number_result
when: option == "1"
- name: Standardize incident_number variable
set_fact:
incident_number: "{{ incident_number_result.user_input }}"
when: option == "1"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
ansible playbook result is
[ansibusr#ansiblemaster monitoring]$ ansible-playbook monitoring.yml
PLAY [all] **************************************************************************
TASK [Gathering Facts] **************************************************************************
ok: [node2]
ok: [node1]
[pause]
Which monitoring you want to perform?
--------------------------------------
1. Memory Utilization:
2. CPU Utilization:
3. Filesystem Utilization:
4. Exist from Playbook:
5. Fetch Nmon Report
Please select option:
--------------------------------------
:
TASK [pause] **************************************************************************
ok: [node1]
TASK [set_fact] **************************************************************************
ok: [node1 -> node1] => (item=node1)
ok: [node1 -> node2] => (item=node2)
[pause]
--------------------------------------
Enter monitoring Incident Number = :
INC123456
TASK [pause] **************************************************************************
ok: [node1]
TASK [Standardize incident_number variable] ****************************************************************************************************************************
ok: [node1 -> node1] => (item=node1)
ok: [node1 -> node2] => (item=node2)

Resources