How do i loop over hosts identified by wildcards in Ansible - loops

Below is my playbook that works fine.
---
- hosts: "PROD_SERVERS"
user: "{{USER}}"
tasks:
- name: Check if all hosts are reachable
fail:
msg: "Server is UNREACHABLE."
when: "hostvars[item].ansible_facts|list|length == 0"
with_items: "{{ groups.PROD_SERVERS }}"
However, can you help me with the syntax when the host is presented as wildcard i.e {{ENV}}_*?
---
- hosts: "{{ENV}}_*"
user: "{{USER}}"
tasks:
- name: Check if all hosts are reachable
fail:
msg: "Server is UNREACHABLE."
when: "hostvars[item].ansible_facts|list|length == 0"
with_items: "{{ groups.{{ENV}}_* }}" <------- Need help with this part of the code

There are special variables also called as "magic variables" to identify the hosts in the current play. They are:
ansible_play_hosts
ansible_play_batch
You can use one of these variables to loop with instead of the inventory group name.
Example with ansible_play_hosts:
- fail:
msg: "Server is UNREACHABLE."
when: hostvars[item].ansible_facts|list|length == 0
with_items: "{{ ansible_play_hosts }}"
Update:
Changed the example for Ansible version "2.4.x", the loop should be performed with with_items rather than loop. Quoting from official documentation
We added loop in Ansible 2.5. It is not yet a full replacement for with_<lookup>, but we recommend it for most use cases.
Note: For Ansible "2.9.16" and "2.10.2" loop works as well.

Related

How do I use Ansible to add an IP to a Linux hosts file on 18 VMs that iterates from 10.x.x.66..10.x.x.83

So, I have a playbook using a hosts file template to update or revert hosts files on 18 specific Linux VMs. The entry which goes at the end of the file looks like:
10.x.x.66 fooconnect
This above example would be on the 1st of 18 VMs, the 18th VM would look like:
10.x.x.83 fooconnect
Normally, that hostname resolves to a VIP. However, we found during some load testing that it may be beneficial to point each front-end VM to a back-end VM directly. So, my goal is to have a playbook that can update what the hostname resolves to with the above mentioned range, or revert it back to the VIP (reverting back is done using a template only--this part works fine).
What I am unsure about is how to implement this in Ansible. Is there a way to loop through the IPs using jinja2 template "for loops?" Or maybe using lineinfile with some loop magic?
Here is my Ansible role example. For the moment I am using a dirty shell command to create my IP list...open to suggestions for a better way to implement this.
- name: Add a line to a hosts file using a template
template:
src: "{{ srcfile }}"
dest: "{{ destfile }}"
owner: "{{ own_var }}"
group: "{{ grp_var }}"
mode: "{{ mode_var }}"
backup: yes
- name: Get the IPs
shell: "COUNTER=66;for i in {66..83};do echo 10.x.x.$i;((COUNTER++));done"
register: pobs_ip
- name: Add a line
lineinfile:
path: /etc/hosts
line: "{{item}} fooconnect" #Ideally would want "item" to just be one IP and not
insertafter: EOF #the entire list as it would be like this.
loop: "{{pobsips}}"
VARs file:
pobsips:
- "{{pobs_ip.stdout}}"
Instead of using a shell task, we can improvise it and create the range of IP addresses using set_fact with range. Once we have the range of IP addresses in a "list", we can loop lineinfile with that and achieve this.
Example:
- name: create a range of IP addresses in a variable my_range
set_fact:
my_range: "{{ my_range|default([]) + [ '10.1.1.' ~ item ] }}"
loop: "{{ range(66, 84)|list }}"
- name: Add a line to /etc/hosts
lineinfile:
path: /etc/hosts
line: "{{ item }} fooconnect"
insertafter: EOF
loop: "{{ my_range }}"
Updated answer:
There is another approach if we want to append only 1 line into the /etc/hosts file of each host with incrementing IP addresses.
For this we can use the ipmath of ipaddr filter to get the next IP address for given IP address.
Use ansible_play_hosts to get the list of hosts on which play is running
Set an index variable index_var and when condition to update file only when the ansible_hostname or inventory_hostname matches.
Run playbook serially and only once on a host per run using serial and run_once flags.
Let's consider an example inventory file like:
[group_1]
host1
host2
host3
host4
...
Then in playbook:
- hosts: group_1
serial: 1
vars:
start_ip: 10.1.1.66
tasks:
- name: Add a line to /etc/hosts
lineinfile:
path: "/tmp/hosts"
line: "{{ start_ip|ipmath(my_idx) }} fooserver"
insertafter: EOF
loop: "{{ ansible_play_hosts }}"
loop_control:
index_var: my_idx
run_once: true
when: item == inventory_hostname

How to pass a register value into a include_role loop

I have an ansible playbook that is reading in a list of files, and setting a register for those values. I want to then pass the list of files into an include_role task. Below is my current code.
- name: Get list of files
command: "sh -c 'find playbooks/vars/files/*.yml'"
register: find_files
- include_vars:
file: "{{ item }}"
loop: "{{ find_files.stdout_lines }}"
register: result
- name: call role
include_role:
name: myRole
loop: "{{ result.results }}"
When the playbook runs, its finds two files in the directory; file1.yml and file2.yml. But when it runs through the include_role loop, its passes file1.yml twice and never passes file2.yml. Trying to determine how I can ensure file2.yml gets passed to the role as well.
I was able to find the correct my issue by constructing an array and then feeding it into include_role and using the find module instead of shell.
- name: Recursively find yml files
find:
paths: ~/vars
recurse: yes
register: find_files
- name: Construct file array
set_fact:
file_arr: "{{ file_arr|default([]) + [file.path] }}"
with_items: "{{ find_files.files }}"
loop_control:
loop_var: file
- name: Topic Management
include_role:
name: kafkaTopicManagement
vars:
kafkaFiles: "{{ item }}"
with_items: "{{ file_arr }}"
This now feeds the the files into include_role.

Error with Ansible include_tasks while adding dynamic hosts in nested loop

Below is the playbook with import_tasks get_hosts.yml for building dynamic hosts in a nested loop. However, I get syntax error running the playbook.
{{ item.split('\t')[0] }} will have ip addresses separated by commas , and then a string seperated by /t
---
- name: "Play 1"
hosts: localhost
tasks:
- name: "Search database"
command: > mysql --user=root --password=p#ssword deployment
--host=localhost -Ns -e "SELECT dest_ip,file_dets FROM deploy_dets"
register: command_result
- name: Add hosts
include_tasks: "{{ playbook_dir }}/gethosts.yml"
dest_ip: "{{ item.split('\t')[0] }}"
groups: dest_nodes
file_dets: "{{ item.split('\t')[1] }}"
ansible_host: localhost
ansible_connection: local
with_items: "{{ command_result.stdout_lines }}"
And below is my get_hosts.yml file
add_host:
name: "{{ item }}"
with_items: "{{ dest_ip.split(',') }}"
Output:
$ ansible-playbook testinclude.yml
ERROR! Syntax Error while loading YAML. did not find expected key
The error appears to be in '/app/deployment/testinclude.yml': line 23, column 8, but may be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
include_tasks: "{{ playbook_dir }}/gethosts.yml"
dest_ip: "{{ item.split('\t')[0] }}"
^ here We could be wrong, but this one looks like it might be an issue with missing quotes. Always quote template expression brackets when they start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
Can you please suggest
Perhaps you forgot vars parameter, so:
include_tasks: "{{ playbook_dir }}/gethosts.yml"
vars: # <------------------------------------------- HERE
dest_ip: "{{ item.split('\t')[0] }}"
groups: dest_nodes

The task includes an option with an undefined variable

I have the below vars file and Ansible task which allows some ports for a specific source IPs on the firewall.
But for some reason, whenever I run the task I got this error:
FAILED! => {"msg": "The task includes an option with an undefined variable.
Even though I'm able to echo out these variables using debug Ansible module, so basically I'm just unable to use these variable specifically in this task.
Ansible Task:
---
- name:
firewalld:
permanent: yes
port: "{{ item.0 }}"
state: enabled
zone: public
source: "{{ item.1 }}"
with_nested:
- "{{ ports }}"
- "{{ ips }}"
Vars File:
ports:
- "8000/tcp"
- "9997/tcp"
- "8181/tcp"
- "8080/tcp"
- "8191/tcp"
- "8088/tcp"
- "8065/tcp"
ips:
- "192.168.210.30/32"
- "192.168.210.35/32"
- "192.168.210.40/32"
- "192.168.210.31/32"
- "192.168.210.36/32"
- "192.168.210.41/32"
- "192.168.210.42/32"
- "192.168.210.37/32"
The indentation of with_nested is wrong. Correct syntax is
- name:
firewalld:
permanent: yes
port: "{{ item.0 }}"
state: enabled
zone: public
source: "{{ item.1 }}"
with_nested:
- "{{ ports }}"
- "{{ ips }}"

How to get the list of remote files under a Dir and iterate over to list out the contents via ansible

Does anyone knows how to get the list of remote files under a particular Dir and to iterate over them and list out the contents of each file via ansible?
For example , i have a location /var/spool/cron and it has many files which i need to cat <file> by iterate over each of them.
fileglob and lookup works locally.
Below is the play but not working as expected.
---
- name: Playbook to quick check the cron jobs for user
hosts: all
remote_user: root
gather_facts: False
tasks:
- name: cron state
shell: |
ls /var/spool/cron/
register: cron_status
- debug: var=item
with_items:
- "{{ cron_status.stdout_lines }}"
Try this as an example
---
- name: Playbook
hosts: all
become: root
gather_facts: False
tasks:
- name: run ls-
shell: |
ls -d1 /etc/cron.d/*
register: cron_status
- name: cat files
shell: cat {{ item }}
register: files_cat
with_items:
- "{{ cron_status.stdout_lines }}"
- debug: var=item
with_items:
- "{{ files_cat.results| map(attribute='stdout_lines')|list }}"
Just for the sake you someones Intrest..
Below Play will give you more cleaner stdout
---
- name: hostname
hosts: all
become: root
gather_facts: False
tasks:
- name: checking cron entries
shell: more /var/spool/cron/*
register: cron_status
- debug: var=cron_status.stdout_lines

Resources