diff --git a/library/files/acl b/library/files/acl new file mode 100644 index 00000000000..1b50d21c58f --- /dev/null +++ b/library/files/acl @@ -0,0 +1,198 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: acl +version_added: "1.3" +short_description: set and retrieve file acl +description: + - Sets and retrivies acl for a file +options: + name: + required: true + default: None + description: + - The full path of the file/object to get the facts of + aliases: ['path'] + entry: + required: false + default: None + description: + - The acl to set/remove. MUST always quote! In form of '::', qualifier may be empty for some types but type and perms are always requried. '-' can be used as placeholder when you don't care about permissions. + state: + required: false + default: query + choices: [ 'query', 'present', 'absent' ] + description: + - defines which operation you want to do. C(query) get the current acl C(present) sets/changes the acl, requires permissions field C(absent) deletes the acl, requires permissions field + follow: + required: false + default: yes + choices: [ 'yes', 'no' ] + description: + - if yes, dereferences symlinks and sets/gets attributes on symlink target, otherwise acts on symlink itself. +author: Brian Coca +notes: + - The "acl" module requires that acl is enabled on the target filesystem and that the setfacl and getfacl binaries are installed. +author: Brian Coca +notes: + - The "acl" module requires the acl command line utilities be installed on the target machine and that acl is enabled on the target filesystem. +''' + +EXAMPLES = ''' +# Obtain the acl of /etc/foo.conf +- acl: name=/etc/foo.conf + +# Grants joe read access to foo +- acl: name=/etc/foo.conf entry="user:joe:r" state=present + +# Removes the acl for joe +- acl: name=/etc/foo.conf entry="user:joe:-" state=absent +''' + +def get_acl(module,path,entry,follow): + + cmd = [ module.get_bin_path('getfacl', True) ] + if not follow: + cmd.append('-h') + # prevents absolute path warnings and removes headers + cmd.append('--omit-header') + cmd.append('--absolute-names') + cmd.append(path) + + return _run_acl(module,cmd) + +def set_acl(module,path,entry,follow): + + cmd = [ module.get_bin_path('setfacl', True) ] + if not follow: + cmd.append('-h') + cmd.append('-m "%s"' % entry) + cmd.append(path) + + return _run_acl(module,cmd) + +def rm_acl(module,path,entry,follow): + + cmd = [ module.get_bin_path('setfacl', True) ] + if not follow: + cmd.append('-h') + entry = entry[0:entry.rfind(':')] + cmd.append('-x "%s"' % entry) + cmd.append(path) + + return _run_acl(module,cmd,False) + +def _run_acl(module,cmd,check_rc=True): + + try: + (rc, out, err) = module.run_command(' '.join(cmd), check_rc=check_rc) + except Exception, e: + module.fail_json(msg=e.strerror) + + return out.splitlines() + +def main(): + module = AnsibleModule( + argument_spec = dict( + name = dict(required=True,aliases=['path']), + entry = dict(required=False, default=None), + state = dict(required=False, default='query', choices=[ 'query', 'present', 'absent' ], type='str'), + follow = dict(required=False, type='bool', default=True), + ), + supports_check_mode=True, + ) + + path = module.params.get('name') + entry = module.params.get('entry') + state = module.params.get('state') + follow = module.params.get('follow') + + if not os.path.exists(path): + module.fail_json(msg="path not found or not accessible!") + + if entry is None: + if state in ['present','absent']: + module.fail_json(msg="%s needs entry to be set" % state) + else: + if entry.count(":") != 2: + module.fail_json(msg="Invalid entry: '%s', it requires 3 sections divided by ':'" % entry) + + changed=False + changes=0 + msg = "" + currentacl = get_acl(module,path,entry,follow) + + + if (state == 'present'): + newe = entry.split(':') + matched = False + for oldentry in currentacl: + diff = False + olde = oldentry.split(':') + if olde[0] == newe[0]: + if newe[0] in ['user', 'group']: + if olde[1] == newe[1]: + matched = True + if not olde[2] == newe[2]: + diff = True + else: + matched = True + if not olde[2] == newe[2]: + diff = True + if diff: + changes=changes+1 + if not module.check_mode: + set_acl(module,path,entry,follow) + if matched: + break + if not matched: + changes=changes+1 + if not module.check_mode: + set_acl(module,path,entry,follow) + msg="%s is present" % (entry) + elif state == 'absent': + rme = entry.split(':') + for oldentry in currentacl: + olde = oldentry.split(':') + if olde[0] == rme[0]: + if rme[0] in ['user', 'group']: + if olde[1] == rme[1]: + changes=changes+1 + if not module.check_mode: + rm_acl(module,path,entry,follow) + break + else: + changes=changes+1 + if not module.check_mode: + rm_acl(module,path,entry,follow) + break + msg="%s is absent" % (entry) + else: + msg="current acl" + + if changes > 0: + changed=True + currentacl = get_acl(module,path,entry,follow) + + msg="%s. %d entries changed" % (msg,changes) + module.exit_json(changed=changed, msg=msg, acl=currentacl) + +# this is magic, see lib/ansible/module_common.py +#<> + +main()