conditional execution of playbook based on exit status of the command module? - ansible

I am trying to write a playbook which would execute some tasks only if a certain package is installed on the hosts.
Is it possible to register the output from a command module and run the tasks depending upon the exit status of the command ?
Something like this:

You are on the right path. If httpd doesnt exist, the playbook execution will fail. You can use ignore_errors to continue execution and then run subsequent tasks based on the return code of httpd_result. I have given an example below:
- hosts: localhost
tasks:
- command: "which httpd"
register: httpd_result
ignore_errors: true
- debug: msg="found http"
when: httpd_result.rc == 0
- debug: msg="not found httpd"
when: httpd_result.rc!=0
Here, instead of debug statements, you can put whatever conditional tasks you need to run. Hope this helps.

Related

implicit ansible file exists remote

I want to execute tasks only if a file exists on the target.
I can do that, of course, with
- ansible.builtin.stat:
path: <remote_file>
register: remote_file
- name: ...
something:
when: remote_file.stat.exists
but is there something smaller?
You can, for example, check files on the host with
when: '/tmp/file.txt' is file
But that only checks files on host.
Is there also something like that available for files on remotes?
Added, as according the comment section I need to be bit more specific.
I want to run tasks on the target, and on the next run, they shouldn´t be executed again. So I thought to put a file somewhere on the target, and on the next run, when this file exists, the tasks should not be executed anymore.
- name: do big stuff
bigsgtuff:
when: not <file> exists
They should be executed, if the file does not exists.
I am not aware of a way to let the Control Node know during templating or compile time if there exist a specific file on Remote Node(s).
I understand your question that you
... want to execute tasks on the target ...
only if a certain condition is met on the target.
This sounds to be impossible without taking a look on the target before or do a lookup somewhere else, in example in a Configuration Management Database (CMDB).
... is there something smaller?
It will depend on your task, what you try to achieve and how do you like to declare a configuration state.
In example, if you you like to declare the absence of a file, there is no need to check if it exists before. Just make sure it becomes deleted.
---
- hosts: test
become: false
gather_facts: false
tasks:
- name: Remove file
shell:
cmd: "rm /home/{{ ansible_user }}/test.txt"
warn: false
register: result
changed_when: result.rc != 1
failed_when: result.rc != 0 and result.rc != 1
- name: Show result
debug:
msg: "{{ result }}"
As you see, it will be necessary to Defining failure and control how the task behaves. Another example for showing the content.
---
- hosts: test
become: false
gather_facts: false
tasks:
- name: Gather file content
shell:
cmd: "cat /home/{{ ansible_user }}/test.txt"
register: result
changed_when: false
failed_when: result.rc != 0 and result.rc != 1
- name: Show result
debug:
msg: "{{ result.stdout }}"
Please take note, for the given example tasks there are already specific Ansible modules available which do the job better.
According the additional given information in your comments I understand that you like to install or configure "something" and like to leave that fact left on the remote node(s). You like to run the task the next time on the remote node(s) in fact only if it is wasn't successful performed before.
To do so, you may have a look into Ansible facts and Discovering variables: facts and magic variables, especially into Adding custom facts.
What your installation or configuration tasks could do, is leaving a custom .fact file with meaningful keys and values after they were successful running.
During next playbook execution and if gather_facts: true, the information would be gathered from the setup module and you can than let tasks run based on conditions in ansible_local.
Further Q&A
How Ansible gather_facts and sets variables
An introduction to Ansible facts
Loading custom facts
Whereby the host facts can be considered as kind of distributed CMDB, by using facts Cache
fact_caching = yaml
fact_caching_connection = /tmp/ansible/facts_cache
fact_caching_timeout = 129600
you can have the information also available on the Control Node. It might even be possible to organize it in host_vars and group_vars.

How to execute Play 2 only if Play 1 succeeds in Ansible

I have two Plays having one task each.
The first Play checks if the /var/test.dat exists on each target. Only if the first play is successful do I want the second play to run which executes these scripts in parallel.
If the first play fails i.e the test.dat does not exist I wish to terminate the playbook without the second Play getting executed. For this purpose, I have set any_errors_fatal set to true
I need to have an ansible Play strategy set to free as each of the scripts takes 30 minutes to complete.
My understanding of ansible is limited.
I understand that if I have both the tasks under a single PLAY and set the strategy to free both the tasks will run in parallel which is something I do not want.
---
- name: Play 1- check for login and script
hosts: all_hosts
any_errors_fatal: true
strategy: free
tasks:
- name: Check script existence
shell: "ls /var/test.dat"
register: checkscript
- name:
fail:
msg: "script {{ scriptdet }} missing on {{ inventory_hostname }}"
when: checkscript.rc != 0
- name: Play 2- Run scripts
hosts: all_hosts
user: "{{ USER }}"
strategy: free
tasks:
- name: Execute backup script
shell: "{{ scriptdet }}"
args:
chdir: ~/..
I tried the above playbook but I see the second play executes despite the first play's task failed.
Can you please suggest how can I get this to work?
Unless any_errors_fatal is true - Ansible will finish running on all hosts - even if some other host failed.
However, as you found out any_errors_fatal does not work with a free strategy.
It's also impossible to create a global variable.
However, you mentioned that you want to use free strategy to speed up the execution.
However, free strategy only prevents "blocking new tasks for hosts that have already finished.".
If you need to execute the same task without waiting for other hosts to finish, you need parallelism.
Strategy has nothing to do with parallelism.
In Ansible you adjust the number of parallel hosts with forks. However the default is very low, and is only 5.
What you want is to set this number higher - depending on your executing host resources. In my experience with a good amount of cpu and memory, this can be adjusted to hundreds.
You set this either in ansible.cfg or in the command line:
ansible-playbook -f 30 my_playbook.yml
This way you can use the default linear strategy with any_errors_fatal set to true.

Execute role in play based on a failure [duplicate]

I'm trying to spin up an AWS deployment environment in Ansible, and I want to make it so that if something fails along the way, Ansible tears down everything on AWS that has been spun up so far. I can't figure out how to get Ansible to throw an error within the role
For example:
<main.yml>
- hosts: localhost
connection: local
roles:
- make_ec2_role
- make_rds_role
- make_s3_role
2. Then I want it to run some code based on that error here.
<make_rds_role>
- name: "Make it"
- rds:
params: etc <-- 1. Let's say it fails in the middle here
I've tried:
- name: this command prints FAILED when it fails
command: /usr/bin/example-command -x -y -z
register: command_result
failed_when: "'FAILED' in command_result.stderr"
As well as other things on within the documentation, but what I really want is just a way to use something like the "block" and "rescue" commands , but as far as I can tell that only works within the same book and on plays, not roles. Does anyone have a good way to do this?
Wrap tasks inside your roles into block/rescue thing.
Make sure that rescue block has at least one task – this way Ansible will not mark the host as failed.
Like this:
- block:
- name: task 1
... # something bad may happen here
- name: task N
rescue:
- assert: # we need a dummy task here to prevent our host from being failed
that: ansible_failed_task is defined
Recent versions of Ansible register ansible_failed_task and ansible_failed_result when hit rescue block.
So you can do some post_tasks in your main.yml playbook like this:
post_tasks:
- debug:
msg: "Failed task: {{ ansible_failed_task }}, failed result: {{ ansible_failed_result }}"
when: ansible_failed_task is defined
But be warned that this trick will NOT prevent other roles from executing.
So in your example if make_rds_role fails ansible will apply make_s3_role and run your post_tasks afterwards.
If you need to prevent it, add some checking for ansible_failed_task fact in the beginning of each role or something.

In Ansible, can playbooks pass tags to other playbooks?

We have a "periodic" tag in our roles that is intended to be run at regular intervals by our Ansible box for file assurance, etc. Would it be possible to have a playbook for periodic runs that calls the other playbooks with the appropriate host groups and tags?
The only way to execute an Ansible playbook "with the appropriate host groups and tags" is to run ansible-playbook executable. This is the only case in which all the data structures starting from the inventory would be created in isolation from the currently running playbook.
You can simply call the executable using command module on the control machine:
- hosts: localhost
tasks:
- command: ansible-playbook {{ playbook }} --tags {{ tags }}
You can also use local_action or delegate_to.
It might be that you want to include plays, or use roles, however given the problem description in the question, it's impossible to tell.
Here is what we ended up with: It turns out that tags and variables passed on the command-line are inherited all the way down the line. This allowed us to pass this on the command line:
ansible-playbook -t periodic periodic.yml
Which calls a playbook like this:
---
- name: This playbook must be called with the "periodic" tag.
hosts: 127.0.0.1
any_errors_fatal: True
tasks:
- fail:
when: periodic not True
- name: Begin periodic runs for type 1 servers
include: type1-server.yml
vars:
servers:
- host_group1
- host_group2
- ...
- name: Begin periodic runs for type 2 servers
...
Our 'real' playbooks have - hosts: "{{ servers }}" so that they can be inherited from the parent. The tasks in our roles are tagged with "periodic" for things that need to be run on a schedule. We then use SystemD to schedule the runs. You can use cron, but SystemD is better IMHO. Examples can be provided upon request.

How to check whether the result of an Ansible task is the same on all the servers?

As part of an Ansible playbook, I want to verify whether my servers are in sync.
To do so, I execute a script on each of my servers using the shell module and I register the result into a variable, say result_value.
The tricky part is to verify whether result_value is the same on all the servers. The expected value is not known beforehand.
Is there an idiomatic way to achieve this in Ansible?
One of possible ways:
---
- hosts: all
tasks:
- shell: /usr/bin/my_check.sh
register: result_value
- assert:
that: hostvars[item]['result_value'].stdout == result_value.stdout
with_items: "{{play_hosts}}"
run_once: true

Resources