Adding paths to config file in most efficent way via Ansible - regex

I wrote a task that is responsible for changing supervisor config file. The case is that on some servers we have more than one app running workers, so sometimes more than one path needs to be added to include section of supervisor.conf.
Currently I wrote this task in /roles/supervisor/tasks/main.yml/:
- name: Add apps paths in include section
lineinfile:
dest: /etc/supervisor/supervisord.conf
regex: '^files ='
line: 'files = /etc/supervisor/conf.d/*.conf /home/app/{{ app_name }}/releases/app/shared/supervisor/*.conf /home/dev/{{ app_name2 }}/releases/dev/shared/supervisor/*.conf'
when: ansible_hostname = 'ser-db-10'
notify: restart supervisor
tags: multi_workers
... and added in /roles/supervisor/defaults/main.yml/ this:
app_name: bla
app_name2: blabla
It works, but I don't like the thing that there are two application paths hardcoded in line and maybe I should also add variable in place of ser-db-10.
I am wondering how to rebuild this task to make it more independent.
What I mean is, if there are 4 apps, add 4 paths, if there are 2 apps, add 2 paths.
What is the most efficient way to do this?

As an example of how to put together the parameter line, the play below
- hosts: test_01
vars:
app_name1: A
app_name2: B
my_conf:
test_01:
lines:
- '/etc/*.conf'
- '/etc/{{ app_name1 }}/*.conf'
- '/etc/{{ app_name2 }}/*.conf'
tasks:
- debug:
msg: "files = {{ my_conf[inventory_hostname].lines|join(' ') }}"
gives
"msg": "files = /etc/*.conf /etc/A/*.conf /etc/B/*.conf"
With appropriate dictionary my_conf the task below should do the job
- name: Add apps paths in include section
lineinfile:
dest: /etc/supervisor/supervisord.conf
regex: '^files ='
line: "files = {{ my_conf[inventory_hostname].lines|join(' ') }}"
notify: restart supervisor
tags: multi_workers
(not tested)

Related

Looking to see if a key from one set of imported variables matches another so it's value can be procured

I have roughly formatted yml files with key/value pairs in them. I then imported the values of both of these files successfully into a running playbook using the include_vars module.
Now, I want to be able to compare the value of the key/value pair from file/list 1, to all of the keys of file/list 2. Then finally when there is a match, print and preferably save/register the value of the matching key from file/list 2.
Essentially I am comparing a machine name to an IP list to try to grab the IP the machine needs out of that list. The name is "dynamic" and is different each time the playbook is run, as file/list 1 is always dynamically populated on each run.
Examples:
file/list 1 contents
machine_serial: m60
s_iteration: a
site_name: dud
t_number: '001'
file/list 2 contents
m51: 10.2.5.201
m52: 10.2.5.202
m53: 10.2.5.203
m54: 10.2.5.204
m55: 10.2.5.205
m56: 10.2.5.206
m57: 10.2.5.207
m58: 10.2.5.208
m59: 10.2.5.209
m60: 10.2.5.210
m61: 10.2.5.211
In a nutshell, I want to be able to get the file/list 1 ct_machine_serial key who's value is currently: m60 to be able to find it's key match in file/list 2, and then print and/or preferably register it's value of 10.2.5.210.
What I've tried so far:
Playbook:
- name: IP gleaning comparison.
hosts: localhost
remote_user: ansible
become: yes
become_method: sudo
vars:
ansible_ssh_pipelining: yes
tasks:
- name: Try to do a variable import of the file1 file.
include_vars:
file: ~/active_ct-scanner-vars.yml
name: ctfile1_vars
become: no
- name: Try to do an import of file2 file for lookup comparison to get an IP match.
include_vars:
file: ~/machine-ip-conversion.yml
name: ip_vars
become: no
- name: Best, but failing attempt to get the value of the match-up IP.
debug:
msg: "{{ item }}"
when: ctfile1_vars.machine_serial == ip_vars
with_items:
- "{{ ip_vars }}"
Every task except the final one works perfectly.
My failed output final task:
TASK [Best, but failing attempt to get the value of the match-up IP.] ***********************************************************************************
skipping: [localhost] => (item={'m51': '10.200.5.201', 'm52': '10.200.5.202', 'm53': '10.200.5.203', 'm54': '10.200.5.204', 'm55': '10.200.5.205', 'm56': '10.200.5.206', 'm57': '10.200.5.207', 'm58': '10.200.5.208', 'm59': '10.200.5.209', 'm60': '10.200.5.210', 'm61': '10.200.5.211'})
skipping: [localhost]
What I hoped for hasn't happened, it simply skips the task, and doesn't iterate over the list like I was hoping, so there must be a problem somewhere. Hopefully there is an easy solution to this I just missed. What could be the correct answer?
Given the files
shell> cat active_ct-scanner-vars.yml
machine_serial: m60
s_iteration: a
site_name: dud
t_number: '001'
shell> cat machine-ip-conversion.yml
m58: 10.2.5.208
m59: 10.2.5.209
m60: 10.2.5.210
m61: 10.2.5.211
Read the files
- include_vars:
file: active_ct-scanner-vars.yml
name: ctfile1_vars
- include_vars:
file: machine-ip-conversion.yml
name: ip_vars
Q: "Compare the machine name to an IP list and grab the IP."
A: Both variables ip_vars and ctfile1_vars are dictionaries. Use ctfile1_vars.machine_serial as index in ip_vars
match_up_IP: "{{ ip_vars[ctfile1_vars.machine_serial] }}"
gives
match_up_IP: 10.2.5.210
Example of a complete playbook for testing
- hosts: localhost
gather_facts: false
vars:
match_up_IP: "{{ ip_vars[ctfile1_vars.machine_serial] }}"
tasks:
- include_vars:
file: active_ct-scanner-vars.yml
name: ctfile1_vars
- include_vars:
file: machine-ip-conversion.yml
name: ip_vars
- debug:
var: match_up_IP

How can I use regex in when condition

On an Ansible playbook, I'm trying to execute a shell command only if a service exist on the remote server.
I have 3 tasks :
service_facts
execution of shell command if tomcat is installed
display the output of the shell command if tomcat is installed
Here is my code :
- name: Get Infos
hosts: all
gather_facts: yes
become: false
remote_user: [MY_USER]
tasks:
- name: Get the list of services
service_facts:
- name: Get version of Tomcat if installed
become: true
shell: 'java -cp /opt/tomcat/lib/catalina.jar org.apache.catalina.util.ServerInfo | grep "Server version"'
register: tomcat_version
when: "'tomcat.service' in services"
- debug: msg="{{ tomcat_version.stdout_lines }}"
when: "'tomcat.service' in services"
The problem is on certains servers the service name is, for example, tomcat-8.1
How can i use regex in the when condition?
I tried regex(), regex_search(), either I'm doing it wrong or I don't know how to do it.
Have you any idea how to do it?
Thanks in advance!
Count matching items. For example
- service_facts:
- block:
- shell: smartctl --version | head -1
register: smart_version
- debug:
msg: "{{ smart_version.stdout_lines }}"
when: _srvcs|length > 0
vars:
_regex: '.*smart.*'
_srvcs: "{{ services|select('match', _regex) }}"
gives
msg:
- smartctl 7.1 2019-12-30 r5022 [x86_64-linux-5.4.0-73-generic] (local build)
The next option is to intersect the list of services, e.g.
when: _srvcs|length > 0
vars:
my_services:
- smartmontools.service
- smart-8.1
- smart-devel.0.0.1
_srvcs: "{{ my_services|intersect(services) }}"
Debug
Q: "It gives me a failure on the server where my service doesn't exist, cause the playbook still tries to execute the shell. Is it normal?"
A: No. It is not normal. Print debug and find out why the condition evaluates to true, i.e. what service(s) match either the regex or the list. For example
- debug:
msg: |
_srvcs:
{{ _srvcs|to_nice_yaml|indent(2) }}
when: debug|d(false)|bool
vars:
my_services:
- smartmontools.service
- smart-8.1
- smart-devel.0.0.1
_srvcs: "{{ my_services|intersect(services) }}"
gives
msg: |-
_srvcs:
- smartmontools.service
To enable the task run the playbook with the option -e debug=true.

Ansible lineinfile regexp to manage /etc/exports

I've been hitting a wall trying to get /etc/exports managed via Ansible.
I've got a role that installs a piece of software on a VM, and I want to then add an entry ot /etc/exports on the NFS server, for that specific VM, so it's able to access the NFS shares needed.
Lineinfile sounds like the way to go, but sofar I can't figure out how to properly write this.
I want this to:
not modify if the host is in the line, no matter where
add the NFS share and the host if there's no line for the NFS share
add the host to the share in case it isn't in there.
The latest installment of my 'add to /etc/exports' that thought should work, but doesn't, is:
- name: Add hosts to mountpoint line
ansible.builtin.lineinfile:
path: /etc/exports
line: '\1 {{ host_ip }}(root_squash,no_subtree_check)'
regex: '^((?!{{ volume_mountpoint }}.*{{ host_ip }}\(root_squash,no_subtree_check\).*).*)$'
backrefs: yes
but i'm still getting all kinds of weird side effects. I've used backreferences etc before, but somehow this one keeps tripping me up.
Anyone who sees what's gong wrong?
Typical /etc/exports entry:
/srv/files 172.16.0.14(rw,no_root_squash,no_subtree_check)
It's not possible in one step to modify a line using backreferences or add the line if missing. To modify the existing mount point the back-references are needed. For example, given the files for testing
shell> cat etc/export1
/srv/files 172.16.0.14(rw,no_root_squash,no_subtree_check)
shell> cat etc/export2
/srv/files 172.16.0.15(rw,no_root_squash,no_subtree_check)
shell> cat etc/export3
/srv/download 172.16.0.14(rw,no_root_squash,no_subtree_check)
the task
tasks:
- lineinfile:
path: "etc/{{ item }}"
regex: '^{{ mount }}(\s+)({{ ipr }})*({{ optionsr }})*(\s*)(.*)$'
line: '{{ mount }}\g<1>{{ ip }}{{ options }} \g<5>'
backrefs: true
vars:
mount: /srv/files
ipr: '172\.16\.0\.14'
ip: '172.16.0.14'
optionsr: '\(.*?\)'
options: '(root_squash,no_subtree_check)'
loop:
- export1
- export2
- export3
gives
--- before: etc/export1 (content)
+++ after: etc/export1 (content)
## -1 +1 ##
-/srv/files 172.16.0.14(rw,no_root_squash,no_subtree_check)
+/srv/files 172.16.0.14(root_squash,no_subtree_check)
changed: [localhost] => (item=export1)
--- before: etc/export2 (content)
+++ after: etc/export2 (content)
## -1 +1 ##
-/srv/files 172.16.0.15(rw,no_root_squash,no_subtree_check)
+/srv/files 172.16.0.14(root_squash,no_subtree_check) 172.16.0.15(rw,no_root_squash,no_subtree_check)
changed: [localhost] => (item=export2)
ok: [localhost] => (item=export3)
The first two files are all right. The problem is the third file. The line hasn't been added to the file. Quoting from backrefs
"... if the regexp does not match anywhere in the file, the file will be left unchanged."
The explanation is simple. There are no groups if the regex doesn't match. If there are no groups the line can't be created.
On the other hand, quoting from regexp
"... If the regular expression is not matched, the line will be added to the file ..."
As a result, it's not possible to ask lineinfile to add a line if the regexp does not match and, at the same time, to do nothing if the regexp is matched. If the regexp is matched you need back-references. If you use back-references you can't add a missing line.
To solve this problem read the content of the files and create a dictionary
- command: "cat etc/{{ item }}"
register: result
loop: [export1, export2, export3]
- set_fact:
content: "{{ dict(_files|zip(_lines)) }}"
vars:
_lines: "{{ result.results|map(attribute='stdout_lines')|list }}"
_files: "{{ result.results|map(attribute='item')|list }}"
gives
content:
export1:
- /srv/files 172.16.0.14(rw,no_root_squash,no_subtree_check)
export2:
- /srv/files 172.16.0.15(rw,no_root_squash,no_subtree_check)
export3:
- /srv/download 172.16.0.14(rw,no_root_squash,no_subtree_check)
Now add the line only if missing, i.e. do not replace the line if the mount point is already there
- lineinfile:
path: "etc/{{ item }}"
line: '{{ mount }} {{ ip }}{{ options }}'
vars:
mount: /srv/files
ip: '172.16.0.14'
options: '(root_squash,no_subtree_check)'
loop: "{{ content|list }}"
when: content[item]|select('search', mount)|length == 0
gives
skipping: [localhost] => (item=export1)
skipping: [localhost] => (item=export2)
--- before: etc/export3 (content)
+++ after: etc/export3 (content)
## -1 +1,2 ##
/srv/download 172.16.0.14(rw,no_root_squash,no_subtree_check)
+/srv/files 172.16.0.14(root_squash,no_subtree_check)

link task depending on content

I have 3 tasks..
first task checks if a file contains <ip> <hostname> pattern
second task adds a line if the sought after string is not present.
third task corrects the line if it is bad.
the 3 tasks run well independently but I want to run them together somehow linked.
I have the following playbook using as model /etc/hosts.
---
- name: check hosts playbook
hosts: centos
tasks:
- name: check whether a line in the form of '<ip> <hostname>' exists
lineinfile:
path: /var/tmp/hosts
regexp: '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s\w+'
state: absent
check_mode: true
register: line_exists
- name: append_host_file
lineinfile:
path: /var/tmp/hosts
insertafter: '^(127\.0\.0\.1|)(?:\d{1,3}\.){3}\d{1,3}'
line: '{{ ansible_default_ipv4.address }} {{ansible_hostname }}'
backup: yes
when: not line_exists.changed
- name: correct_hosts_file
lineinfile:
path: /var/tmp/hosts
regexp: '^(?!{{ ansible_default_ipv4.address }}\s{{ ansible_hostname }})(?:\d{1,3}\.){3}\d{1,3}\s\w+'
line: '{{ ansible_default_ipv4.address }} {{ansible_hostname }}'
when: line_exists.changed
the issue i have is the correct task is running when the line is correct.. so i need to use some other sort of criteria to prevent it from running when the line in the file is correct...if the line in the file is wrong it work because it replaces it.
It's a common problem with lineinfile, it's not that useful as it looks.
My advice: load file content into variable (- command: cat /etc/hosts), register it (register: old_hosts) than iterate over each line of that variable in a template.
- name: get hosts
command: cat /etc/hosts
register: old_hosts
- name: write hosts
template:
src: hosts.j2
dest: /etc/hosts
hosts.j2:
{% for line in old_hosts.stdout_lines %}
{% if line (....) %}
...
{% endif %}
{% endfor %}

I want to print the names of files present in a directory as a list using ansible

---
- hosts: localhost
user: root
tasks:
- command: "ls /root/Tmp/Deployment/script_files/Hotfix"
register: dir_out
- debug: msg="The hotfix ids are: {{dir_out.stdout_lines}}"
The output I got was:
but I want it as
The hotfix ids are :["1001","1002"]
How do I do this?
I needed to change: {{dir_out.stdout_lines}} to {{dir_out.stdout_lines|join(',')}}