1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2017, Ansible Project 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10DOCUMENTATION = ''' 11--- 12module: xattr 13short_description: Manage user defined extended attributes 14description: 15 - Manages filesystem user defined extended attributes. 16 - Requires that extended attributes are enabled on the target filesystem 17 and that the setfattr/getfattr utilities are present. 18options: 19 path: 20 description: 21 - The full path of the file/object to get the facts of. 22 - Before 2.3 this option was only usable as I(name). 23 type: path 24 required: true 25 aliases: [ name ] 26 namespace: 27 description: 28 - Namespace of the named name/key. 29 type: str 30 default: user 31 key: 32 description: 33 - The name of a specific Extended attribute key to set/retrieve. 34 type: str 35 value: 36 description: 37 - The value to set the named name/key to, it automatically sets the C(state) to 'set'. 38 type: str 39 state: 40 description: 41 - defines which state you want to do. 42 C(read) retrieves the current value for a C(key) (default) 43 C(present) sets C(name) to C(value), default if value is set 44 C(all) dumps all data 45 C(keys) retrieves all keys 46 C(absent) deletes the key 47 type: str 48 choices: [ absent, all, keys, present, read ] 49 default: read 50 follow: 51 description: 52 - If C(yes), dereferences symlinks and sets/gets attributes on symlink target, 53 otherwise acts on symlink itself. 54 type: bool 55 default: yes 56notes: 57 - As of Ansible 2.3, the I(name) option has been changed to I(path) as default, but I(name) still works as well. 58author: 59- Brian Coca (@bcoca) 60''' 61 62EXAMPLES = ''' 63- name: Obtain the extended attributes of /etc/foo.conf 64 community.general.xattr: 65 path: /etc/foo.conf 66 67- name: Set the key 'user.foo' to value 'bar' 68 community.general.xattr: 69 path: /etc/foo.conf 70 key: foo 71 value: bar 72 73- name: Set the key 'trusted.glusterfs.volume-id' to value '0x817b94343f164f199e5b573b4ea1f914' 74 community.general.xattr: 75 path: /mnt/bricks/brick1 76 namespace: trusted 77 key: glusterfs.volume-id 78 value: "0x817b94343f164f199e5b573b4ea1f914" 79 80- name: Remove the key 'user.foo' 81 community.general.xattr: 82 path: /etc/foo.conf 83 key: foo 84 state: absent 85 86- name: Remove the key 'trusted.glusterfs.volume-id' 87 community.general.xattr: 88 path: /mnt/bricks/brick1 89 namespace: trusted 90 key: glusterfs.volume-id 91 state: absent 92''' 93 94import os 95 96# import module snippets 97from ansible.module_utils.basic import AnsibleModule 98from ansible.module_utils.common.text.converters import to_native 99 100 101def get_xattr_keys(module, path, follow): 102 cmd = [module.get_bin_path('getfattr', True), '--absolute-names'] 103 104 if not follow: 105 cmd.append('-h') 106 cmd.append(path) 107 108 return _run_xattr(module, cmd) 109 110 111def get_xattr(module, path, key, follow): 112 cmd = [module.get_bin_path('getfattr', True), '--absolute-names'] 113 114 if not follow: 115 cmd.append('-h') 116 if key is None: 117 cmd.append('-d') 118 else: 119 cmd.append('-n %s' % key) 120 cmd.append(path) 121 122 return _run_xattr(module, cmd, False) 123 124 125def set_xattr(module, path, key, value, follow): 126 127 cmd = [module.get_bin_path('setfattr', True)] 128 if not follow: 129 cmd.append('-h') 130 cmd.append('-n %s' % key) 131 cmd.append('-v %s' % value) 132 cmd.append(path) 133 134 return _run_xattr(module, cmd) 135 136 137def rm_xattr(module, path, key, follow): 138 139 cmd = [module.get_bin_path('setfattr', True)] 140 if not follow: 141 cmd.append('-h') 142 cmd.append('-x %s' % key) 143 cmd.append(path) 144 145 return _run_xattr(module, cmd, False) 146 147 148def _run_xattr(module, cmd, check_rc=True): 149 150 try: 151 (rc, out, err) = module.run_command(' '.join(cmd), check_rc=check_rc) 152 except Exception as e: 153 module.fail_json(msg="%s!" % to_native(e)) 154 155 # result = {'raw': out} 156 result = {} 157 for line in out.splitlines(): 158 if line.startswith('#') or line == '': 159 pass 160 elif '=' in line: 161 (key, val) = line.split('=') 162 result[key] = val.strip('"') 163 else: 164 result[line] = '' 165 return result 166 167 168def main(): 169 module = AnsibleModule( 170 argument_spec=dict( 171 path=dict(type='path', required=True, aliases=['name']), 172 namespace=dict(type='str', default='user'), 173 key=dict(type='str', no_log=False), 174 value=dict(type='str'), 175 state=dict(type='str', default='read', choices=['absent', 'all', 'keys', 'present', 'read']), 176 follow=dict(type='bool', default=True), 177 ), 178 supports_check_mode=True, 179 ) 180 path = module.params.get('path') 181 namespace = module.params.get('namespace') 182 key = module.params.get('key') 183 value = module.params.get('value') 184 state = module.params.get('state') 185 follow = module.params.get('follow') 186 187 if not os.path.exists(path): 188 module.fail_json(msg="path not found or not accessible!") 189 190 changed = False 191 msg = "" 192 res = {} 193 194 if key is None and state in ['absent', 'present']: 195 module.fail_json(msg="%s needs a key parameter" % state) 196 197 # Prepend the key with the namespace if defined 198 if ( 199 key is not None and 200 namespace is not None and 201 len(namespace) > 0 and 202 not (namespace == 'user' and key.startswith('user.'))): 203 key = '%s.%s' % (namespace, key) 204 205 if (state == 'present' or value is not None): 206 current = get_xattr(module, path, key, follow) 207 if current is None or key not in current or value != current[key]: 208 if not module.check_mode: 209 res = set_xattr(module, path, key, value, follow) 210 changed = True 211 res = current 212 msg = "%s set to %s" % (key, value) 213 elif state == 'absent': 214 current = get_xattr(module, path, key, follow) 215 if current is not None and key in current: 216 if not module.check_mode: 217 res = rm_xattr(module, path, key, follow) 218 changed = True 219 res = current 220 msg = "%s removed" % (key) 221 elif state == 'keys': 222 res = get_xattr_keys(module, path, follow) 223 msg = "returning all keys" 224 elif state == 'all': 225 res = get_xattr(module, path, None, follow) 226 msg = "dumping all" 227 else: 228 res = get_xattr(module, path, key, follow) 229 msg = "returning %s" % key 230 231 module.exit_json(changed=changed, msg=msg, xattr=res) 232 233 234if __name__ == '__main__': 235 main() 236