ansible delegation to other hosts - ansible

I use ansible 2.1 and I want to run a command to a group of hosts, using delegate_to. I use localhost as the host param and I want to delegate a “touch” command to both of cls hosts
I have the following
---
- hosts: ansible
# gather_facts: yes
tasks:
- debug: var=groups.cls
- name: touch a file to running host
shell: echo {{ item }} >> /tmp/{{ inventory_hostname }}
delegate_to: "{{ item }}"
with_items: "{{ groups.cls }}"
with output:
[root#ansible control]# ansible-playbook -i inventory test.yml
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [ansible]
TASK [debug] *******************************************************************
ok: [ansible] => {
"groups.cls": [
"cls-host-1",
"cls-host-2"
]
}
TASK [touch a file to running host] ********************************************
changed: [ansible -> cls-host-1] => (item=cls-host-1)
changed: [ansible -> cls-host-2] => (item=cls-host-2)
PLAY RECAP *********************************************************************
ansible : ok=3 changed=1 unreachable=0 failed=0
but the touch is done only on the first host:
[root#cls-host-1 ~]# more /tmp/ansible
cls-host-1
cls-host-2
Is anything wrong? Can I delegate the command with any other way?

I've tested a variation of your playbook using Ansible 2.4.0.0:
#!/usr/bin/env ansible-playbook
- hosts: stretch.fritz.box
tasks:
- name: touch
shell: echo {{item}} >>/tmp/{{inventory_hostname}}
delegate_to: "{{item}}"
with_items:
- jessie.fritz.box
- short.fritz.box
This is working fine: the touch is performed on jessie and short
jessie$ cat /tmp/stretch.fritz.box
jessie.fritz.box
short$ cat /tmp/stretch.fritz.box
short.fritz.box
Perhaps this feature was introduced in Ansible between 2.1 and 2.4.

Related

Ansible: How to check multiple servers for a text file value, to decide which servers to run the script on?

I am trying to ask Ansible to check if a server is passive or active based on the value of a specific file in each server, then Ansible will decide which server it runs the next script on.
For example with 2 servers:
Server1
cat /tmp/currentstate
PASSIVE
Server2
cat /tmp/currentstate
ACTIVE
In Ansible
Trigger next set of jobs on server where the output was ACTIVE.
Once the jobs complete, trigger next set of jobs on server where output was PASSIVE
What I have done so far to grab the state, and output the value to Ansible is
- hosts: "{{ hostname1 | mandatory }}"
gather_facts: no
tasks:
- name: Grab state of first server
shell: |
cat {{ ans_script_path }}currentstate.log
register: state_server1
- debug:
msg: "{{ state_server1.stdout }}"
- hosts: "{{ hostname2 | mandatory }}"
gather_facts: no
tasks:
- name: Grab state of second server
shell: |
cat {{ ans_script_path }}currentstate.log
register: state_server2
- debug:
msg: "{{ state_server2.stdout }}"
What I have done so far to trigger the script
- hosts: "{{ active_hostname | mandatory }}"
tasks:
- name: Run the shutdown on active server first
shell: sh {{ ans_script_path }}stopstart_terracotta_main.sh shutdown
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
- hosts: "{{ passive_hostname | mandatory }}"
tasks:
- name: Run the shutdown on passive server first
shell: sh {{ ans_script_path }}stopstart_terracotta_main.sh shutdown
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
but I don't know how to set the value of active_hostname & passive_hostname based on the value from the script above.
How can I set the Ansible variable of active_hostname & passive_hostname based on the output of the first section?
A better solution came to my mind is to include hosts in new groups according to their state.
This would be more optimal in case there are more than two hosts.
- hosts: all
gather_facts: no
vars:
ans_script_path: /tmp/
tasks:
- name: Grab state of server
shell: |
cat {{ ans_script_path }}currentstate.log
register: server_state
- add_host:
hostname: "{{ item }}"
# every host will be added to a new group according to its state
groups: "{{ 'active' if hostvars[item].server_state.stdout == 'ACTIVE' else 'passive' }}"
# Shorter, but the new groups will be in capital letters
# groups: "{{ hostvars[item].server_state.stdout }}"
loop: "{{ ansible_play_hosts }}"
changed_when: false
- name: show the groups the host(s) are in
debug:
msg: "{{ group_names }}"
- hosts: active
gather_facts: no
tasks:
- name: Run the shutdown on active server first
shell: hostname -f # changed that for debugging
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
- hosts: passive
gather_facts: no
tasks:
- name: Run the shutdown on passive server first
shell: hostname -f
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
test-001 is PASSIVE
test-002 is ACTIVE
PLAY [all] ***************************************************************
TASK [Grab state of server] **********************************************
ok: [test-002]
ok: [test-001]
TASK [add_host] **********************************************************
ok: [test-001] => (item=test-001)
ok: [test-001] => (item=test-002)
TASK [show the groups the host(s) are in] ********************************
ok: [test-001] => {
"msg": [
"passive"
]
}
ok: [test-002] => {
"msg": [
"active"
]
}
PLAY [active] *************************************************************
TASK [Run the shutdown on active server first] ****************************
changed: [test-002]
TASK [debug] **************************************************************
ok: [test-002] => {
"msg": "test-002"
}
PLAY [passive] ************************************************************
TASK [Run the shutdown on passive server first] ****************************
changed: [test-001]
TASK [debug] **************************************************************
ok: [test-001] => {
"msg": "test-001"
}
PLAY RECAP ****************************************************************
test-001 : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test-002 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
For example, given two remote hosts
shell> ssh admin#test_11 cat /tmp/currentstate.log
ACTIVE
shell> ssh admin#test_13 cat /tmp/currentstate.log
PASSIVE
The playbook below reads the files and runs the commands on active and passive servers
shell> cat pb.yml
- hosts: "{{ host1 }},{{ host2 }}"
gather_facts: false
vars:
server_states: "{{ dict(ansible_play_hosts|
zip(ansible_play_hosts|
map('extract', hostvars, ['server_state', 'stdout'])|
list)) }}"
server_active: "{{ server_states|dict2items|
selectattr('value', 'eq', 'ACTIVE')|
map(attribute='key')|list }}"
server_pasive: "{{ server_states|dict2items|
selectattr('value', 'eq', 'PASSIVE')|
map(attribute='key')|list }}"
tasks:
- command: cat /tmp/currentstate.log
register: server_state
- debug:
var: server_state.stdout
- block:
- debug:
var: server_states
- debug:
var: server_active
- debug:
var: server_pasive
run_once: true
- command: echo 'Shutdown active server'
register: out_active
delegate_to: "{{ server_active.0 }}"
- command: echo 'Shutdown passive server'
register: out_pasive
delegate_to: "{{ server_pasive.0 }}"
- debug:
msg: |
{{ server_active.0 }}: [{{ out_active.stdout }}] {{ out_active.start }}
{{ server_pasive.0 }}: [{{ out_pasive.stdout }}] {{ out_pasive.start }}
run_once: true
shell> ansible-playbook pb.yml -e host1=test_11 -e host2=test_13
PLAY [test_11,test_13] ***********************************************************************
TASK [command] *******************************************************************************
changed: [test_13]
changed: [test_11]
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_state.stdout: ACTIVE
ok: [test_13] =>
server_state.stdout: PASSIVE
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_states:
test_11: ACTIVE
test_13: PASSIVE
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_active:
- test_11
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_pasive:
- test_13
TASK [command] *******************************************************************************
changed: [test_11]
changed: [test_13 -> test_11]
TASK [command] *******************************************************************************
changed: [test_11 -> test_13]
changed: [test_13]
TASK [debug] *********************************************************************************
ok: [test_11] =>
msg: |-
test_11: [Shutdown active server] 2022-10-27 11:16:00.766309
test_13: [Shutdown passive server] 2022-10-27 11:16:02.501907
PLAY RECAP ***********************************************************************************
test_11: ok=8 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_13: ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
From the description of your use case I understand that you like to perform tasks on certain servers which a have service role installed (annot.: Terracotta Server) and based on a certain service state.
Therefore, I like to recommend an approach with Custom facts.
Depending on if you have control about where the currentstate.log is placed or how it is structured, you could use in example something like
cat /tmp/ansible/service/terracotta.fact
[currentstate]
ACTIVE = true
PASSIVE = false
or add dynamic facts by adding executable scripts to facts.d ...
Means, alternatively, you can add the current service state to your host facts by creating and running a script in facts.d, which would just read the content of /tmp/currentstate.log.
Then, a sample playbook like
---
- hosts: localhost
become: false
gather_facts: true
fact_path: /tmp/ansible/service
gather_subset:
- "!all"
- "!min"
- "local"
tasks:
- name: Show Gathered Facts
debug:
msg: "{{ ansible_facts }}"
when: ansible_local.terracotta.currentstate.active | bool
will result into an output of
TASK [Show Gathered Facts] ******
ok: [localhost] =>
msg:
ansible_local:
terracotta:
currentstate:
active: 'true'
passive: 'false'
gather_subset:
- '!all'
- '!min'
- local
module_setup: true
An other approach is to address How the inventory is build and Group the hosts
[terracotta:children]
terracotta_active
terracotta_passive
[terracotta_active]
terracotta1.example.com
[terracotta_passive]
terracotta2.example.com
You can then just easily and simple define where a playbook or task should run, just by Targeting hosts and groups
ansible-inventory -i hosts--graph
#all:
|--#terracotta:
| |--#terracotta_active:
| | |--terracotta1.example.com
| |--#terracotta_passive:
| | |--terracotta2.example.com
|--#ungrouped:
ansible-inventory -i hosts terracotta_active --graph
#terracotta_active:
|--terracotta1.example.com
or Conditionals based on ansible_facts, in example
when: 'terracotta_active' in group_names
... from my understanding, both would be minimal and simple solutions without re-implementing functionality which seems to be already there.

Display Ansible playbook with lookups interpolated

I have an Ansible playbook that looks, in part, like this:
...
environment:
F2B_DB_PURGE_AGE: "{{ lookup('env','F2B_DB_PURGE_AGE') }}"
F2B_LOG_LEVEL: "{{ lookup('env','F2B_LOG_LEVEL') }}"
SSMTP_HOST: "{{ lookup('env','SSMTP_HOST') }}"
SSMTP_PORT: "{{ lookup('env','SSMTP_PORT') }}"
SSMTP_TLS: "{{ lookup('env','SSMTP_TLS') }}"
...
Is there any way to run ansible-playbook so that it will show the results of the YAML file after replacing the lookups with their values? That is, I would like to be able to run something like ansible-playbook file.yaml --dry-run and see on standard output (assuming the environment variables were set appropriately):
...
environment:
F2B_DB_PURGE_AGE: "20"
F2B_LOG_LEVEL: "debug"
SSMTP_HOST: "smtp.example.com"
SSMTP_PORT: "487"
SSMTP_TLS: "true"
...
Set the environment for testing
shell> cat env.sh
#!/usr/bin/bash
export F2B_DB_PURGE_AGE="20"
export F2B_LOG_LEVEL="debug"
export SSMTP_HOST="smtp.example.com"
export SSMTP_PORT="487"
export SSMTP_TLS="true"
shell> source env.sh
Given the inventory
shell> cat hosts
localhost ansible_connection=local
Q: "Run something like ansible-playbook file.yaml --dry-run and see environment"
A: The below playbook does the job
shell> cat file.yml
- hosts: all
vars:
my_environment:
F2B_DB_PURGE_AGE: "{{ lookup('env','F2B_DB_PURGE_AGE') }}"
F2B_LOG_LEVEL: "{{ lookup('env','F2B_LOG_LEVEL') }}"
SSMTP_HOST: "{{ lookup('env','SSMTP_HOST') }}"
SSMTP_PORT: "{{ lookup('env','SSMTP_PORT') }}"
SSMTP_TLS: "{{ lookup('env','SSMTP_TLS') }}"
tasks:
- block:
- debug:
msg: |
my_environment:
{{ my_environment|to_nice_yaml|indent(2) }}
- meta: end_play
when: dry_run|d(false)|bool
- debug:
msg: Continue ...
Set dry_run=true
shell> ansible-playbook file.yml -e dry_run=true
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
my_environment:
F2B_DB_PURGE_AGE: '20'
F2B_LOG_LEVEL: debug
SSMTP_HOST: smtp.example.com
SSMTP_PORT: '487'
SSMTP_TLS: 'true'
TASK [meta] **********************************************************************************
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
By default, the playbook will execute tasks
shell> ansible-playbook file.yml
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
skipping: [localhost]
TASK [meta] **********************************************************************************
skipping: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: Continue ...
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Optionally, let the playbook gather facts and use the dictionary ansible_env. Use the filer ansible.utils.keep_keys to select your variables
- hosts: all
gather_facts: true
vars:
my_environment_vars:
- F2B_DB_PURGE_AGE
- F2B_LOG_LEVEL
- SSMTP_HOST
- SSMTP_PORT
- SSMTP_TLS
my_environment: "{{ ansible_env|
ansible.utils.keep_keys(target=my_environment_vars) }}"
tasks:
- block:
- debug:
msg: |
my_environment:
{{ my_environment|to_nice_yaml|indent(2) }}
- meta: end_play
when: dry_run|d(false)|bool
- debug:
msg: Continue ...

Play recap and ignored=1 in Ansible

I am checking whether all services related to a docker-compose.yml are running on a system. The code snippet shown below.
---
- name:
shell: docker-compose ps -q "{{ item }}"
register: result
ignore_errors: yes
The code above works as expected. I have to ignore errors otherwise Ansible will not complete. The following result shows ignored=1
PLAY RECAP *******************************************************************************************************
192.168.50.219 : ok=38 changed=12 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
If this completes successfully I want to run a subsequent playbook but don't know how to specify ignored=1 correctly.
---
- name:
include_tasks: do_other_things.yml
when: ignored is false
How do I get the result from PLAY RECAP into something I can test with?
A better idea than trying to cope with the error returned by docker compose ps when the container does not exist would be to use the purposed module: docker_container_info to achieve the same.
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- docker_container_info:
name: "{{ item }}"
register: containers
loop:
- node1 # exists
- node404 # does not exists
- debug:
msg: "`{{ item.item }}` is not started"
loop: "{{ containers.results }}"
loop_control:
label: "{{ item.item }}"
when: not item.exists
This would yield:
TASK [docker_container_info] *************************************************
ok: [localhost] => (item=node1)
ok: [localhost] => (item=node404)
TASK [debug] *****************************************************************
skipping: [localhost] => (item=node1)
ok: [localhost] => (item=node404) =>
msg: `node404` is not started

ansible loop over shell command output

I am learning ansible and I would like to know how to iterate of the results of a shell command. Here is what I have tried. I have this playbook:
[root#d61311ae17e2 /]# cat loop.yaml
---
- name: Loop Example
hosts: localhost
tasks:
- name:
command: cat /vcs.txt
register: vcs
- name: Nonsense to demo loop
template:
src: /foo.j2
dest: /foo.{{ item.1 }}
with_indexed_items: "{{ vcs }}"
The file /vcs.txt contains this:
[root#d61311ae17e2 /]# cat vcs.txt
vc-one
vc-two
vc-three
vc-four
What I was hoping would happen was the creation of four files: foo.vc-one, foo.vc-two, foo.vc-three and foo.vc-four. But what happens instead when I run ansible-playbook loop.yaml is this:
PLAY [Loop Example] *********************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************
ok: [127.0.0.1]
TASK [command] **************************************************************************************************************************************************
changed: [127.0.0.1]
TASK [Nonsense to demo loop] ************************************************************************************************************************************
fatal: [127.0.0.1]: FAILED! => {"msg": "with_indexed_items expects a list"}
to retry, use: --limit #/loop.retry
PLAY RECAP ******************************************************************************************************************************************************
127.0.0.1 : ok=2 changed=1 unreachable=0 failed=1
I needed to do this with_indexed_items: "{{ vcs.stdout.split('\n')}}"
If you need stdout on a line-by-line basis, with_indexed_items: "{{ vcs.stdout_lines }}" is equivalent to .split('\n') and likely simpler/clearer.

Ansible - Access inventory alias

Having an inventory file like:
[my_hosts]
my_host ansible_ssh_host=123.123.123.123
my_host2 ansible_ssh_host=234.234.234.234
I want to gather some debug information in my templates.
How do I acces the alias variable in a playbook/template?
I.e.:
debug: msg=Myhost is {{ ansible_host_alias }}
# Myhost is my_host
# Myhost is myhost2
I tried to gather facts using ansible -m setup my_host . The variables ansible_hostname , HOSTNAME and HOST contain the machine's hostname, i.e. echo $HOSTNAME which differs from my ansible alias.
The variable I was searching for is a built in feature: inventory_hostname
Ansible documentation about inventory_hostname and inventory_hostname_short is found from chapter Magic Variables, and How To Access Information About Other Hosts.
Original question: https://groups.google.com/forum/#!topic/ansible-project/Oa5YXjHecIw
You can use the inventory_hostname special variable.
Documentation: https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
Alternatively,
You can just simply use {{ ansible_ssh_host }} For example:
Inventory:
[my_hosts]
my_host ansible_ssh_host=127.0.0.1 my_host_alias=my_host
Playbook:
---
- name: My Good playbook
user: ubuntu
hosts: all
tasks:
- name: My message
debug: msg="Myhost is {{ ansible_ssh_host }}"
- name: My message bogus
debug: msg="My host alias is {{ my_host_alias }}"
Execution:
$ ansible-playbook -i inventory play.yml
PLAY [My Good playbook] *******************************************************
GATHERING FACTS ***************************************************************
ok: [my_host]
TASK: [My message] ************************************************************
ok: [my_host] => {
"msg": "Myhost is 127.0.0.1"
}
TASK: [My message bogus] ******************************************************
ok: [my_host] => {
"msg": "My host alias is my_host"
}
PLAY RECAP ********************************************************************
my_host : ok=3 changed=0 unreachable=0 failed=0

Resources