Add ability to filter find on mode (#81485)

pull/81524/head
Matt Martz 1 year ago committed by GitHub
parent a2d9c4d62f
commit ca08261f08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
minor_changes:
- find module - Add ability to filter based on modes

@ -111,6 +111,22 @@ options:
- Set this to V(true) to include hidden files, otherwise they will be ignored. - Set this to V(true) to include hidden files, otherwise they will be ignored.
type: bool type: bool
default: no default: no
mode:
description:
- Choose objects matching a specified permission. This value is
restricted to modes that can be applied using the python
C(os.chmod) function.
- The mode can be provided as an octal such as V("0644") or
as symbolic such as V(u=rw,g=r,o=r)
type: raw
version_added: '2.16'
exact_mode:
description:
- Restrict mode matching to exact matches only, and not as a
minimum set of permissions to match.
type: bool
default: true
version_added: '2.16'
follow: follow:
description: description:
- Set this to V(true) to follow symlinks in path for systems with python 2.6+. - Set this to V(true) to follow symlinks in path for systems with python 2.6+.
@ -252,6 +268,13 @@ import time
from ansible.module_utils.common.text.converters import to_text, to_native from ansible.module_utils.common.text.converters import to_text, to_native
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import string_types
class _Object:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def pfilter(f, patterns=None, excludes=None, use_regex=False): def pfilter(f, patterns=None, excludes=None, use_regex=False):
@ -344,6 +367,25 @@ def contentfilter(fsname, pattern, read_whole_file=False):
return False return False
def mode_filter(st, mode, exact, module):
if not mode:
return True
st_mode = stat.S_IMODE(st.st_mode)
try:
mode = int(mode, 8)
except ValueError:
mode = module._symbolic_mode_to_octal(_Object(st_mode=0), mode)
mode = stat.S_IMODE(mode)
if exact:
return st_mode == mode
return bool(st_mode & mode)
def statinfo(st): def statinfo(st):
pw_name = "" pw_name = ""
gr_name = "" gr_name = ""
@ -414,12 +456,19 @@ def main():
get_checksum=dict(type='bool', default=False), get_checksum=dict(type='bool', default=False),
use_regex=dict(type='bool', default=False), use_regex=dict(type='bool', default=False),
depth=dict(type='int'), depth=dict(type='int'),
mode=dict(type='raw'),
exact_mode=dict(type='bool', default=True),
), ),
supports_check_mode=True, supports_check_mode=True,
) )
params = module.params params = module.params
if params['mode'] and not isinstance(params['mode'], string_types):
module.fail_json(
msg="argument 'mode' is not a string and conversion is not allowed, value is of type %s" % params['mode'].__class__.__name__
)
# Set the default match pattern to either a match-all glob or # Set the default match pattern to either a match-all glob or
# regex depending on use_regex being set. This makes sure if you # regex depending on use_regex being set. This makes sure if you
# set excludes: without a pattern pfilter gets something it can # set excludes: without a pattern pfilter gets something it can
@ -489,7 +538,9 @@ def main():
r = {'path': fsname} r = {'path': fsname}
if params['file_type'] == 'any': if params['file_type'] == 'any':
if pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and agefilter(st, now, age, params['age_stamp']): if (pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and
agefilter(st, now, age, params['age_stamp']) and
mode_filter(st, params['mode'], params['exact_mode'], module)):
r.update(statinfo(st)) r.update(statinfo(st))
if stat.S_ISREG(st.st_mode) and params['get_checksum']: if stat.S_ISREG(st.st_mode) and params['get_checksum']:
@ -502,15 +553,19 @@ def main():
filelist.append(r) filelist.append(r)
elif stat.S_ISDIR(st.st_mode) and params['file_type'] == 'directory': elif stat.S_ISDIR(st.st_mode) and params['file_type'] == 'directory':
if pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and agefilter(st, now, age, params['age_stamp']): if (pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and
agefilter(st, now, age, params['age_stamp']) and
mode_filter(st, params['mode'], params['exact_mode'], module)):
r.update(statinfo(st)) r.update(statinfo(st))
filelist.append(r) filelist.append(r)
elif stat.S_ISREG(st.st_mode) and params['file_type'] == 'file': elif stat.S_ISREG(st.st_mode) and params['file_type'] == 'file':
if pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and \ if (pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and
agefilter(st, now, age, params['age_stamp']) and \ agefilter(st, now, age, params['age_stamp']) and
sizefilter(st, size) and contentfilter(fsname, params['contains'], params['read_whole_file']): sizefilter(st, size) and
contentfilter(fsname, params['contains'], params['read_whole_file']) and
mode_filter(st, params['mode'], params['exact_mode'], module)):
r.update(statinfo(st)) r.update(statinfo(st))
if params['get_checksum']: if params['get_checksum']:
@ -518,7 +573,9 @@ def main():
filelist.append(r) filelist.append(r)
elif stat.S_ISLNK(st.st_mode) and params['file_type'] == 'link': elif stat.S_ISLNK(st.st_mode) and params['file_type'] == 'link':
if pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and agefilter(st, now, age, params['age_stamp']): if (pfilter(fsobj, params['patterns'], params['excludes'], params['use_regex']) and
agefilter(st, now, age, params['age_stamp']) and
mode_filter(st, params['mode'], params['exact_mode'], module)):
r.update(statinfo(st)) r.update(statinfo(st))
filelist.append(r) filelist.append(r)

@ -374,3 +374,6 @@
- '"{{ remote_tmp_dir_test }}/astest/old.txt" in astest_list' - '"{{ remote_tmp_dir_test }}/astest/old.txt" in astest_list'
- '"{{ remote_tmp_dir_test }}/astest/.hidden.txt" in astest_list' - '"{{ remote_tmp_dir_test }}/astest/.hidden.txt" in astest_list'
- '"checksum" in result.files[0]' - '"checksum" in result.files[0]'
- name: Run mode tests
import_tasks: mode.yml

@ -0,0 +1,68 @@
- name: create test files for mode matching
file:
path: '{{ remote_tmp_dir_test }}/mode_{{ item }}'
state: touch
mode: '{{ item }}'
loop:
- '0644'
- '0444'
- '0400'
- '0700'
- '0666'
- name: exact mode octal
find:
path: '{{ remote_tmp_dir_test }}'
pattern: 'mode_*'
mode: '0644'
exact_mode: true
register: exact_mode_0644
- name: exact mode symbolic
find:
path: '{{ remote_tmp_dir_test }}'
pattern: 'mode_*'
mode: 'u=rw,g=r,o=r'
exact_mode: true
register: exact_mode_0644_symbolic
- name: find all user readable files octal
find:
path: '{{ remote_tmp_dir_test }}'
pattern: 'mode_*'
mode: '0400'
exact_mode: false
register: user_readable_octal
- name: find all user readable files symbolic
find:
path: '{{ remote_tmp_dir_test }}'
pattern: 'mode_*'
mode: 'u=r'
exact_mode: false
register: user_readable_symbolic
- name: all other readable files octal
find:
path: '{{ remote_tmp_dir_test }}'
pattern: 'mode_*'
mode: '0004'
exact_mode: false
register: other_readable_octal
- name: all other readable files symbolic
find:
path: '{{ remote_tmp_dir_test }}'
pattern: 'mode_*'
mode: 'o=r'
exact_mode: false
register: other_readable_symbolic
- assert:
that:
- exact_mode_0644.files == exact_mode_0644_symbolic.files
- exact_mode_0644.files[0].path == '{{ remote_tmp_dir_test }}/mode_0644'
- user_readable_octal.files == user_readable_symbolic.files
- user_readable_octal.files|map(attribute='path')|map('basename')|sort == ['mode_0400', 'mode_0444', 'mode_0644', 'mode_0666', 'mode_0700']
- other_readable_octal.files == other_readable_symbolic.files
- other_readable_octal.files|map(attribute='path')|map('basename')|sort == ['mode_0444', 'mode_0644', 'mode_0666']
Loading…
Cancel
Save