Commit b45fb649 by Brian Coca

now handles default and allaows for either shorthand entry or specific

fields per entry section

the default option works both when added to entry or as stand alone.
Signed-off-by: Brian Coca <briancoca+dev@gmail.com>
parent d505586b
...@@ -24,15 +24,10 @@ description: ...@@ -24,15 +24,10 @@ description:
options: options:
name: name:
required: true required: true
default: None default: null
description: description:
- The full path of the file or object. - The full path of the file or object.
aliases: ['path'] aliases: ['path']
entry:
required: false
default: None
description:
- The acl to set or remove. This must always be quoted in the form of '<type>:<qualifier>:<perms>'. The qualifier may be empty for some types, but the type and perms are always requried. '-' can be used as placeholder when you do not care about permissions.
state: state:
required: false required: false
...@@ -40,12 +35,50 @@ options: ...@@ -40,12 +35,50 @@ options:
choices: [ 'query', 'present', 'absent' ] choices: [ 'query', 'present', 'absent' ]
description: description:
- defines whether the ACL should be present or not. The C(query) state gets the current acl C(present) without changing it, for use in 'register' operations. - defines whether the ACL should be present or not. The C(query) state gets the current acl C(present) without changing it, for use in 'register' operations.
follow: follow:
required: false required: false
default: yes default: yes
choices: [ 'yes', 'no' ] choices: [ 'yes', 'no' ]
description: description:
- whether to follow symlinks on the path if a symlink is encountered. - whether to follow symlinks on the path if a symlink is encountered.
default:
version_added: "1.5"
required: false
default: no
choices: [ 'yes', 'no' ]
description:
- if the target is a directory, setting this to yes will make it the default acl for entities created inside the directory. It causes an error if name is a file.
entity:
version_added: "1.5"
required: false
description:
- actual user or group that the ACL applies to when matching entity types user or group are selected.
type:
version_added: "1.5"
required: false
default: null
choices: [ 'user', 'group', 'mask', 'other' ]
description:
- if the target is a directory, setting this to yes will make it the default acl for entities created inside the directory. It causes an error if name is a file.
d
permissions:
version_added: "1.5"
required: false
default: null
description:
- Permissions to apply/remove can be any combination of r, w and x (read, write and execute respectively)
entry:(deprecated)
required: false
default: null
description:
- The acl to set or remove. This must always be quoted in the form of '<type>:<qualifier>:<perms>'. The qualifier may be empty for some types, but the type and perms are always requried. '-' can be used as placeholder when you do not care about permissions. This is now superceeded by entity, type and permissions fields.
author: Brian Coca author: Brian Coca
notes: notes:
- The "acl" module requires that acls are enabled on the target filesystem and that the setfacl and getfacl binaries are installed. - The "acl" module requires that acls are enabled on the target filesystem and that the setfacl and getfacl binaries are installed.
...@@ -53,17 +86,59 @@ notes: ...@@ -53,17 +86,59 @@ notes:
EXAMPLES = ''' EXAMPLES = '''
# Grant user Joe read access to a file # Grant user Joe read access to a file
- acl: name=/etc/foo.conf entry="user:joe:r" state=present - acl: name=/etc/foo.conf entity=joe type=user permissions="r" state=present
# Removes the acl for Joe on a specific file # Removes the acl for Joe on a specific file
- acl: name=/etc/foo.conf entry="user:joe:-" state=absent - acl: name=/etc/foo.conf entity=joe type=user state=absent
# Sets default acl for joe on foo.d
- acl: name=/etc/foo.d entity=joe type=user permissions=rw default=yes state=present
# Same as previous but using entry shorthand
- acl: name=/etc/foo.d entrty="default:user:joe:rw-" state=present
# Obtain the acl for a specific file # Obtain the acl for a specific file
- acl: name=/etc/foo.conf - acl: name=/etc/foo.conf
register: acl_info register: acl_info
''' '''
def get_acl(module,path,entry,follow): def split_entry(entry):
''' splits entry and ensures normalized return'''
a = entry.split(':')
a.reverse()
if len(a) == 3:
a.append(False)
try:
p,e,t,d = a
except ValueError, e:
print "wtf?? %s => %s" % (entry,a)
raise e
if t.startswith("u"):
t = "user"
elif t.startswith("g"):
t = "group"
elif t.startswith("m"):
t = "mask"
elif t.startswith("o"):
t = "other"
else:
t = None
perms = ['-','-','-']
for char in p:
if char == 'r':
perms[0] = 'r'
if char == 'w':
perms[1] = 'w'
if char == 'x':
perms[2] = 'x'
p = ''.join(perms)
return [d,t,e,p]
def get_acls(module,path,follow):
cmd = [ module.get_bin_path('getfacl', True) ] cmd = [ module.get_bin_path('getfacl', True) ]
if not follow: if not follow:
...@@ -75,21 +150,25 @@ def get_acl(module,path,entry,follow): ...@@ -75,21 +150,25 @@ def get_acl(module,path,entry,follow):
return _run_acl(module,cmd) return _run_acl(module,cmd)
def set_acl(module,path,entry,follow): def set_acl(module,path,entry,follow,default):
cmd = [ module.get_bin_path('setfacl', True) ] cmd = [ module.get_bin_path('setfacl', True) ]
if not follow: if not follow:
cmd.append('-h') cmd.append('-h')
if default:
cmd.append('-d')
cmd.append('-m "%s"' % entry) cmd.append('-m "%s"' % entry)
cmd.append(path) cmd.append(path)
return _run_acl(module,cmd) return _run_acl(module,cmd)
def rm_acl(module,path,entry,follow): def rm_acl(module,path,entry,follow,default):
cmd = [ module.get_bin_path('setfacl', True) ] cmd = [ module.get_bin_path('setfacl', True) ]
if not follow: if not follow:
cmd.append('-h') cmd.append('-h')
if default:
cmd.append('-k')
entry = entry[0:entry.rfind(':')] entry = entry[0:entry.rfind(':')]
cmd.append('-x "%s"' % entry) cmd.append('-x "%s"' % entry)
cmd.append(path) cmd.append(path)
...@@ -103,93 +182,104 @@ def _run_acl(module,cmd,check_rc=True): ...@@ -103,93 +182,104 @@ def _run_acl(module,cmd,check_rc=True):
except Exception, e: except Exception, e:
module.fail_json(msg=e.strerror) module.fail_json(msg=e.strerror)
return out.splitlines() # trim last line as it is always empty
ret = out.splitlines()
return ret[0:len(ret)-1]
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec = dict( argument_spec = dict(
name = dict(required=True,aliases=['path']), name = dict(required=True,aliases=['path'], type='str'),
entry = dict(required=False, default=None), entry = dict(required=False, type='str'),
entity = dict(required=False, type='str', default=''),
type = dict(required=False, choices=['other', 'user', 'group', 'mask'], type='str'),
permissions = dict(required=False, type='str'),
state = dict(required=False, default='query', choices=[ 'query', 'present', 'absent' ], type='str'), state = dict(required=False, default='query', choices=[ 'query', 'present', 'absent' ], type='str'),
follow = dict(required=False, type='bool', default=True), follow = dict(required=False, type='bool', default=True),
default= dict(required=False, type='bool', default=False),
), ),
supports_check_mode=True, supports_check_mode=True,
) )
path = module.params.get('name') path = module.params.get('name')
entry = module.params.get('entry') entry = module.params.get('entry')
entity = module.params.get('entity')
type = module.params.get('type')
permissions = module.params.get('permissions')
state = module.params.get('state') state = module.params.get('state')
follow = module.params.get('follow') follow = module.params.get('follow')
default = module.params.get('default')
if not os.path.exists(path): if not os.path.exists(path):
module.fail_json(msg="path not found or not accessible!") module.fail_json(msg="path not found or not accessible!")
if entry is None:
if state in ['present','absent']: if state in ['present','absent']:
module.fail_json(msg="%s needs entry to be set" % state) if not entry and not type:
else: module.fail_json(msg="%s requries to have ither either type and permissions or entry to be set" % state)
if entry.count(":") != 2:
module.fail_json(msg="Invalid entry: '%s', it requires 3 sections divided by ':'" % entry) if entry:
if type or entity or permissions:
module.fail_json(msg="entry and another incompatible field (entity, type or permissions) are also set")
if entry.count(":") not in [2,3]:
module.fail_json(msg="Invalid entry: '%s', it requires 3 or 4 sections divided by ':'" % entry)
default, type, entity, permissions = split_entry(entry)
changed=False changed=False
changes=0
msg = "" msg = ""
currentacl = get_acl(module,path,entry,follow) currentacls = get_acls(module,path,follow)
if (state == 'present'): if (state == 'present'):
newe = entry.split(':')
matched = False matched = False
for oldentry in currentacl: for oldentry in currentacls:
diff = False if oldentry.count(":") == 0:
olde = oldentry.split(':') continue
if olde[0] == newe[0]: old_default, old_type, old_entity, old_permissions = split_entry(oldentry)
if newe[0] in ['user', 'group']: if old_default == default:
if olde[1] == newe[1]: if old_type == type:
if type in ['user', 'group']:
if old_entity == entity:
matched = True matched = True
if not olde[2] == newe[2]: if not old_permissions == permissions:
diff = True changed = True
break
else: else:
matched = True matched = True
if not olde[2] == newe[2]: if not old_permissions == permissions:
diff = True changed = True
if diff: break
changes=changes+1
if not module.check_mode:
set_acl(module,path,entry,follow)
if matched:
break break
if not matched: if not matched:
changes=changes+1 changed=True
if not module.check_mode:
set_acl(module,path,entry,follow) if changed and not module.check_mode:
msg="%s is present" % (entry) set_acl(module,path,':'.join([type, str(entity), permissions]),follow,default)
msg="%s is present" % ':'.join([type, str(entity), permissions])
elif state == 'absent': elif state == 'absent':
rme = entry.split(':') for oldentry in currentacls:
for oldentry in currentacl: if oldentry.count(":") == 0:
olde = oldentry.split(':') continue
if olde[0] == rme[0]: old_default, old_type, old_entity, old_permissions = split_entry(oldentry)
if rme[0] in ['user', 'group']: if old_default == default:
if olde[1] == rme[1]: if old_type == type:
changes=changes+1 if type in ['user', 'group']:
if not module.check_mode: if old_entity == entity:
rm_acl(module,path,entry,follow) changed=True
break break
else: else:
changes=changes+1 changed=True
if not module.check_mode:
rm_acl(module,path,entry,follow)
break break
msg="%s is absent" % (entry) if changed and not module.check_mode:
rm_acl(module,path,':'.join([type, entity, '---']),follow,default)
msg="%s is absent" % ':'.join([type, entity, '---'])
else: else:
msg="current acl" msg="current acl"
if changes > 0: if changed:
changed=True currentacls = get_acls(module,path,follow)
currentacl = get_acl(module,path,entry,follow)
msg="%s. %d entries changed" % (msg,changes) module.exit_json(changed=changed, msg=msg, acl=currentacls)
module.exit_json(changed=changed, msg=msg, acl=currentacl)
# import module snippets # import module snippets
from ansible.module_utils.basic import * from ansible.module_utils.basic import *
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment