I have a list of entities that I need to remove, but if I iterate the loop too quickly, the removal may fail as the operation can only be done serially and needs approx 10 seconds between removals. So, I am doing this
- name: Loop through removing all hosts
shell: "echo yes | gravity remove --force {{ item }}"
loop: "{{ result.stdout_lines }}"
loop_control:
pause: 12
this generally works fine, but very occasionally I may get an error when the 12 seconds is not enough. I don't want to increase the pause, so am trying to figure out how to test and retry any failures.
A simple additional pause and retry again if an individual node fails would work. Any idea how I can do this?
Duh! I had originally tried an until, but stupidly forgot to register the var, so it wasn't working, but this now works
- name: Loop through removing all hosts
shell: "echo yes | gravity remove --force {{ item }}"
register: result
loop: "{{ file_list.stdout_lines }}"
loop_control:
pause: 12
retries: 2
delay: 12
until: result is not failed
Related
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
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.
Not sure how to have this logic implemented, I know how to do it a single file :
- name: Obtain information about a file
win_stat:
path: "C:\myfile.txt"
register: fileinfo
- [...]
when: fileinfo.exists == False
how should I go with a list of files?
If you just want to reduce the steps for doing this, you should be able to do your download step (not shown in your example) with ignore_errors: yes on your download commands. If you use a combination of ignore_errors: yes and register, you can even tell whether the command failed.
If you're looking to make it a bit more efficient, you can do the stat in a single task and then examine the results of that. When you execute a task with a list, you get a hash of answers.
Assuming you have a list of file names/paths in ssh_key_config, you use the stat and then you can loop over the items (which conveniently have the file name in them).
- name: Check to see if file exists
stat:
path: "{{ remote_dir }}/{{ item }}"
register: stat_results
with_items: "{{ target_files }}"
ignore_errors: True
- name: perform operation
fetch:
src: "{{ remote_dir }}/{{ item.item }}"
dest: "{{ your_dest_dir }}"
flat: yes
with_items: "{{ stat_results.results }}"
when: item.stat.exists == False
In this case, the assumptions are that remote_dir contains the remote directory on the host, target_files contains the actual file names, and your_dest_dir contains the location you want the files placed locally.
I don't do much with Windows and Ansible, but win_stat is documented pretty much the same as stat, so you can likely just replace that.
Also note that this expects the list of files, not a glob. If you use a glob (for example, you want to retrieve all files with a certain extension from the remote), then you would not use the with_items clause, and you'd need to use the item.stat.filename and/or item.stat.path to retrieve the file remotely (since the item.item would contain the request item, which would be the glob.
Prior to Ansible 2.5, the syntax for loops used to be with_x. Starting at 2.5, loop is favored and with_x basically disappeared from the docs.
Still, the docs mention exemples of how to replace with_x with loop. But I'm clueless as to how we're now supposed to loop through a directory of files.
Let's say I need to upload all the files within a given dir, I used to use with_fileglob.
- name: Install local checks
copy:
src: "{{ item }}"
dest: /etc/sensu/plugins/
owner: sensu
group: sensu
mode: 0744
with_fileglob:
- plugins/*
So what's the modern equivalent? Is it even possible? I know I still can use with_fileglob but as I'm writing new roles, I'd better have them future-proof.
The equivalent is
loop: "{{ lookup('fileglob', 'plugins/*', wantlist=True) }}"
Here is the doc.
From the current Ansible loops doc:
Any with_* statement that requires using lookup within a loop should
not be converted to use the loop keyword. For example, instead of
doing:
loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"
it’s cleaner to keep:
with_fileglob: '*.txt'
ansible version 1.9.1
cat files.yml
tasks:
- name: stat files
stat: path=~/{{ item }}
register: {{ item }}.stat
with_items:
- foo.zip
- bar.zip
- name: copy files
copy: src=~/{{ item }} dest=/tmp/{{ item }}
register: {{ item }}.result
when: {{ item }}.stat.stat.exists == False
with_items:
- foo.zip
- bar.zip
- name: unzip files
shell: cd /tmp/ && unzip -o {{ item }}
when: {{ item }}.result|changed == True
with_items:
- foo.zip
- bar.zip
ERROR: Syntax Error while loading YAML script
If so, how?
The preferred way would be to use synchronize module.
From ansible documentation ( http://docs.ansible.com/ansible/synchronize_module.html#synopsis )
This is a wrapper around rsync. Of course you could just use the command action to call rsync yourself, but you also have to add a fair number of boilerplate options and host facts. You still may need to call rsync directly via command or shell depending on your use case. The synchronize action is meant to do common things with rsync easily. It does not provide access to the full power of rsync, but does make most invocations easier to follow.
Here is an example:
- name: Sync files
synchronize: src=some/relative/path dest=/some/absolute/path