adding an fstab option using Ansible - regex

I am trying to add nodev to my /etc/fstab file. I am using the Ansible command below but with no luck. My issue lies with the regular expression, I'm not a pro at regex.
- name: Add nodev to /etc/fstab
lineinfile:
dest=/etc/fstab
backup=yes
backrefs=yes
state=present
regexp='(^/dev[\w/_-]+(\s+(?!nodev)[\w,]+)*)'
line='\1,nodev'
One of the lines from /etc/fstab that I am trying to add nodev is:
/dev/mapper/ex_sys-ex_home /home /ext4 rw,exec,auto,nouser,sync 1 2

While this may not be the most elegant answer, it worked for me.
- name: Ensure fstab uses nodev
mount:
name: "{{ item.mount }}"
src: "{{ item.device }}"
fstype: "{{ item.fstype }}"
opts: "{{ item.options }},nodev"
state: present
with_items: ansible_mounts
when: item.options.find(",") >= 0 and item.options.find("nodev") == -1

Inspired by Joe's answer I made this version which will add a single option to a specific line in /etc/fstab if it isn't there already. This will also keep any other options the line already had.
main.yml
- import_tasks: fstab-opt-present.yml point=/home opt=nodev
fstab-opt-present.yml
- name: '/etc/fstab: Set opt "{{ opt }}" for mount point {{ point }}'
lineinfile:
path: /etc/fstab
backup: yes
backrefs: yes
regexp: '^(\S+\s+{{ point }}\s+\S+\s+)(?!(?:\S*,)?{{ opt }}(?:,\S*)?\s+)(\S+)(\s+.+)$'
line: '\1{{ opt }},\2\3'
register: fstab
- name: 'If {{ point }} changed, remount'
command: 'mount {{ point }} -o remount'
when: fstab.changed
https://regex101.com/ is a really helpful tool for building and testing these kind of regexps. Just enable the "multiline" option there and open the "Substitution" panel and you can even paste in your /etc/fstab and see which lines your regex will match and what it will do to them. Just remember to use real values instead of the Ansible variables {{ point }} etc. when testing there

We've developed a 3rd-party ansible module to add, set or remove mount options. Check it out!
- mountopts:
name: /
option: nodev
https://github.com/Uberspace/ansible-mountopts

I wanted to state that there seems to be a new ansible module which covers all this much more easily:
https://docs.ansible.com/ansible/latest/modules/mount_module.html

Tested & works fine
- name: Set nodev option
replace:
path: /etc/fstab
backup: yes
regexp: '^(\S+\s+)(\/\S+)(\s+)((?:ext4|xfs)\s+)(?!(?:\S*,)?nodev(?:,\S*)?\s+)(\S+)(\s+.+)$'
replace: '\1\2 \4 \5,nodev \6'
It excludes adding nodev to /(root), sets only to ext4 and xfs filesystem. doesn't add to temp filesystems.
Note: while you test regexp101, make sure to select python

Landed here looking for an answer, wound up rolling my own for my use case:
main.yml
- include: fstab-opts.yml point=/tmp opts=noexec,nodev,nosuid,noatime
- include: fstab-opts.yml point=/backup opts=noatime
fstab-opts.yml
---
- name: 'Ensure {{ point }} flags'
lineinfile:
path: /etc/fstab
# uses "(not-spaces spaces /tmp spaces )(not-spaces)(the rest)" pattern to match column content and capture args
regexp: '^([^ ]+[ ]+\{{ point }}[ ]+[^ ]+[ ]+)([^ ]+)(.*)'
line: '\1{{ opts }}\3'
backrefs: yes
register: fstab
- name: 'If {{ point }} changed, remount'
command: mount -o remount {{ point }}
when: fstab.changed

i have added the noexec,nodev,nosuid option in /etc/fstab for the /var/tmp mount point.
Requirement is:
Ensure noexec option set on /var/tmp partition
Ensure nodev option set on /var/tmp partition
Ensure nosuid option set on /var/tmp partition
if required install ansible.posix.mount module using below command .
# ansible-galaxy collection install ansible.posix
Playbook:
---
- name: "STEP 1: Get /var/tmp mounted SRC device"
shell: mount | grep -E '\s/var/tmp\s' | awk '{print $1}'
register: "vartmpsrc"
- debug:
msg: "Validated the /var/tmp mount output: {{ vartmpsrc.stdout }}"
- name: "Add mount noexec,nodev,nosuid options for /var/tmp"
mount:
path: "/var/tmp"
src: "{{ vartmpsrc.stdout }}"
fstype: "tmpfs"
opts: "nosuid,nodev,noexec"
state: "present"
when: vartmpsrc.stdout == "/var/tmp"
- name: Remount /var/tmp mounted volume with mount options noexec,nodev,nosuid
ansible.posix.mount:
path: /var/tmp
state: remounted
when: vartmpsrc.stdout == "/var/tmp"
- name: 'STEP 2: Validate noexec,nodev,nosuid option set on /var/tmp partition'
shell: mount | grep -E '\s/var/tmp\s' | grep -v {{ item }}
loop:
- noexec
- nodev
- nosuid
register: vartmp_exists
ignore_errors: yes
when: vartmpsrc.stdout == "/var/tmp"

Related

Convert Ansible variable to integer for arithmetic

I would like to execute a command to obtain the current AWS EC2 launch template version in an integer format so I can do basic arithmetic on it to use in subsequent queries / deletes.
For example:
tasks:
- name: Lookup current default version of EC2 launch template
command: aws ec2 describe-launch-template-versions --launch-template-id lt-xxx --filters Name=is-default-version,Value=true --query 'LaunchTemplateVersions[*].[VersionNumber]'
delegate_to: localhost
register: result
- name: Show results
debug:
msg: '{{ result.stdout }}'
delegate_to: localhost
If this output is '5' I would like to subtract 1 from it so I can execute an additional command to do the following:
aws ec2 delete-launch-template-versions --launch-template-id lt-xxx --versions {{result - 1}}
I realize this will not work as written, but this is the premise I'm going for.
Convert the string to an integer. For example
- command: echo 5
register: result
- command: "echo {{ result.stdout|int - 1 }}"
register: result
- debug:
var: result.stdout
gives
result.stdout: '4'
The type of the command return values' attribute stdout is string. See the results below
- command: echo 5
register: result
- debug:
msg: |-
result.stdout: {{ result.stdout }}
result.stdout|type_debug: {{ result.stdout|type_debug }}
result.stdout|int|type_debug: {{ result.stdout|int|type_debug }}
msg: |-
result.stdout: 5
result.stdout|type_debug: AnsibleUnsafeText
result.stdout|int|type_debug: int

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)

Dictionary with list remove u ticks and brackets

I'm having an issue accessing a list within a dictionay in ansible.
It's actually more complex than this, I've made a simplified version of the issue for brevity.
ansible version is 2.9
The dictionary looks like:
data_dict:
data:
arch_paths:
- /opt/data1/20201007_test1
- /opt/data1/20201013_test2
- /opt/data1/20201029_test3
bucket: host-data
path: archives/host
src_path: /opt/data1
targz:
arch_paths:
- /opt/data2/20201005.tar.gz
- /opt/data2/20201013.tar.gz
- /opt/data2/20201029.tar.gz
bucket: app-data
path: archives/app
src_path: /opt/data2
My task examples:
- name: print files to archive
debug:
msg: "{{item.value.arch_paths}}"
loop: "{{ data_dict|dict2items }}"
- name: Archive the things
shell: echo "{{item.value.bucket}} -k {{item.value.path}} -p {{item.value.src_path}} {{item.value.arch_paths}}" >> /var/log/put.log
loop: "{{ data_dict|dict2items }}"
These run as expected. But, what ends up in the log file is:
host-data -k archives/host -p /opt/data1 [u'/opt/data1/20201007_test1', u'/opt/data1/20201013_test2', u'/opt/data1/20201029_test3']
app-data -k archives/app -p /opt/data2 [u'/opt/data2/20201005.tar.gz', u'/opt/data2/20201013.tar.gz', u'/opt/data2/20201029.tar.gz']
I want to remove the u'...' and commas so I just have the values with spaces e.g.
host-data -k archives/host -p /opt/data1 /opt/data1/20201007_test1 /opt/data1/20201013_test2 /opt/data1/20201029_test3
app-data -k archives/app -p /opt/data2 /opt/data2/20201005.tar.gz /opt/data2/20201013.tar.gz /opt/data2/20201029.tar.gz
I've seen similar problems on this site but have been unable to adapt any of the solutions.
This happens because arch_paths is a list, so, you just need to join the list items: item.value.arch_paths | join(' ').
With the task:
- shell: echo "{{ item.value.bucket }} -k {{ item.value.path }} -p {{ item.value.src_path }} {{ item.value.arch_paths | join(' ') }}" >> /var/log/put.log
loop: "{{ data_dict|dict2items }}"
It gives a file /var/log/put.log containing:
host-data -k archives/host -p /opt/data1 /opt/data1/20201007_test1 /opt/data1/20201013_test2 /opt/data1/20201029_test3
app-data -k archives/app -p /opt/data2 /opt/data2/20201005.tar.gz /opt/data2/20201013.tar.gz /opt/data2/20201029.tar.gz

ansible parse text string from stdout

My problem is with ansible and parsing stdout. I need to capture the stdout from an ansible play and parse this output for a specific substring within stdout and save into a var. My specific use case is below
- shell: "vault.sh --keystore EAP_HOME/vault/vault.keystore |
--keystore-password vault22 --alias vault --vault-block |
vb --attribute password --sec-attr 0penS3sam3 --enc-dir |
EAP_HOME/vault/ --iteration 120 --salt 1234abcd"
register: results
become: true
This generates an output with the following line, the goal is to capture the masked key that jboss vault generates and save that in an ansible var so I can use it to configure the standalone.xml template:
vault-option name="KEYSTORE_PASSWORD" value="MASK-5dOaAVafCSd"/>
I need a way parse this string with possibly regex and save the "MASK-5dOaAVafCSd" substring into an ansible var using set_facts module or any other ansible module.
Currently my code looks like this
#example stdout
results: vault-option name=\"KEYSTORE_PASSWORD\" value=\"MASK-5dOaAVafCSd\"/>
- name: JBOSS_VAULT:define keystore password masked value variable
set_fact:
masked_value: |
"{{ results.stdout |
regex_replace('^.+(MASK-.+?)\\.+','\\\1') }}"
This code is defining masked_value as the results.stdout, not the expected capture group.
You are very close. I advice you to use regex101.com to test regular expressions.
Here is my solution:
---
- hosts: localhost
gather_facts: no
tasks:
- shell: echo 'vault-option name="KEYSTORE_PASSWORD" value="MASK-5dOaAVafCSd"'
register: results
- set_fact:
myvalue: "{{ results.stdout | regex_search(regexp,'\\1') }}"
vars:
regexp: 'value=\"([^"]+)'
- debug:
var: myvalue
result:
ok: [localhost] => {
"myvalue": [
"MASK-5dOaAVafCSd"
]
}
Update:
regex_search returns a list of found matches, so to get only first one use:
{{ results.stdout | regex_search(regexp,'\\1') | first }}
The above solution worked for me, however I had to do some extra logic to filter shell command output to get to the line which contains following
<vault-option name="KEYSTORE_PASSWORD" value="MASK-6qcNdkIprlA"/>
because vault command output has many lines in it. Once this line is captured, the solution given by Konstantin works just fine. Below is the whole thing that needs to done in one place.
- name: Creating jboss vault
shell: |
{{ baseDir }}/bin/vault.sh -e {{ vaultDir }} -k {{ keystoreURL }} -p {{ keystorePassword }} \
-s {{ keystoreSalt }} -i {{ iterationCount }} -v {{ keystoreAlias }} -b {{ vaultBlock }} \
-a {{ attributeName }} -x {{ attributeValue }}
register: vaultResult
- set_fact:
jbossKeystorePassword: "{{ item | regex_search('value=\"([^\"]+)','\\1') | first }}"
when: item | trim | match('.*KEYSTORE_PASSWORD.*')
with_items:
- "{{ vaultResult.stdout_lines }}"
- debug:
var: jbossKeystorePassword
Be sure to replace all variables with your values in above vault.sh command.