Ansible: loops with inventory groups elements - ansible

I got a bit stuck in the following problem. I have an inventory file similar with:
[server]
server0 ansible_host=10.254.1.35
server1 ansible_host=10.254.1.46
[worker]
worker0 ansible_host=10.254.1.10
worker1 ansible_host=10.254.1.12
worker2 ansible_host=10.254.1.13
and I want to make a role that tests the connectivity between all machines from two groups (server0 to all workers, server2 with all workers etc).
So something like:
- hosts: "{{ target | default('server') }}"
tasks
- name: Test connectivity
shell: |
ping {{ worker_machine_ip }}
args:
executable: /bin/bash
where the worker_machine_ip to be replaced with the IP of each worker from the inventory file. Is this possible ?

You can use the special variable groups to list all hosts in a specific group, then the other sepcial variable hostvars to get the IP of that other host you want to ping:
- hosts: "{{ target | default('server') }}"
tasks:
- name: Test connectivity
shell: ping -c 3 {{ hostvars[item].ansible_host }}
loop: "{{ groups['worker'] }}"
args:
executable: /bin/bash

Related

Ansible assign hostname using inventory items

I'm trying to assign hostname to a few hosts, gathering it from inventory.
My inventory is:
[masters]
master.domain.tld
[workers]
worker1.domain.tld
worker2.domain.tld
[all:vars]
ansible_user=root
ansible_ssh_private_key_file=~/.ssh/id_rsa
The code I'm using is:
- name: Set hostname
shell: hostnamectl set-hostname {{ item }}
with_items: "{{ groups['all'] }}"
Unfortunately, code iterate all items (both IPs and hostnames) and assigns to all 3 hosts the last item...
Any help would be much appreciated.
Don't loop: this is going through all your hosts on each target. Use only the natural inventory loop and the inventory_hostname special var.
Moreover, don't use shell when there is a dedicated module
- name: Assign hostname
hosts: all
gather_facts: false
tasks:
- name: Set hostname for target
hostname:
name: "{{ inventory_hostname }}"

How to let Ansible run a command with host as argument locally

I installed servers at multiple hosts using ansible playbook. the hosts are defined at inventory file:
[services]
host_ip1
host_ip1
...
Now I need to test if each host works properly using command:
service-cli -h <host_ip1> -p 6380 ping
...
How do I write that using ansible? it is for sure ansible will run the command at local not remote, Then how do I pass the host_ipX to ansible?
I know how to use delegate_to: 127.0.0.1, I do not know how to get the ip of inventory file using with_items, what keywords should I set? services?
Your use case is literally documented in the extract examples
- debug:
msg: service-cli -h {{ item }} -p 6380 ping
with_items: '{{ groups["services"] | map("extract", hostvars, "ansible_host") }}'
delegate_to: localhost
# this is required if you want the task as part of a playbook
# that contains "hosts: all"
run_once: yes
or, as an alternative to the "run_once" you can isolate that as its own play:
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: service-cli -h {{ item }} -p 6380 ping
with_items: '{{ groups["services"] | map("extract", hostvars, "ansible_host") }}'
- hosts: all
... # and now back to normal life

Run ansible task only once per each unique fact value

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.

ansible multiple per-host output to file

I want to run an Ansible playbook on multiple hosts and register outputs to a variable. Now using this variable, I want to copy output to single file. The issue is that in the end there is output of only one host in the file. How can I add output of all the hosts in a file one after the other. I don't want to use serial = 1 as it slows down execution considerably if we have multiple hosts.
-
hosts: all
remote_user: cisco
connection: local
gather_facts: no
vars_files:
- group_vars/passwords.yml
tasks:
- name: Show command collection
ntc_show_command:
connection: ssh
template_dir: /ntc-ansible/ntc-templates/templates
platform: cisco_ios
host: "{{ inventory_hostname }}"
username: "{{ ansible_ssh_user }}"
password: "{{ ansible_ssh_pass }}"
command: "{{commands}}"
register: result
- local_action:
copy content="{{result.response}}" dest='/home/user/show_cmd_ouput.txt'
result variable will be registered as a fact on each host the task ntc_show_command was run, thus you should access the value through hostvars dictionary.
- local_action:
module: copy
content: "{{ groups['all'] | map('extract', hostvars, 'result') | map(attribute='response') | list }}"
dest: /home/user/show_cmd_ouput.txt
run_once: true
You also need run_once because the action would still be run as many times as hosts in the group.

Ansible first hostname of groups

I am not sure on how to find the first ansible hostname from group_names. Could you kindly advise me on how to do it?
hosts
[webservers]
server1
server2
server3
[webserver-el7]
server4
server5
server6
And i have 2 different playbook for each host groups
playbook1.yml
- name: deploy app
hosts: webservers
serial: 8
roles:
- roles1
playbook2.yml
- name: deploy app
hosts: webservers-el7
serial: 8
roles:
- roles1
the problem is that i have delegate task to first host of each group. previously i only used webservers group, so it was much easier by using the task below
- name: syncing web files to {{ version_dir }}
synchronize:
src: "{{ build_dir }}"
dest: "{{ version_dir }}"
rsync_timeout: 60
delegate_to: "{{ groups.webservers | first }}"
If i have 2 different group_names, how can i select the first one of each group? so it can be more dynamic
If you want the first host of current play to be a kind of master host to sync from, I'd recommend another approach: use one of play_hosts or ansible_play_hosts (depending on your Ansible version) variables. See magic variables.
Like delegate_to: "{{ play_hosts | first }}".
The thing is when you say hosts: webservers-el7 to Ansible webservers-el7 is a pattern here. Ansible search for hosts to match this pattern and feed them into Play. You may have written webservers-el* as well. So inside Play you don't have any variable that will tell you "I'm running this Play on hosts from group webserver-el7...". You may only make some guess work analyzing group_names and groups magic variables. But this become clumsy when you have one host in several groups.
For hosts in single group only, you may try: groups[group_names | first] | first
To get any element from group, use group[group_name][0...n].
This will get the first element from the group.
- debug: msg="{{ groups['group_name'][0] }}"

Resources