Ansible role checking path of dynamic variable within task - loops

I have a couple of ansible role tasks which I am using to setup a number of configuration directories. The problem is that I have a list of configurations which need to be setup with a certain path but only if they do not exist.
- name: Ensure core configuration directories exist.
shell: "cp -r {{ service_install_path }}/conf/ {{ service_home }}/{{ item }}.conf.d"
when:
- "item in conf_indexes_current.content"
- "not {{ service_home }}/{{ item }}.conf.d.stat.exists"
with_items: "{{ conf_dirs }}"
The problem however is that you can't stat a path like this:
- "not {{ service_home }}/{{ item }}.conf.d.stat.exists"
The error I am getting is this:
The conditional check 'not {{ service_home }}/{{ item
}}.conf.d.stat.exists' failed. The error was: unexpected '/'
line 1
Is there a way to set a dynamic variable full path and then test it? It does not seem like I can set a fact here either.
[update]: Just read another question trying to do something with a very vaguely similar loop concept. Is the correct method to simply use a shell script/template at this point?

Alright I did eventually figure this out with hints from bits and pieces of other Stack questions and random sites. I don't use ansible very often and we've only been using it for roughly a year. I ended up doing the following, breaking it into two tasks:
- name: Check existence of config for each index.
stat:
path: "{{ service_home }}/{{ item }}.conf.d"
register: "{{ item }}_conf_true"
with_items: "{{ conf_dirs }}"
- name: Ensure core configuration directories exist as full templates for modification.
shell: "cp -r {{ service_install_path }}/conf/ {{ service_home }}/{{ item }}.conf.d"
when:
- "item in conf_indexes_current.content"
- "{{ item }}_conf_true is not defined"
with_items: "{{ conf_dirs }}"
Does it work? Yes. Is it proper? Maybe but there is likely a better way I have not thought of or seen yet.

Related

Ansible: loop trough list of files of different users, sort by date, delete oldest x ones of each user

I am struggling with this issue since several days and would appreciate your help. I would like to check if the crontab file for several users exist on a host. If so, a backup shall be done and the oldest x backups shall be removed.
To achieve this I first check if the crontab exists:
- name: check if crontab exists
ansible.builtin.stat:
path: "/var/spool/cron/tabs/{{ item }}"
loop: "{{ users }}"
register: crontabFile
Then I perform a backup of the file:
- name: backup old crontab
ansible.builtin.copy:
src: "{{ item.stat.path }}"
dest: "{{ item.stat.path }}_{{ ansible_date_time.iso8601_basic_short }}"
mode: "preserve"
remote_src: true
loop: "{{ crontabFile.results }}"
when: item.stat.exists
This works fine so far. Afterwards I check for existing backups:
- name: find backups of crontab
ansible.builtin.find:
paths: "{{ item.stat.path | dirname }}"
patterns: "{{ item.stat.path | basename }}_*"
loop: "{{ crontabFile.results }}"
register: crontabBackup
when: item.stat.exists
The next step would be to loop over the results of crontabBackup and remove the oldest x files of the backups for each user individually.
The following does not work:
- name: delete oldest backups of crontab, keep last 5
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop: "{{ (crontabBackup.results|selectattr('files', 'defined')|map(attribute='files')|flatten|sort(attribute='mtime')|map(attribute='path')|list)[:-5] }}"
What happens is, that a flat list of all files is generated and the oldest files of this list get removed. What I want is to sort the list per user and remove the files per user.
After trying many different approaches, I think I've figured it out for myself. I will share my solution in case anyone is facing the same issue.
The solution is to use an outer loop that includes the inner loop via include_tasks as mentioned here.
# main.yml
- include_tasks: delete_backups_inner_loop.yml
loop: "{{ crontabBackup.results|selectattr('files', 'defined')|map(attribute='files') }}"
loop_control:
loop_var: userLoop
# delete_backups_inner_loop.yml
- name: delete oldest backups of crontab, keep last 5
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ (userLoop|sort(attribute='mtime'))[:-5] }}"

Ansible find matched patterns directory, remove all but keep the last 3 versions

I have surf the stackoverflow site around but couldn't find anything similar to what I want to achieve and hope that someone can point me out would be much appreciated.
I have a directory which stored all the artifacts Release Candidate and Dev versions when ever there is a bamboo build kick in. So far, I can figure out how to find the directory patterns and verify the results to remove. But could not filter the results to exclude the last 3 latest versions which I want to keep.
Here is the structures and the code
Structures
---
- name: Ansible find match directory, keep the last 3 version and remove all other
hosts: localhost
connection: local
vars:
base_dir: "/opt/repo/"
artifacts:
- "subject-mapper"
- "artemis-margin-api"
tasks:
- name: Find Release Candidate Directory Packages
become: yes
find:
paths: "{{ base_dir }}/{{ item }}"
patterns:
- "{{ item }}-[0-9]*.[0-9]*.[0-9]*$"
use_regex: yes
recurse: no
file_type: directory
loop: "{{ artifacts }}"
register: output
- debug:
msg: "{{ output }}"
- name: Filter out the Release Candidate results and keep the last 3 versions
set_fact:
files_to_delete: "{{ (files_to_delete|default([])) + (item['files'] | sort(attribute='mtime'))[:-3] }}"
loop: "{{ output['results'] }}"
- debug:
msg: "{{ files_to_delete }}"
- name: Delete the filtered results but keep the last 3 version
file:
path: "{{ item.path }}"
state: absent
loop: "{{ files_to_delete }}"
when: confirm|default(false)|bool
register: output_delete
- debug:
msg: "{{ output_delete }}"
here
Simplest way would be to pop first three matches from each result:
- set_fact:
files_to_delete: "{{ (files_to_delete|default([])) + (item['files'] | sort(attribute='mtime'))[3:] }}"
loop: "{{ output['results'] }}"

Can items in Ansible loop or with_items be processed concurrently?

- block:
- name: ensure backup directory exists
win_file:
path: "{{ BACKUP }}"
state: directory
recurse: yes
- include_tasks: increment_backup.yml
with_items: "{{ filelist.stdout_lines }}"
when: work_status.stat.exists
I just want these items be dealt with concurrently.
For example, there are 10 items within filelist.stdout_lines. What I wanna do is these 10 items can be processed parallel.
It's not task level. It's within one single task.
But I didn't find any clues from Ansible documents.
Does anybody know about this?

Ansible looping over nested variables

I have a set of variables which define FQDNs.
domains:
- erp: erp.mycompany.com
- crm: crm.mycompany.com
- git: git.mycompany.com
Indeed, I both need to loop over them and access them namely (in a template file). So accessing them like domains.erpworks like a charm. But I can't get ansible to loop over these.
Obviously, if I do:
- name: Print domains
debug:
msg: test {{ item }}
with_items:
- "{{ domains }}"
It prints both the key and the value… And if I do:
- name: Print domains
debug:
msg: test {{ domains[{{ item }}] }}
with_items:
- "{{ domain }}"
But that doesn't work. I also tried the hashes form as mentionned in the docs, but didn't get any luck either…
Finally, I had to use a dict.
It didn't work the first time because unlike with_items, which has the items going each on their own line, with_dict is a one liner without - before the element to loop through.
domains:
erp:
address: erp.mycompany.com
crm:
address: crm.mycompany.com
git:
address: git.mycompany.com
# used by letsencrypt
webserverType: apache2
withCerts: true
tasks:
- name: Print phone records
debug:
msg: "{{ item.value.address }}"
with_dict: "{{ domains }}"
# I can still access a given domain by its name when needed like so:
{{ domains.erp.address }}
Looks like you figured out your issue. Your original attempt uses a list of dictionaries that do not contain the same keys, making it difficult to access the values uniformly across each list item.
Your second solution creates a dictionary where the keys refer to other dictionaries.
Another solution than what you posted if you still wanted to use a list:
- hosts: localhost
vars:
domains:
- name: erp
address: erp.mycompany.com
- name: crm
address: crm.mycompany.com
- name: git
address: git.mycompany.com
tasks:
- name: Print phone records
debug:
msg: "{{ item.address }}"
with_items: "{{ domains }}"
To me this approach is simpler but your second approach works as well.

ansible loop over list and dictionary at the same time

I am writing a playbook that ensure nodes appear in /etc/fstab.
I am using loops to prevent code duplication.
The logic is first to check if the line appears using grep (with perl regex because it is a multi line) and store the results in a register.
Then I want to add only the lines that are not in fstab file. To achieve that I need to loop over list (the register with the grep return codes) and a dictionary (that contains the fstab entries).
I am having errors with the parallel loop. I tried to follow these steps.
One or more undefined variables: 'str object' has no attribute 'item'
tasks/fstab.yaml:
---
- name: Make dirs
sudo: yes
file: path={{ item.value }} state=directory
with_dict:
"{{ fstab.paths }}"
- name: Check whether declared in fstab
sudo: no
command: grep -Pzq '{{ item.value }}' /etc/fstab
register: is_declared
with_dict:
"{{ fstab.regexs }}"
- name: Add the missing entries
sudo: yes
lineinfile: dest=/etc/fstab line="{{ item.1.item.value }}"
when: item.0.rc == 1
with_together:
- "{{ is_declared.results }}"
- "{{ fstab.entries }}"
vars/main.yml:
---
fstab:
paths:
a: "/mnt/a"
b: "/mnt/b"
regexs:
a: '\n# \(a\)\nfoo1'
b: '\n# \(b\)\nfoo2'
entries:
a: "\n# (a)\nfoo1"
b: "\n# (b)\nfoo2"
I am not using template on purpose (I want to add entries to existing files and not to over write them).
UPDATE: I see ansible has module "mount" which deals with fstab. However I am still looking for a solution to this issue because I might be needed it again later on.
I have a couple ideas as to why your original approach was failing, but let's scratch that for a moment. It looks like you're overcomplicating things- why not use a complex list var to tie it all together, and use the regexp arg to the lineinfile module instead of a separate regex task? (though your sample data should work fine even without the regexp param) Something like:
---
- name: Make dirs
sudo: yes
file: path={{ item.path }} state=directory
with_items: fstab
- name: Add the missing entries
sudo: yes
lineinfile: dest=/etc/fstab line={{ item.entry }} regexp={{ item.regex }}
with_items: fstab
fstab:
- path: /mnt/a
regex: '\n# \(a\)\nfoo1'
entry: "\n# (a)\nfoo1"
- path: /mnt/b
regex: '\n# \(b\)\nfoo2'
entry: '\n# (b)\nfoo2'

Resources