#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2013, Johan Wiren # # 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: zfs short_description: Manage zfs description: - Manages ZFS file systems on Solaris and FreeBSD. Can manage file systems, volumes and snapshots. Supports the following options from zfs(1M) (See the man page for more information): aclinherit aclmode atime canmount casesensitivity checksum compression copies dedup devices exec jailed logbias mountpoint nbmand normalization primarycache quota readonly recordsize refquota refreservation reservation secondarycache setuid shareiscsi sharenfs sharesmb snapdir sync utf8only volsize volblocksize vscan xattr zoned options: name: description: - File system, snapshot or volume name e.g. C(rpool/myfs) required: true state: description: - Whether to create (C(present)), or remove (C(absent)) a file system, snapshot or volume. required: true choices: [present, absent] examples: - code: zfs name=rpool/myfs state=present description: Create a new file system called myfs in pool rpool - code: zfs name=rpool/myvol state=present volsize=10M description: Create a new volume called myvol in pool rpool. - code: zfs name=rpool/myfs@mysnapshot state=present description: Create a snapshot of rpool/myfs file system. - code: zfs name=rpool/myfs2 state=present snapdir=enabled description: Create a new file system called myfs2 whith snapdir enabled author: Johan Wiren ''' import os class Zfs(object): def __init__(self, module, name, properties): self.module = module self.name = name self.properties = properties self.changed = False self.immutable_properties = [ 'casesensitivity', 'normalization', 'utf8only' ] def exists(self): cmd = [self.module.get_bin_path('zfs', True)] cmd.append('list') cmd.append('-t all') cmd.append(self.name) (rc, out, err) = self.module.run_command(' '.join(cmd)) if rc == 0: return True else: return False def create(self): properties=self.properties volsize = properties.pop('volsize', None) volblocksize = properties.pop('volblocksize', None) if "@" in self.name: action = 'snapshot' else: action = 'create' cmd = [self.module.get_bin_path('zfs', True)] cmd.append(action) if volblocksize: cmd.append('-b %s' % volblocksize) if properties: for prop, value in properties.iteritems(): cmd.append('-o %s=%s' % (prop, value)) if volsize: cmd.append('-V') cmd.append(volsize) cmd.append(self.name) (rc, err, out) = self.module.run_command(' '.join(cmd)) if rc == 0: self.changed=True else: self.module.fail_json(msg=out) def destroy(self): cmd = [self.module.get_bin_path('zfs', True)] cmd.append('destroy') cmd.append(self.name) (rc, err, out) = self.module.run_command(' '.join(cmd)) if rc == 0: self.changed = True else: self.module.fail_json(msg=out) def set_property(self, prop, value): cmd = [self.module.get_bin_path('zfs', True)] cmd.append('set') cmd.append(prop + '=' + value) cmd.append(self.name) (rc, err, out) = self.module.run_command(' '.join(cmd)) if rc == 0: self.changed = True else: self.module.fail_json(msg=out) def set_properties_if_changed(self): current_properties = self.get_current_properties() for prop, value in self.properties.iteritems(): if current_properties[prop] != value: if prop in self.immutable_properties: self.module.fail_json(msg='Cannot change property %s after creation.' % prop) else: self.set_property(prop, value) def get_current_properties(self): cmd = [self.module.get_bin_path('zfs', True)] cmd.append('get -H all') cmd.append(self.name) rc, out, err = self.module.run_command(' '.join(cmd)) properties=dict() for l in out.splitlines(): p, v = l.split()[1:3] properties[p] = v return properties def run_command(self, cmd): progname = cmd[0] cmd[0] = module.get_bin_path(progname, True) return module.run_command(cmd) def main(): module = AnsibleModule( argument_spec = { 'name': {'required': True}, 'state': {'required': True, 'choices':['present', 'absent']}, 'aclinherit': {'required': False, 'choices':['discard', 'noallow', 'restricted', 'passthrough', 'passthrough-x']}, 'aclmode': {'required': False, 'choices':['discard', 'groupmask', 'passthrough']}, 'atime': {'required': False, 'choices':['on', 'off']}, 'canmount': {'required': False, 'choices':['on', 'off', 'noauto']}, 'casesensitivity': {'required': False, 'choices':['sensitive', 'insensitive', 'mixed']}, 'checksum': {'required': False, 'choices':['on', 'off', 'fletcher2', 'fletcher4', 'sha256']}, 'compression': {'required': False, 'choices':['on', 'off', 'lzjb', 'gzip', 'gzip-1', 'gzip-2', 'gzip-3', 'gzip-4', 'gzip-5', 'gzip-6', 'gzip-7', 'gzip-8', 'gzip-9']}, 'copies': {'required': False, 'choices':['1', '2', '3']}, 'dedup': {'required': False, 'choices':['on', 'off']}, 'devices': {'required': False, 'choices':['on', 'off']}, 'exec': {'required': False, 'choices':['on', 'off']}, # Not supported #'groupquota': {'required': False}, 'jailed': {'required': False, 'choices':['on', 'off']}, 'logbias': {'required': False, 'choices':['latency', 'throughput']}, 'mountpoint': {'required': False}, 'nbmand': {'required': False, 'choices':['on', 'off']}, 'normalization': {'required': False, 'choices':['none', 'formC', 'formD', 'formKC', 'formKD']}, 'primarycache': {'required': False, 'choices':['all', 'none', 'metadata']}, 'quota': {'required': False}, 'readonly': {'required': False, 'choices':['on', 'off']}, 'recordsize': {'required': False}, 'refquota': {'required': False}, 'refreservation': {'required': False}, 'reservation': {'required': False}, 'secondarycache': {'required': False, 'choices':['all', 'none', 'metadata']}, 'setuid': {'required': False, 'choices':['on', 'off']}, 'shareiscsi': {'required': False, 'choices':['on', 'off']}, 'sharenfs': {'required': False}, 'sharesmb': {'required': False}, 'snapdir': {'required': False, 'choices':['hidden', 'visible']}, 'sync': {'required': False, 'choices':['on', 'off']}, # Not supported #'userquota': {'required': False}, 'utf8only': {'required': False, 'choices':['on', 'off']}, 'volsize': {'required': False}, 'volblocksize': {'required': False}, 'vscan': {'required': False, 'choices':['on', 'off']}, 'xattr': {'required': False, 'choices':['on', 'off']}, 'zoned': {'required': False, 'choices':['on', 'off']}, } ) state = module.params.pop('state') name = module.params.pop('name') # Remaining items in module.params are zfs properties # Remove 'null' value properties properties = dict() for prop, value in module.params.iteritems(): if value: properties[prop] = value result = {} result['name'] = name result['state'] = state zfs=Zfs(module, name, properties) if state == 'present': if zfs.exists(): zfs.set_properties_if_changed() else: zfs.create() elif state == 'absent': if zfs.exists(): zfs.destroy() result.update(zfs.properties) result['changed'] = zfs.changed module.exit_json(**result) # include magic from lib/ansible/module_common.py #<> main()