1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# (c) 2016, Adam Števko <adam.stevko@gmail.com> 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 10 11DOCUMENTATION = ''' 12--- 13module: zfs_facts 14short_description: Gather facts about ZFS datasets. 15description: 16 - Gather facts from ZFS dataset properties. 17author: Adam Števko (@xen0l) 18options: 19 name: 20 description: 21 - ZFS dataset name. 22 required: yes 23 aliases: [ "ds", "dataset" ] 24 type: str 25 recurse: 26 description: 27 - Specifies if properties for any children should be recursively 28 displayed. 29 type: bool 30 default: 'no' 31 parsable: 32 description: 33 - Specifies if property values should be displayed in machine 34 friendly format. 35 type: bool 36 default: 'no' 37 properties: 38 description: 39 - Specifies which dataset properties should be queried in comma-separated format. 40 For more information about dataset properties, check zfs(1M) man page. 41 default: all 42 type: str 43 type: 44 description: 45 - Specifies which datasets types to display. Multiple values have to be 46 provided in comma-separated form. 47 choices: [ 'all', 'filesystem', 'volume', 'snapshot', 'bookmark' ] 48 default: all 49 type: str 50 depth: 51 description: 52 - Specifies recursion depth. 53 type: int 54''' 55 56EXAMPLES = ''' 57- name: Gather facts about ZFS dataset rpool/export/home 58 community.general.zfs_facts: 59 dataset: rpool/export/home 60 61- name: Report space usage on ZFS filesystems under data/home 62 community.general.zfs_facts: 63 name: data/home 64 recurse: yes 65 type: filesystem 66 67- ansible.builtin.debug: 68 msg: 'ZFS dataset {{ item.name }} consumes {{ item.used }} of disk space.' 69 with_items: '{{ ansible_zfs_datasets }}' 70''' 71 72RETURN = ''' 73name: 74 description: ZFS dataset name 75 returned: always 76 type: str 77 sample: rpool/var/spool 78parsable: 79 description: if parsable output should be provided in machine friendly format. 80 returned: if 'parsable' is set to True 81 type: bool 82 sample: True 83recurse: 84 description: if we should recurse over ZFS dataset 85 returned: if 'recurse' is set to True 86 type: bool 87 sample: True 88zfs_datasets: 89 description: ZFS dataset facts 90 returned: always 91 type: str 92 sample: 93 { 94 "aclinherit": "restricted", 95 "aclmode": "discard", 96 "atime": "on", 97 "available": "43.8G", 98 "canmount": "on", 99 "casesensitivity": "sensitive", 100 "checksum": "on", 101 "compression": "off", 102 "compressratio": "1.00x", 103 "copies": "1", 104 "creation": "Thu Jun 16 11:37 2016", 105 "dedup": "off", 106 "devices": "on", 107 "exec": "on", 108 "filesystem_count": "none", 109 "filesystem_limit": "none", 110 "logbias": "latency", 111 "logicalreferenced": "18.5K", 112 "logicalused": "3.45G", 113 "mlslabel": "none", 114 "mounted": "yes", 115 "mountpoint": "/rpool", 116 "name": "rpool", 117 "nbmand": "off", 118 "normalization": "none", 119 "org.openindiana.caiman:install": "ready", 120 "primarycache": "all", 121 "quota": "none", 122 "readonly": "off", 123 "recordsize": "128K", 124 "redundant_metadata": "all", 125 "refcompressratio": "1.00x", 126 "referenced": "29.5K", 127 "refquota": "none", 128 "refreservation": "none", 129 "reservation": "none", 130 "secondarycache": "all", 131 "setuid": "on", 132 "sharenfs": "off", 133 "sharesmb": "off", 134 "snapdir": "hidden", 135 "snapshot_count": "none", 136 "snapshot_limit": "none", 137 "sync": "standard", 138 "type": "filesystem", 139 "used": "4.41G", 140 "usedbychildren": "4.41G", 141 "usedbydataset": "29.5K", 142 "usedbyrefreservation": "0", 143 "usedbysnapshots": "0", 144 "utf8only": "off", 145 "version": "5", 146 "vscan": "off", 147 "written": "29.5K", 148 "xattr": "on", 149 "zoned": "off" 150 } 151''' 152 153from collections import defaultdict 154 155from ansible.module_utils.basic import AnsibleModule 156from ansible.module_utils.six import iteritems 157 158 159SUPPORTED_TYPES = ['all', 'filesystem', 'volume', 'snapshot', 'bookmark'] 160 161 162class ZFSFacts(object): 163 def __init__(self, module): 164 165 self.module = module 166 167 self.name = module.params['name'] 168 self.recurse = module.params['recurse'] 169 self.parsable = module.params['parsable'] 170 self.properties = module.params['properties'] 171 self.type = module.params['type'] 172 self.depth = module.params['depth'] 173 174 self._datasets = defaultdict(dict) 175 self.facts = [] 176 177 def dataset_exists(self): 178 cmd = [self.module.get_bin_path('zfs'), 'list', self.name] 179 180 (rc, out, err) = self.module.run_command(cmd) 181 182 if rc == 0: 183 return True 184 else: 185 return False 186 187 def get_facts(self): 188 cmd = [self.module.get_bin_path('zfs'), 'get', '-H'] 189 if self.parsable: 190 cmd.append('-p') 191 if self.recurse: 192 cmd.append('-r') 193 if int(self.depth) != 0: 194 cmd.append('-d') 195 cmd.append('%s' % self.depth) 196 if self.type: 197 cmd.append('-t') 198 cmd.append(self.type) 199 cmd.extend(['-o', 'name,property,value', self.properties, self.name]) 200 201 (rc, out, err) = self.module.run_command(cmd) 202 203 if rc == 0: 204 for line in out.splitlines(): 205 dataset, property, value = line.split('\t') 206 207 self._datasets[dataset].update({property: value}) 208 209 for k, v in iteritems(self._datasets): 210 v.update({'name': k}) 211 self.facts.append(v) 212 213 return {'ansible_zfs_datasets': self.facts} 214 else: 215 self.module.fail_json(msg='Error while trying to get facts about ZFS dataset: %s' % self.name, 216 stderr=err, 217 rc=rc) 218 219 220def main(): 221 module = AnsibleModule( 222 argument_spec=dict( 223 name=dict(required=True, aliases=['ds', 'dataset'], type='str'), 224 recurse=dict(required=False, default=False, type='bool'), 225 parsable=dict(required=False, default=False, type='bool'), 226 properties=dict(required=False, default='all', type='str'), 227 type=dict(required=False, default='all', type='str', choices=SUPPORTED_TYPES), 228 depth=dict(required=False, default=0, type='int') 229 ), 230 supports_check_mode=True 231 ) 232 233 zfs_facts = ZFSFacts(module) 234 235 result = {} 236 result['changed'] = False 237 result['name'] = zfs_facts.name 238 239 if zfs_facts.parsable: 240 result['parsable'] = zfs_facts.parsable 241 242 if zfs_facts.recurse: 243 result['recurse'] = zfs_facts.recurse 244 245 if zfs_facts.dataset_exists(): 246 result['ansible_facts'] = zfs_facts.get_facts() 247 else: 248 module.fail_json(msg='ZFS dataset %s does not exist!' % zfs_facts.name) 249 250 module.exit_json(**result) 251 252 253if __name__ == '__main__': 254 main() 255