Access hosts in play filtered by task - ansible

I have a task that checks the redis service status on the host list below
- hosts: 192.168.0.1, 192.168.0.2, 192.168.0.3
tasks:
- command:
cmd: service redis-server status
register: result
- debug:
var: result
After checking I need to access hosts where service does not exist.
And they should be accessible as variable to proceed with them in the next tasks.
Can someone please help?

Similar to Ansible facts it is also possible to gather service_facts. In example
- name: Set facts SERVICE
set_fact:
SERVICE: "redis-server.service"
- name: Gathering Service Facts
service_facts:
- name: Show ansible_facts.services
debug:
msg:
- "{{ ansible_facts.services[SERVICE].status }}"
If you like to perform tasks after on a service of which you don't the status, with Conditionals you can check the state.
If the service is not installed at that time, the specific variable (key) would not be defined. You would perform Conditionals based on variables then
when: ansible_facts.services[SERVICE] is defined
when: ansible_facts.services['redis-server.service'] is defined
Also it is recommend to use the Ansible service module to perform tasks on the service
- name: Start redis-server, if not started
service:
name: redis-server
state: started
instead of using the command module.
Further services related Q&A
How to check service exists and is not installed in the server using service_facts module in an Ansible playbook?
Ansible: How to start stopped services?
Ansible: How to get disabled but running services?
How to list only the running services with ansible_facts?

Finally found the solution that perfectly matches.
- name: Check that redis service exists
systemd:
name: "redis"
register: redis_status
changed_when: redis_status.status.ActiveState == "inactive"
- set_fact:
_dict: "{{ dict(ansible_play_hosts|zip(
ansible_play_hosts|map('extract', hostvars, 'redis_status'))) }}"
run_once: true
- set_fact:
_changed: "{{ (_dict|dict2items|json_query('[?value.changed].key'))| join(',') }}"
run_once: true

Related

Stop/Start Windows services only when state not in manual or disabled

I have an Ansible playbook to start or stop services and I needed to add a condition to pick up services that are neither in Manual nor Disabled State. Appreciate your help !!!
My code looks like this.
---
- name: windows start services
hosts: all
tasks:
- name: Include primary app services
include_vars:
file: inventory/vars/servicestart.yml
- name: Start task
win_service:
name: "{{ item }}"
state: started
loop: "{{ services }}"
when: inventory_hostname in groups['primaryappservers']

How to use multiple condition in 'when'

I have a Windows services start/stop playbook and services are specific to certain hosts.
I want to control start/stop services if it exists in a specific host.
I have a when to run on specific hosts but how to add and clause to check if services fromAppStopServices.yml exists before start or stop ?
Sample code
-name: service stop
hosts:all
tasks:
-name: Include app services
include_vars:
file: inventory/{{ env }}/group_vars/appstopservice.syml
-name: stop app services
win_service:
name: "{[ item }}"
state: stopped
loop: "{{ services }}"
when: inventory_hostname in group['Appservers']
You can use the win_service_info module to get the list of all win services on the target server, register the output and then query it to see if the service already exists before stopping it.
---
- name: service stop
hosts: all
tasks:
- name: Include app services
include_vars:
file: inventory/{{ env }}/group_vars/appstopservice.syml
# #Option 1 - using ansible win_service_info module
# - name: Get info for all installed services
# win_service_info:
# register: service_info
# Option2 - using ansible powershell module
- name: Get info for all installed services
win_powershell:
script: Get-Service
register: service_info
# # Option3 - using ansible win_shell module
# - name: Get info for all installed services
# win_shell: |
# sc queryex type=service state=all
# args:
# executable: cmd
# register: service_info
- name: stop app services
win_service:
name: "{[ item }}"
state: stopped
loop: "{{ services }}"
when:
- inventory_hostname in group['Appservers']
- service_info.stdout.find(item) != -1

Start only specific systemd service from list of services using Ansible

I have list of systemd services defined as
vars:
systemd_scripts: ['a.service', 'b.service', 'c.service']
Now I want to stop only a.service from above list. How this can be achieved using systemd_module?
What are you trying to achieve? As written, you could just do:
- name: Stop service A
systemd:
name: a.service
state: stopped
If instead you mean "the first service", use the first filter or an index:
- name: Stop first service
systemd:
name: "{{ systemd_scripts | first }}"
state: stopped
OR
- name: Stop first service
systemd:
name: "{{ systemd_scripts[0] }}"
state: stopped
Your question is very vague and unspecific. However, parts of your question could be answered with the following approach
- name: Loop over service list and stop one service
systemd:
name: "{{ item }}"
state: stopped
when:
- systemd_scripts[item] == 'a.service'
with_items: "{{ systemd_scripts }}"
You may need to extend and change it for your needs and required functionality.
Regarding discovering, starting and stopping services via Ansible facts (service_facts) and systemd you may have a look into
Further readings
Ansible: How to get disabled but running services?
How to declare a variable for service_facts?
How to check service exists and is not installed in the server using service_facts module in an Ansible playbook?

Ansible: Stop and Disable services if they are present

I'm trying to stop and disable a list of services only if they are installed. I'm calling the service_facts module to generate a list of running services and using filter "union"
- name: Include variables for Amazon Linux.
include_vars: ../vars/test.yml
- name: populate service facts
service_facts:
- name: start the service if it's enabled
service:
name: "{{ item }}"
enabled: no
state: stopped
loop: "{{ stop_services |union(services) }}"
when: stop_services is defined
I'm getting an error Unexpected templating type error occurred on ({{ stop_services |union(services) }}): can only concatenate list (not \"dict\") to list"
Is there any other way to stop and disable a list of services only if they are installed.
Your problem is described accurately by the error message: stop_services is a list, and services is a dictionary. You can't just squash the two together. You will need to build a list of service names and compare your list of services to stop against that.
For example:
- hosts: localhost
gather_facts: false
vars:
stop_services:
- sshd.service
- avahi-daemon.service
tasks:
- service_facts:
- name: stop and disable a service
service:
name: "{{ item }}"
state: stopped
enabled: false
when: "item in service_names"
loop: "{{ stop_services }}"
vars:
service_names: "{{ services|dict2items|map(attribute='value.name')|list }}"

Run an Ansible handler only once for the entire playbook

I would like to run a handler only once in an entire playbook.
I attempted using an include statement in the following in the playbook file, but this resulted in the handler being run multiple times, once for each play:
- name: Configure common config
hosts: all
become: true
vars:
OE: "{{ ansible_hostname[5] }}"
roles:
- { role: common }
handlers:
- include: handlers/main.yml
- name: Configure metadata config
hosts: metadata
become: true
vars:
OE: "{{ ansible_hostname[5] }}"
roles:
- { role: metadata }
handlers:
- include: handlers/main.yml
Here is the content of handlers/main.yml:
- name: restart autofs
service:
name: autofs.service
state: restarted
Here is an example of one of the tasks that notifies the handler:
- name: Configure automount - /opt/local/xxx in /etc/auto.direct
lineinfile:
dest: /etc/auto.direct
regexp: "^/opt/local/xxx"
line: "/opt/local/xxx -acdirmin=0,acdirmax=0,rdirplus,rw,hard,intr,bg,retry=2 nfs_server:/vol/xxx"
notify: restart autofs
How can I get the playbook to only execute the handler once for the entire playbook?
The answer
The literal answer to the question in the title is: no.
Playbook is a list of plays. Playbook has no namespace, no variables, no state. All the configuration, logic, and tasks are defined in plays.
Handler is a task with a different calling schedule (not sequential, but conditional, once at the end of a play, or triggered by the meta: flush_handlers task).
A handler belongs to a play, not a playbook, and there is no way to trigger it outside of the play (i.e. at the end of the playbook).
Solution
The solution to the problem is possible without referring to handlers.
You can use group_by module to create an ad-hoc group based on the result of the tasks at the bottom of each play.
Then you can define a separate play at the end of the playbook restarting the service on targets belonging to the above ad-hoc group.
Refer to the below stub for the idea:
- hosts: all
roles:
# roles declaration
tasks:
- # an example task modifying Nginx configuration
register: nginx_configuration
# ... other tasks ...
- name: the last task in the play
group_by:
key: hosts_to_restart_{{ 'nginx' if nginx_configuration is changed else '' }}
# ... other plays ...
- hosts: hosts_to_restart_nginx
gather_facts: no
tasks:
- service:
name: nginx
state: restarted
Possible solution
Use handlers to add hosts to in-memory inventory. Then add play to run restart service only for these hosts.
See this example:
If task is changed, it notify mark to restart to set fact, that host needs service restart.
Second handler add host is quite special, because add_host task only run once for whole play even in handler, see also documentation. But if notified, it will run after marking is done implied from handlers order.
Handler loops over hosts on which tasks were run and check if host service needs restart, if yes, add to special hosts_to_restart group.
Because facts are persistent across plays, notify third handler clear mark for affected hosts.
A lot of lines you hide with moving handlers to separate file and include them.
inventory file
10.1.1.[1:10]
[primary]
10.1.1.1
10.1.1.5
test.yml
---
- hosts: all
gather_facts: no
tasks:
- name: Random change to notify trigger
debug: msg="test"
changed_when: "1|random == 1"
notify:
- mark to restart
- add host
- clear mark
handlers:
- name: mark to restart
set_fact: restart_service=true
- name: add host
add_host:
name: "{{item}}"
groups: "hosts_to_restart"
when: hostvars[item].restart_service is defined and hostvars[item].restart_service
with_items: "{{ansible_play_batch}}"
- name: clear mark
set_fact: restart_service=false
- hosts: primary
gather_facts: no
tasks:
- name: Change to notify trigger
debug: msg="test"
changed_when: true
notify:
- mark to restart
- add host
- clear mark
handlers:
- name: mark to restart
set_fact: restart_service=true
- name: add host
add_host:
name: "{{item}}"
groups: "hosts_to_restart"
when: hostvars[item].restart_service is defined and hostvars[item].restart_service
with_items: "{{ansible_play_batch}}"
- name: clear mark
set_fact: restart_service=false
- hosts: hosts_to_restart
gather_facts: no
tasks:
- name: Restart service
debug: msg="Service restarted"
changed_when: true
A handler triggered in post_tasks will run after everything else. And the handler can be set to run_once: true.
It's not clear to me what your handler should do. Anyway, as for official documentation, handlers
are triggered at the end of each block of tasks in a play,
and will only be triggered once even if notified by multiple different
tasks [...] As of Ansible 2.2, handlers can also “listen” to generic topics, and tasks can notify those topics as follows:
So handlers are notified / executed once for each block of tasks.
May be you get your goal just keeping handlers after "all" target hosts, but it doesn't seem a clean use of handlers.
.

Resources