files/file: add support for symbolic permission modes
This commit is contained in:
parent
9018d53a3c
commit
fc3597af5d
3 changed files with 145 additions and 15 deletions
|
@ -416,17 +416,23 @@ class AnsibleModule(object):
|
||||||
|
|
||||||
def set_mode_if_different(self, path, mode, changed):
|
def set_mode_if_different(self, path, mode, changed):
|
||||||
path = os.path.expanduser(path)
|
path = os.path.expanduser(path)
|
||||||
|
path_stat = os.lstat(path)
|
||||||
|
|
||||||
if mode is None:
|
if mode is None:
|
||||||
return changed
|
return changed
|
||||||
try:
|
|
||||||
# FIXME: support English modes
|
|
||||||
if not isinstance(mode, int):
|
|
||||||
mode = int(mode, 8)
|
|
||||||
except Exception, e:
|
|
||||||
self.fail_json(path=path, msg='mode needs to be something octalish', details=str(e))
|
|
||||||
|
|
||||||
st = os.lstat(path)
|
if not isinstance(mode, int):
|
||||||
prev_mode = stat.S_IMODE(st[stat.ST_MODE])
|
try:
|
||||||
|
mode = int(mode, 8)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
mode = self._symbolic_mode_to_octal(path_stat, mode)
|
||||||
|
except Exception, e:
|
||||||
|
self.fail_json(path=path,
|
||||||
|
msg="mode must be in octal or symbolic form",
|
||||||
|
details=str(e))
|
||||||
|
|
||||||
|
prev_mode = stat.S_IMODE(path_stat.st_mode)
|
||||||
|
|
||||||
if prev_mode != mode:
|
if prev_mode != mode:
|
||||||
if self.check_mode:
|
if self.check_mode:
|
||||||
|
@ -448,13 +454,93 @@ class AnsibleModule(object):
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.fail_json(path=path, msg='chmod failed', details=str(e))
|
self.fail_json(path=path, msg='chmod failed', details=str(e))
|
||||||
|
|
||||||
st = os.lstat(path)
|
path_stat = os.lstat(path)
|
||||||
new_mode = stat.S_IMODE(st[stat.ST_MODE])
|
new_mode = stat.S_IMODE(path_stat.st_mode)
|
||||||
|
|
||||||
if new_mode != prev_mode:
|
if new_mode != prev_mode:
|
||||||
changed = True
|
changed = True
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
def _symbolic_mode_to_octal(self, path_stat, symbolic_mode):
|
||||||
|
new_mode = stat.S_IMODE(path_stat.st_mode)
|
||||||
|
|
||||||
|
mode_re = re.compile(r'^(?P<users>[ugoa]+)(?P<operator>[-+=])(?P<perms>[rwxXst]*|[ugo])$')
|
||||||
|
for mode in symbolic_mode.split(','):
|
||||||
|
match = mode_re.match(mode)
|
||||||
|
if match:
|
||||||
|
users = match.group('users')
|
||||||
|
operator = match.group('operator')
|
||||||
|
perms = match.group('perms')
|
||||||
|
|
||||||
|
if users == 'a': users = 'ugo'
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
mode_to_apply = self._get_octal_mode_from_symbolic_perms(path_stat, user, perms)
|
||||||
|
new_mode = self._apply_operation_to_mode(user, operator, mode_to_apply, new_mode)
|
||||||
|
else:
|
||||||
|
raise ValueError("bad symbolic permission for mode: %s" % mode)
|
||||||
|
return new_mode
|
||||||
|
|
||||||
|
def _apply_operation_to_mode(self, user, operator, mode_to_apply, current_mode):
|
||||||
|
if operator == '=':
|
||||||
|
if user == 'u': mask = stat.S_IRWXU | stat.S_ISUID
|
||||||
|
elif user == 'g': mask = stat.S_IRWXG | stat.S_ISGID
|
||||||
|
elif user == 'o': mask = stat.S_IRWXO | stat.S_ISVTX
|
||||||
|
|
||||||
|
# mask out u, g, or o permissions from current_mode and apply new permissions
|
||||||
|
inverse_mask = mask ^ 07777
|
||||||
|
new_mode = (current_mode & inverse_mask) | mode_to_apply
|
||||||
|
elif operator == '+':
|
||||||
|
new_mode = current_mode | mode_to_apply
|
||||||
|
elif operator == '-':
|
||||||
|
new_mode = current_mode - (current_mode & mode_to_apply)
|
||||||
|
return new_mode
|
||||||
|
|
||||||
|
def _get_octal_mode_from_symbolic_perms(self, path_stat, user, perms):
|
||||||
|
prev_mode = stat.S_IMODE(path_stat.st_mode)
|
||||||
|
|
||||||
|
is_directory = stat.S_ISDIR(path_stat.st_mode)
|
||||||
|
has_x_permissions = (prev_mode & 00111) > 0
|
||||||
|
apply_X_permission = is_directory or has_x_permissions
|
||||||
|
|
||||||
|
# Permission bits constants documented at:
|
||||||
|
# http://docs.python.org/2/library/stat.html#stat.S_ISUID
|
||||||
|
user_perms_to_modes = {
|
||||||
|
'u': {
|
||||||
|
'r': stat.S_IRUSR,
|
||||||
|
'w': stat.S_IWUSR,
|
||||||
|
'x': stat.S_IXUSR,
|
||||||
|
'X': stat.S_IXUSR if apply_X_permission else 0,
|
||||||
|
's': stat.S_ISUID,
|
||||||
|
't': 0,
|
||||||
|
'u': prev_mode & stat.S_IRWXU,
|
||||||
|
'g': (prev_mode & stat.S_IRWXG) << 3,
|
||||||
|
'o': (prev_mode & stat.S_IRWXO) << 6 },
|
||||||
|
'g': {
|
||||||
|
'r': stat.S_IRGRP,
|
||||||
|
'w': stat.S_IWGRP,
|
||||||
|
'x': stat.S_IXGRP,
|
||||||
|
'X': stat.S_IXGRP if apply_X_permission else 0,
|
||||||
|
's': stat.S_ISGID,
|
||||||
|
't': 0,
|
||||||
|
'u': (prev_mode & stat.S_IRWXU) >> 3,
|
||||||
|
'g': prev_mode & stat.S_IRWXG,
|
||||||
|
'o': (prev_mode & stat.S_IRWXO) << 3 },
|
||||||
|
'o': {
|
||||||
|
'r': stat.S_IROTH,
|
||||||
|
'w': stat.S_IWOTH,
|
||||||
|
'x': stat.S_IXOTH,
|
||||||
|
'X': stat.S_IXOTH if apply_X_permission else 0,
|
||||||
|
's': 0,
|
||||||
|
't': stat.S_ISVTX,
|
||||||
|
'u': (prev_mode & stat.S_IRWXU) >> 6,
|
||||||
|
'g': (prev_mode & stat.S_IRWXG) >> 3,
|
||||||
|
'o': prev_mode & stat.S_IRWXO }
|
||||||
|
}
|
||||||
|
|
||||||
|
or_reduce = lambda mode, perm: mode | user_perms_to_modes[user][perm]
|
||||||
|
return reduce(or_reduce, perms, 0)
|
||||||
|
|
||||||
def set_file_attributes_if_different(self, file_args, changed):
|
def set_file_attributes_if_different(self, file_args, changed):
|
||||||
# set modes owners and context as needed
|
# set modes owners and context as needed
|
||||||
changed = self.set_context_if_different(
|
changed = self.set_context_if_different(
|
||||||
|
|
|
@ -63,7 +63,7 @@ options:
|
||||||
default: null
|
default: null
|
||||||
choices: []
|
choices: []
|
||||||
description:
|
description:
|
||||||
- mode the file or directory should be, such as 0644 as would be fed to I(chmod)
|
- mode the file or directory should be, such as 0644 or o=rwx,g=rwx,o=rx as would be fed to I(chmod)
|
||||||
owner:
|
owner:
|
||||||
required: false
|
required: false
|
||||||
default: null
|
default: null
|
||||||
|
|
|
@ -10,6 +10,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import stat
|
||||||
|
|
||||||
from nose.plugins.skip import SkipTest
|
from nose.plugins.skip import SkipTest
|
||||||
|
|
||||||
|
@ -221,9 +222,47 @@ class TestRunner(unittest.TestCase):
|
||||||
assert self._run('file', ['dest=' + filedemo, 'src=/dev/null', 'state=link'])['failed']
|
assert self._run('file', ['dest=' + filedemo, 'src=/dev/null', 'state=link'])['failed']
|
||||||
assert os.path.isfile(filedemo)
|
assert os.path.isfile(filedemo)
|
||||||
|
|
||||||
res = self._run('file', ['dest=' + filedemo, 'mode=604', 'state=file'])
|
os.chmod(filedemo, 0)
|
||||||
assert res['changed']
|
assert self._run('file', ['dest=' + filedemo, 'mode=604', 'state=file'])['changed']
|
||||||
assert os.path.isfile(filedemo) and os.stat(filedemo).st_mode == 0100604
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00604
|
||||||
|
|
||||||
|
os.chmod(filedemo, 0)
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode="u=rwsx,g=rwxs,o=rwxt"', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 07777
|
||||||
|
|
||||||
|
os.chmod(filedemo, 0)
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode="u=rwx,g=rwx,o=rwx"', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00777
|
||||||
|
|
||||||
|
os.chmod(filedemo, 0)
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode="u=rwx,g=,o=rwx"', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00707
|
||||||
|
|
||||||
|
os.chmod(filedemo, 07777)
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode="u=,g=,o="', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 0
|
||||||
|
|
||||||
|
os.chmod(filedemo, 00777)
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode="u-X"', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00677
|
||||||
|
|
||||||
|
os.chmod(filedemo, 00777)
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode="u-x"', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00677
|
||||||
|
|
||||||
|
os.chmod(filedemo, 00411)
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode="u+X"', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00511
|
||||||
|
|
||||||
|
os.chmod(filedemo, 00444)
|
||||||
|
assert not self._run('file', ['dest=' + filedemo, 'mode="g+X"', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00444
|
||||||
|
|
||||||
|
os.chmod(filedemo, 00444)
|
||||||
|
assert not self._run('file', ['dest=' + filedemo, 'mode="u=u,g=g,o=o"', 'state=file'])['changed']
|
||||||
|
assert os.path.isfile(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00444
|
||||||
|
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode=u=gx', 'state=file'])['failed']
|
||||||
|
|
||||||
assert self._run('file', ['dest=' + filedemo, 'state=absent'])['changed']
|
assert self._run('file', ['dest=' + filedemo, 'state=absent'])['changed']
|
||||||
assert not os.path.exists(filedemo)
|
assert not os.path.exists(filedemo)
|
||||||
|
@ -239,8 +278,13 @@ class TestRunner(unittest.TestCase):
|
||||||
#assert result['failed']
|
#assert result['failed']
|
||||||
#assert os.path.isdir(filedemo)
|
#assert os.path.isdir(filedemo)
|
||||||
|
|
||||||
|
os.chmod(filedemo, 0)
|
||||||
assert self._run('file', ['dest=' + filedemo, 'mode=701', 'state=directory'])['changed']
|
assert self._run('file', ['dest=' + filedemo, 'mode=701', 'state=directory'])['changed']
|
||||||
assert os.path.isdir(filedemo) and os.stat(filedemo).st_mode == 040701
|
assert os.path.isdir(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00701
|
||||||
|
|
||||||
|
os.chmod(filedemo, 00444)
|
||||||
|
assert self._run('file', ['dest=' + filedemo, 'mode=ugo+X', 'state=directory'])['changed']
|
||||||
|
assert os.path.isdir(filedemo) and stat.S_IMODE(os.stat(filedemo).st_mode) == 00555
|
||||||
|
|
||||||
assert self._run('file', ['dest=' + filedemo, 'state=absent'])['changed']
|
assert self._run('file', ['dest=' + filedemo, 'state=absent'])['changed']
|
||||||
assert not os.path.exists(filedemo)
|
assert not os.path.exists(filedemo)
|
||||||
|
|
Loading…
Reference in a new issue