From f6dac99f96cdd878f7deefa0668c584704d3dc9f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johan=20Wir=C3=A9n?= <johan.wiren@init.se>
Date: Thu, 14 Feb 2013 14:16:08 +0100
Subject: [PATCH 1/2] Added zfs module

---
 zfs | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 260 insertions(+)
 create mode 100644 zfs

diff --git a/zfs b/zfs
new file mode 100644
index 00000000000..04016773b45
--- /dev/null
+++ b/zfs
@@ -0,0 +1,260 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+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
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+main()

From ffa5c86d736cea653292ee485dbd8c283d554759 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johan=20Wir=C3=A9n?= <johan.wiren@init.se>
Date: Sat, 16 Feb 2013 15:11:07 +0100
Subject: [PATCH 2/2] Updated documentation. Works with ansible-doc

---
 zfs | 213 ++++++++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 173 insertions(+), 40 deletions(-)

diff --git a/zfs b/zfs
index 04016773b45..397e096f679 100644
--- a/zfs
+++ b/zfs
@@ -23,46 +23,9 @@ 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
+description: >
+    Manages ZFS file systems on Solaris and FreeBSD. Can manage file systems, volumes
+    and snapshots. See zfs(1M) for more information about the properties.
 options:
   name:
     description:
@@ -73,6 +36,176 @@ options:
       - Whether to create (C(present)), or remove (C(absent)) a file system, snapshot or volume.
     required: true
     choices: [present, absent]
+  aclinherit:
+    description:
+      - The aclinherit property.
+    required: False
+    choices: [discard,noallow,restricted,passthrough,passthrough-x]
+  aclmode:
+    description:
+      - The aclmode property.
+    required: False
+    choices: [discard,groupmask,passthrough]
+  atime:
+    description:
+      - The atime property.
+    required: False
+    choices: [on,off]
+  canmount:
+    description:
+      - The canmount property.
+    required: False
+    choices: [on,off,noauto]
+  casesensitivity:
+    description:
+      - The casesensitivity property.
+    required: False
+    choices: [sensitive,insensitive,mixed]
+  checksum:
+    description:
+      - The checksum property.
+    required: False
+    choices: [on,off,fletcher2,fletcher4,sha256]
+  compression:
+    description:
+      - The compression property.
+    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:
+    description:
+      - The copies property.
+    required: False
+    choices: [1,2,3]
+  dedup:
+    description:
+      - The dedup property.
+    required: False
+    choices: [on,off]
+  devices:
+    description:
+      - The devices property.
+    required: False
+    choices: [on,off]
+  exec:
+    description:
+      - The exec property.
+    required: False
+    choices: [on,off]
+  jailed:
+    description:
+      - The jailed property.
+    required: False
+    choices: [on,off]
+  logbias:
+    description:
+      - The logbias property.
+    required: False
+    choices: [latency,throughput]
+  mountpoint:
+    description:
+      - The mountpoint property.
+    required: False
+  nbmand:
+    description:
+      - The nbmand property.
+    required: False
+    choices: [on,off]
+  normalization:
+    description:
+      - The normalization property.
+    required: False
+    choices: [none,formC,formD,formKC,formKD]
+  primarycache:
+    description:
+      - The primarycache property.
+    required: False
+    choices: [all,none,metadata]
+  quota:
+    description:
+      - The quota property.
+    required: False
+  readonly:
+    description:
+      - The readonly property.
+    required: False
+    choices: [on,off]
+  recordsize:
+    description:
+      - The recordsize property.
+    required: False
+  refquota:
+    description:
+      - The refquota property.
+    required: False
+  refreservation:
+    description:
+      - The refreservation property.
+    required: False
+  reservation:
+    description:
+      - The reservation property.
+    required: False
+  secondarycache:
+    description:
+      - The secondarycache property.
+    required: False
+    choices: [all,none,metadata]
+  setuid:
+    description:
+      - The setuid property.
+    required: False
+    choices: [on,off]
+  shareiscsi:
+    description:
+      - The shareiscsi property.
+    required: False
+    choices: [on,off]
+  sharenfs:
+    description:
+      - The sharenfs property.
+    required: False
+  sharesmb:
+    description:
+      - The sharesmb property.
+    required: False
+  snapdir:
+    description:
+      - The snapdir property.
+    required: False
+    choices: [hidden,visible]
+  sync:
+    description:
+      - The sync property.
+    required: False
+    choices: [on,off]
+  utf8only:
+    description:
+      - The utf8only property.
+    required: False
+    choices: [on,off]
+  volsize:
+    description:
+      - The volsize property.
+    required: False
+  volblocksize:
+    description:
+      - The volblocksize property.
+    required: False
+  vscan:
+    description:
+      - The vscan property.
+    required: False
+    choices: [on,off]
+  xattr:
+    description:
+      - The xattr property.
+    required: False
+    choices: [on,off]
+  zoned:
+    description:
+      - The zoned property.
+    required: False
+    choices: [on,off]
 examples:
   - code: zfs name=rpool/myfs state=present
     description: Create a new file system called myfs in pool rpool