We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ZFS tries to change immutable attributes
##### SUMMARY

Module `zfs` tries to set immutable attributes like `volblocksize`. Seems to be a regression, see [ansible/ansible-modules-extras#769](https://github.com/ansible/ansible-modules-extras/issues/769) and [ansible/ansible-modules-extras#1378](https://github.com/ansible/ansible-modules-extras/pull/1378)
##### ISSUE TYPE

    * Bug Report


##### COMPONENT NAME

zfs
##### ANSIBLE VERSION

```
ansible 2.8.1
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.13 (default, Sep 26 2018, 18:42:22) [GCC 6.3.0 20170516]
```

##### CONFIGURATION

```
ANSIBLE_PIPELINING(/etc/ansible/ansible.cfg) = True
DEFAULT_VAULT_PASSWORD_FILE(env: ANSIBLE_VAULT_PASSWORD_FILE) = /root/.ansible_vault_password.txt
INVENTORY_ENABLED(/etc/ansible/ansible.cfg) = [u'k8s', u'host_list', u'script', u'yaml', u'ini', u'constructed']
RETRY_FILES_ENABLED(/etc/ansible/ansible.cfg) = False
```

##### OS / ENVIRONMENT

Debian GNU/Linux 9.9 (Stretch)
##### STEPS TO REPRODUCE

```yaml
- name: "Create zvol"
  zfs:
    name: mypool/folder/myzvol
    state: present
    extra_zfs_properties:
      volsize: '10G'
      volblocksize: 128K
```

##### EXPECTED RESULTS

Module `zfs` should not try to set immutable attributes like `volblocksize` on existing fileystems/volumes.
##### ACTUAL RESULTS

```
fatal: [nodename]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "createparent": null,
            "extra_zfs_properties": {
                "volblocksize": "128K",
                "volsize": "10G"
            },
            "name": "mypool/folder/myzvol",
            "origin": null,
            "state": "present"
        }
    },
    "msg": "cannot set property for 'mypool/folder/myzvol: 'volblocksize' is readonly\n"
}
```


Similarly with `encryption`:


```yaml
            extra_zfs_properties:
              encryption: aes-256-gcm
              keyformat: passphrase
              keylocation: file:///tmp/zfs-pass
              mountpoint: legacy
```

```
fatal: [barbican]: FAILED! => {"changed": false, "msg": "cannot set property for 'barbican/nixos': 'encryption' is readonly\n"}
```

cf https://github.com/ansible/ansible/issues/58759 https://github.com/ansible/ansible/issues/29422

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `plugins/modules/storage/zfs/zfs.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 
4 # Copyright: (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>
5 # Copyright: (c) 2017, Ansible Project
6 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
7 
8 from __future__ import absolute_import, division, print_function
9 __metaclass__ = type
10 
11 DOCUMENTATION = '''
12 ---
13 module: zfs
14 short_description: Manage zfs
15 description:
16   - Manages ZFS file systems, volumes, clones and snapshots
17 options:
18   name:
19     description:
20       - File system, snapshot or volume name e.g. C(rpool/myfs).
21     required: true
22     type: str
23   state:
24     description:
25       - Whether to create (C(present)), or remove (C(absent)) a
26         file system, snapshot or volume. All parents/children
27         will be created/destroyed as needed to reach the desired state.
28     choices: [ absent, present ]
29     required: true
30     type: str
31   origin:
32     description:
33       - Snapshot from which to create a clone.
34     type: str
35   extra_zfs_properties:
36     description:
37       - A dictionary of zfs properties to be set.
38       - See the zfs(8) man page for more information.
39     type: dict
40 author:
41 - Johan Wiren (@johanwiren)
42 '''
43 
44 EXAMPLES = '''
45 - name: Create a new file system called myfs in pool rpool with the setuid property turned off
46   community.general.zfs:
47     name: rpool/myfs
48     state: present
49     extra_zfs_properties:
50       setuid: off
51 
52 - name: Create a new volume called myvol in pool rpool.
53   community.general.zfs:
54     name: rpool/myvol
55     state: present
56     extra_zfs_properties:
57       volsize: 10M
58 
59 - name: Create a snapshot of rpool/myfs file system.
60   community.general.zfs:
61     name: rpool/myfs@mysnapshot
62     state: present
63 
64 - name: Create a new file system called myfs2 with snapdir enabled
65   community.general.zfs:
66     name: rpool/myfs2
67     state: present
68     extra_zfs_properties:
69       snapdir: enabled
70 
71 - name: Create a new file system by cloning a snapshot
72   community.general.zfs:
73     name: rpool/cloned_fs
74     state: present
75     origin: rpool/myfs@mysnapshot
76 
77 - name: Destroy a filesystem
78   community.general.zfs:
79     name: rpool/myfs
80     state: absent
81 '''
82 
83 import os
84 
85 from ansible.module_utils.basic import AnsibleModule
86 
87 
88 class Zfs(object):
89 
90     def __init__(self, module, name, properties):
91         self.module = module
92         self.name = name
93         self.properties = properties
94         self.changed = False
95         self.zfs_cmd = module.get_bin_path('zfs', True)
96         self.zpool_cmd = module.get_bin_path('zpool', True)
97         self.pool = name.split('/')[0].split('@')[0]
98         self.is_solaris = os.uname()[0] == 'SunOS'
99         self.is_openzfs = self.check_openzfs()
100         self.enhanced_sharing = self.check_enhanced_sharing()
101 
102     def check_openzfs(self):
103         cmd = [self.zpool_cmd]
104         cmd.extend(['get', 'version'])
105         cmd.append(self.pool)
106         (rc, out, err) = self.module.run_command(cmd, check_rc=True)
107         version = out.splitlines()[-1].split()[2]
108         if version == '-':
109             return True
110         if int(version) == 5000:
111             return True
112         return False
113 
114     def check_enhanced_sharing(self):
115         if self.is_solaris and not self.is_openzfs:
116             cmd = [self.zpool_cmd]
117             cmd.extend(['get', 'version'])
118             cmd.append(self.pool)
119             (rc, out, err) = self.module.run_command(cmd, check_rc=True)
120             version = out.splitlines()[-1].split()[2]
121             if int(version) >= 34:
122                 return True
123         return False
124 
125     def exists(self):
126         cmd = [self.zfs_cmd, 'list', '-t', 'all', self.name]
127         (rc, out, err) = self.module.run_command(' '.join(cmd))
128         if rc == 0:
129             return True
130         else:
131             return False
132 
133     def create(self):
134         if self.module.check_mode:
135             self.changed = True
136             return
137         properties = self.properties
138         origin = self.module.params.get('origin', None)
139         cmd = [self.zfs_cmd]
140 
141         if "@" in self.name:
142             action = 'snapshot'
143         elif origin:
144             action = 'clone'
145         else:
146             action = 'create'
147 
148         cmd.append(action)
149 
150         if action in ['create', 'clone']:
151             cmd += ['-p']
152 
153         if properties:
154             for prop, value in properties.items():
155                 if prop == 'volsize':
156                     cmd += ['-V', value]
157                 elif prop == 'volblocksize':
158                     cmd += ['-b', value]
159                 else:
160                     cmd += ['-o', '%s="%s"' % (prop, value)]
161         if origin and action == 'clone':
162             cmd.append(origin)
163         cmd.append(self.name)
164         (rc, out, err) = self.module.run_command(' '.join(cmd))
165         if rc == 0:
166             self.changed = True
167         else:
168             self.module.fail_json(msg=err)
169 
170     def destroy(self):
171         if self.module.check_mode:
172             self.changed = True
173             return
174         cmd = [self.zfs_cmd, 'destroy', '-R', self.name]
175         (rc, out, err) = self.module.run_command(' '.join(cmd))
176         if rc == 0:
177             self.changed = True
178         else:
179             self.module.fail_json(msg=err)
180 
181     def set_property(self, prop, value):
182         if self.module.check_mode:
183             self.changed = True
184             return
185         cmd = [self.zfs_cmd, 'set', prop + '=' + str(value), self.name]
186         (rc, out, err) = self.module.run_command(cmd)
187         if rc == 0:
188             self.changed = True
189         else:
190             self.module.fail_json(msg=err)
191 
192     def set_properties_if_changed(self):
193         current_properties = self.get_current_properties()
194         for prop, value in self.properties.items():
195             if current_properties.get(prop, None) != value:
196                 self.set_property(prop, value)
197 
198     def get_current_properties(self):
199         cmd = [self.zfs_cmd, 'get', '-H']
200         if self.enhanced_sharing:
201             cmd += ['-e']
202         cmd += ['all', self.name]
203         rc, out, err = self.module.run_command(" ".join(cmd))
204         properties = dict()
205         for prop, value, source in [l.split('\t')[1:4] for l in out.splitlines()]:
206             if source == 'local':
207                 properties[prop] = value
208         # Add alias for enhanced sharing properties
209         if self.enhanced_sharing:
210             properties['sharenfs'] = properties.get('share.nfs', None)
211             properties['sharesmb'] = properties.get('share.smb', None)
212         return properties
213 
214 
215 def main():
216 
217     module = AnsibleModule(
218         argument_spec=dict(
219             name=dict(type='str', required=True),
220             state=dict(type='str', required=True, choices=['absent', 'present']),
221             origin=dict(type='str', default=None),
222             extra_zfs_properties=dict(type='dict', default={}),
223         ),
224         supports_check_mode=True,
225     )
226 
227     state = module.params.get('state')
228     name = module.params.get('name')
229 
230     if module.params.get('origin') and '@' in name:
231         module.fail_json(msg='cannot specify origin when operating on a snapshot')
232 
233     # Reverse the boolification of zfs properties
234     for prop, value in module.params['extra_zfs_properties'].items():
235         if isinstance(value, bool):
236             if value is True:
237                 module.params['extra_zfs_properties'][prop] = 'on'
238             else:
239                 module.params['extra_zfs_properties'][prop] = 'off'
240         else:
241             module.params['extra_zfs_properties'][prop] = value
242 
243     result = dict(
244         name=name,
245         state=state,
246     )
247 
248     zfs = Zfs(module, name, module.params['extra_zfs_properties'])
249 
250     if state == 'present':
251         if zfs.exists():
252             zfs.set_properties_if_changed()
253         else:
254             zfs.create()
255 
256     elif state == 'absent':
257         if zfs.exists():
258             zfs.destroy()
259 
260     result.update(zfs.properties)
261     result['changed'] = zfs.changed
262     module.exit_json(**result)
263 
264 
265 if __name__ == '__main__':
266     main()
267
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/plugins/modules/storage/zfs/zfs.py b/plugins/modules/storage/zfs/zfs.py
--- a/plugins/modules/storage/zfs/zfs.py
+++ b/plugins/modules/storage/zfs/zfs.py
@@ -203,7 +203,10 @@
         rc, out, err = self.module.run_command(" ".join(cmd))
         properties = dict()
         for prop, value, source in [l.split('\t')[1:4] for l in out.splitlines()]:
-            if source == 'local':
+            # include source '-' so that creation-only properties are not removed
+            # to avoids errors when the dataset already exists and the property is not changed
+            # this scenario is most likely when the same playbook is run more than once
+            if source == 'local' or source == '-':
                 properties[prop] = value
         # Add alias for enhanced sharing properties
         if self.enhanced_sharing:

{"golden_diff": "diff --git a/plugins/modules/storage/zfs/zfs.py b/plugins/modules/storage/zfs/zfs.py\n--- a/plugins/modules/storage/zfs/zfs.py\n+++ b/plugins/modules/storage/zfs/zfs.py\n@@ -203,7 +203,10 @@\n         rc, out, err = self.module.run_command(\" \".join(cmd))\n         properties = dict()\n         for prop, value, source in [l.split('\\t')[1:4] for l in out.splitlines()]:\n-            if source == 'local':\n+            # include source '-' so that creation-only properties are not removed\n+            # to avoids errors when the dataset already exists and the property is not changed\n+            # this scenario is most likely when the same playbook is run more than once\n+            if source == 'local' or source == '-':\n                 properties[prop] = value\n         # Add alias for enhanced sharing properties\n         if self.enhanced_sharing:\n", "issue": "ZFS tries to change immutable attributes\n##### SUMMARY\r\n\r\nModule `zfs` tries to set immutable attributes like `volblocksize`. Seems to be a regression, see [ansible/ansible-modules-extras#769](https://github.com/ansible/ansible-modules-extras/issues/769) and [ansible/ansible-modules-extras#1378](https://github.com/ansible/ansible-modules-extras/pull/1378)\r\n##### ISSUE TYPE\r\n\r\n    * Bug Report\r\n\r\n\r\n##### COMPONENT NAME\r\n\r\nzfs\r\n##### ANSIBLE VERSION\r\n\r\n```\r\nansible 2.8.1\r\n  config file = /etc/ansible/ansible.cfg\r\n  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']\r\n  ansible python module location = /usr/lib/python2.7/dist-packages/ansible\r\n  executable location = /usr/bin/ansible\r\n  python version = 2.7.13 (default, Sep 26 2018, 18:42:22) [GCC 6.3.0 20170516]\r\n```\r\n\r\n##### CONFIGURATION\r\n\r\n```\r\nANSIBLE_PIPELINING(/etc/ansible/ansible.cfg) = True\r\nDEFAULT_VAULT_PASSWORD_FILE(env: ANSIBLE_VAULT_PASSWORD_FILE) = /root/.ansible_vault_password.txt\r\nINVENTORY_ENABLED(/etc/ansible/ansible.cfg) = [u'k8s', u'host_list', u'script', u'yaml', u'ini', u'constructed']\r\nRETRY_FILES_ENABLED(/etc/ansible/ansible.cfg) = False\r\n```\r\n\r\n##### OS / ENVIRONMENT\r\n\r\nDebian GNU/Linux 9.9 (Stretch)\r\n##### STEPS TO REPRODUCE\r\n\r\n```yaml\r\n- name: \"Create zvol\"\r\n  zfs:\r\n    name: mypool/folder/myzvol\r\n    state: present\r\n    extra_zfs_properties:\r\n      volsize: '10G'\r\n      volblocksize: 128K\r\n```\r\n\r\n##### EXPECTED RESULTS\r\n\r\nModule `zfs` should not try to set immutable attributes like `volblocksize` on existing fileystems/volumes.\r\n##### ACTUAL RESULTS\r\n\r\n```\r\nfatal: [nodename]: FAILED! => {\r\n    \"changed\": false,\r\n    \"invocation\": {\r\n        \"module_args\": {\r\n            \"createparent\": null,\r\n            \"extra_zfs_properties\": {\r\n                \"volblocksize\": \"128K\",\r\n                \"volsize\": \"10G\"\r\n            },\r\n            \"name\": \"mypool/folder/myzvol\",\r\n            \"origin\": null,\r\n            \"state\": \"present\"\r\n        }\r\n    },\r\n    \"msg\": \"cannot set property for 'mypool/folder/myzvol: 'volblocksize' is readonly\\n\"\r\n}\r\n```\r\n\r\n\r\nSimilarly with `encryption`:\r\n\r\n\r\n```yaml\r\n            extra_zfs_properties:\r\n              encryption: aes-256-gcm\r\n              keyformat: passphrase\r\n              keylocation: file:///tmp/zfs-pass\r\n              mountpoint: legacy\r\n```\r\n\r\n```\r\nfatal: [barbican]: FAILED! => {\"changed\": false, \"msg\": \"cannot set property for 'barbican/nixos': 'encryption' is readonly\\n\"}\r\n```\r\n\r\ncf https://github.com/ansible/ansible/issues/58759 https://github.com/ansible/ansible/issues/29422\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>\n# Copyright: (c) 2017, Ansible Project\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n\nDOCUMENTATION = '''\n---\nmodule: zfs\nshort_description: Manage zfs\ndescription:\n  - Manages ZFS file systems, volumes, clones and snapshots\noptions:\n  name:\n    description:\n      - File system, snapshot or volume name e.g. C(rpool/myfs).\n    required: true\n    type: str\n  state:\n    description:\n      - Whether to create (C(present)), or remove (C(absent)) a\n        file system, snapshot or volume. All parents/children\n        will be created/destroyed as needed to reach the desired state.\n    choices: [ absent, present ]\n    required: true\n    type: str\n  origin:\n    description:\n      - Snapshot from which to create a clone.\n    type: str\n  extra_zfs_properties:\n    description:\n      - A dictionary of zfs properties to be set.\n      - See the zfs(8) man page for more information.\n    type: dict\nauthor:\n- Johan Wiren (@johanwiren)\n'''\n\nEXAMPLES = '''\n- name: Create a new file system called myfs in pool rpool with the setuid property turned off\n  community.general.zfs:\n    name: rpool/myfs\n    state: present\n    extra_zfs_properties:\n      setuid: off\n\n- name: Create a new volume called myvol in pool rpool.\n  community.general.zfs:\n    name: rpool/myvol\n    state: present\n    extra_zfs_properties:\n      volsize: 10M\n\n- name: Create a snapshot of rpool/myfs file system.\n  community.general.zfs:\n    name: rpool/myfs@mysnapshot\n    state: present\n\n- name: Create a new file system called myfs2 with snapdir enabled\n  community.general.zfs:\n    name: rpool/myfs2\n    state: present\n    extra_zfs_properties:\n      snapdir: enabled\n\n- name: Create a new file system by cloning a snapshot\n  community.general.zfs:\n    name: rpool/cloned_fs\n    state: present\n    origin: rpool/myfs@mysnapshot\n\n- name: Destroy a filesystem\n  community.general.zfs:\n    name: rpool/myfs\n    state: absent\n'''\n\nimport os\n\nfrom ansible.module_utils.basic import AnsibleModule\n\n\nclass Zfs(object):\n\n    def __init__(self, module, name, properties):\n        self.module = module\n        self.name = name\n        self.properties = properties\n        self.changed = False\n        self.zfs_cmd = module.get_bin_path('zfs', True)\n        self.zpool_cmd = module.get_bin_path('zpool', True)\n        self.pool = name.split('/')[0].split('@')[0]\n        self.is_solaris = os.uname()[0] == 'SunOS'\n        self.is_openzfs = self.check_openzfs()\n        self.enhanced_sharing = self.check_enhanced_sharing()\n\n    def check_openzfs(self):\n        cmd = [self.zpool_cmd]\n        cmd.extend(['get', 'version'])\n        cmd.append(self.pool)\n        (rc, out, err) = self.module.run_command(cmd, check_rc=True)\n        version = out.splitlines()[-1].split()[2]\n        if version == '-':\n            return True\n        if int(version) == 5000:\n            return True\n        return False\n\n    def check_enhanced_sharing(self):\n        if self.is_solaris and not self.is_openzfs:\n            cmd = [self.zpool_cmd]\n            cmd.extend(['get', 'version'])\n            cmd.append(self.pool)\n            (rc, out, err) = self.module.run_command(cmd, check_rc=True)\n            version = out.splitlines()[-1].split()[2]\n            if int(version) >= 34:\n                return True\n        return False\n\n    def exists(self):\n        cmd = [self.zfs_cmd, 'list', '-t', 'all', self.name]\n        (rc, out, err) = self.module.run_command(' '.join(cmd))\n        if rc == 0:\n            return True\n        else:\n            return False\n\n    def create(self):\n        if self.module.check_mode:\n            self.changed = True\n            return\n        properties = self.properties\n        origin = self.module.params.get('origin', None)\n        cmd = [self.zfs_cmd]\n\n        if \"@\" in self.name:\n            action = 'snapshot'\n        elif origin:\n            action = 'clone'\n        else:\n            action = 'create'\n\n        cmd.append(action)\n\n        if action in ['create', 'clone']:\n            cmd += ['-p']\n\n        if properties:\n            for prop, value in properties.items():\n                if prop == 'volsize':\n                    cmd += ['-V', value]\n                elif prop == 'volblocksize':\n                    cmd += ['-b', value]\n                else:\n                    cmd += ['-o', '%s=\"%s\"' % (prop, value)]\n        if origin and action == 'clone':\n            cmd.append(origin)\n        cmd.append(self.name)\n        (rc, out, err) = self.module.run_command(' '.join(cmd))\n        if rc == 0:\n            self.changed = True\n        else:\n            self.module.fail_json(msg=err)\n\n    def destroy(self):\n        if self.module.check_mode:\n            self.changed = True\n            return\n        cmd = [self.zfs_cmd, 'destroy', '-R', self.name]\n        (rc, out, err) = self.module.run_command(' '.join(cmd))\n        if rc == 0:\n            self.changed = True\n        else:\n            self.module.fail_json(msg=err)\n\n    def set_property(self, prop, value):\n        if self.module.check_mode:\n            self.changed = True\n            return\n        cmd = [self.zfs_cmd, 'set', prop + '=' + str(value), self.name]\n        (rc, out, err) = self.module.run_command(cmd)\n        if rc == 0:\n            self.changed = True\n        else:\n            self.module.fail_json(msg=err)\n\n    def set_properties_if_changed(self):\n        current_properties = self.get_current_properties()\n        for prop, value in self.properties.items():\n            if current_properties.get(prop, None) != value:\n                self.set_property(prop, value)\n\n    def get_current_properties(self):\n        cmd = [self.zfs_cmd, 'get', '-H']\n        if self.enhanced_sharing:\n            cmd += ['-e']\n        cmd += ['all', self.name]\n        rc, out, err = self.module.run_command(\" \".join(cmd))\n        properties = dict()\n        for prop, value, source in [l.split('\\t')[1:4] for l in out.splitlines()]:\n            if source == 'local':\n                properties[prop] = value\n        # Add alias for enhanced sharing properties\n        if self.enhanced_sharing:\n            properties['sharenfs'] = properties.get('share.nfs', None)\n            properties['sharesmb'] = properties.get('share.smb', None)\n        return properties\n\n\ndef main():\n\n    module = AnsibleModule(\n        argument_spec=dict(\n            name=dict(type='str', required=True),\n            state=dict(type='str', required=True, choices=['absent', 'present']),\n            origin=dict(type='str', default=None),\n            extra_zfs_properties=dict(type='dict', default={}),\n        ),\n        supports_check_mode=True,\n    )\n\n    state = module.params.get('state')\n    name = module.params.get('name')\n\n    if module.params.get('origin') and '@' in name:\n        module.fail_json(msg='cannot specify origin when operating on a snapshot')\n\n    # Reverse the boolification of zfs properties\n    for prop, value in module.params['extra_zfs_properties'].items():\n        if isinstance(value, bool):\n            if value is True:\n                module.params['extra_zfs_properties'][prop] = 'on'\n            else:\n                module.params['extra_zfs_properties'][prop] = 'off'\n        else:\n            module.params['extra_zfs_properties'][prop] = value\n\n    result = dict(\n        name=name,\n        state=state,\n    )\n\n    zfs = Zfs(module, name, module.params['extra_zfs_properties'])\n\n    if state == 'present':\n        if zfs.exists():\n            zfs.set_properties_if_changed()\n        else:\n            zfs.create()\n\n    elif state == 'absent':\n        if zfs.exists():\n            zfs.destroy()\n\n    result.update(zfs.properties)\n    result['changed'] = zfs.changed\n    module.exit_json(**result)\n\n\nif __name__ == '__main__':\n    main()\n", "path": "plugins/modules/storage/zfs/zfs.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>\n# Copyright: (c) 2017, Ansible Project\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n\nDOCUMENTATION = '''\n---\nmodule: zfs\nshort_description: Manage zfs\ndescription:\n  - Manages ZFS file systems, volumes, clones and snapshots\noptions:\n  name:\n    description:\n      - File system, snapshot or volume name e.g. C(rpool/myfs).\n    required: true\n    type: str\n  state:\n    description:\n      - Whether to create (C(present)), or remove (C(absent)) a\n        file system, snapshot or volume. All parents/children\n        will be created/destroyed as needed to reach the desired state.\n    choices: [ absent, present ]\n    required: true\n    type: str\n  origin:\n    description:\n      - Snapshot from which to create a clone.\n    type: str\n  extra_zfs_properties:\n    description:\n      - A dictionary of zfs properties to be set.\n      - See the zfs(8) man page for more information.\n    type: dict\nauthor:\n- Johan Wiren (@johanwiren)\n'''\n\nEXAMPLES = '''\n- name: Create a new file system called myfs in pool rpool with the setuid property turned off\n  community.general.zfs:\n    name: rpool/myfs\n    state: present\n    extra_zfs_properties:\n      setuid: off\n\n- name: Create a new volume called myvol in pool rpool.\n  community.general.zfs:\n    name: rpool/myvol\n    state: present\n    extra_zfs_properties:\n      volsize: 10M\n\n- name: Create a snapshot of rpool/myfs file system.\n  community.general.zfs:\n    name: rpool/myfs@mysnapshot\n    state: present\n\n- name: Create a new file system called myfs2 with snapdir enabled\n  community.general.zfs:\n    name: rpool/myfs2\n    state: present\n    extra_zfs_properties:\n      snapdir: enabled\n\n- name: Create a new file system by cloning a snapshot\n  community.general.zfs:\n    name: rpool/cloned_fs\n    state: present\n    origin: rpool/myfs@mysnapshot\n\n- name: Destroy a filesystem\n  community.general.zfs:\n    name: rpool/myfs\n    state: absent\n'''\n\nimport os\n\nfrom ansible.module_utils.basic import AnsibleModule\n\n\nclass Zfs(object):\n\n    def __init__(self, module, name, properties):\n        self.module = module\n        self.name = name\n        self.properties = properties\n        self.changed = False\n        self.zfs_cmd = module.get_bin_path('zfs', True)\n        self.zpool_cmd = module.get_bin_path('zpool', True)\n        self.pool = name.split('/')[0].split('@')[0]\n        self.is_solaris = os.uname()[0] == 'SunOS'\n        self.is_openzfs = self.check_openzfs()\n        self.enhanced_sharing = self.check_enhanced_sharing()\n\n    def check_openzfs(self):\n        cmd = [self.zpool_cmd]\n        cmd.extend(['get', 'version'])\n        cmd.append(self.pool)\n        (rc, out, err) = self.module.run_command(cmd, check_rc=True)\n        version = out.splitlines()[-1].split()[2]\n        if version == '-':\n            return True\n        if int(version) == 5000:\n            return True\n        return False\n\n    def check_enhanced_sharing(self):\n        if self.is_solaris and not self.is_openzfs:\n            cmd = [self.zpool_cmd]\n            cmd.extend(['get', 'version'])\n            cmd.append(self.pool)\n            (rc, out, err) = self.module.run_command(cmd, check_rc=True)\n            version = out.splitlines()[-1].split()[2]\n            if int(version) >= 34:\n                return True\n        return False\n\n    def exists(self):\n        cmd = [self.zfs_cmd, 'list', '-t', 'all', self.name]\n        (rc, out, err) = self.module.run_command(' '.join(cmd))\n        if rc == 0:\n            return True\n        else:\n            return False\n\n    def create(self):\n        if self.module.check_mode:\n            self.changed = True\n            return\n        properties = self.properties\n        origin = self.module.params.get('origin', None)\n        cmd = [self.zfs_cmd]\n\n        if \"@\" in self.name:\n            action = 'snapshot'\n        elif origin:\n            action = 'clone'\n        else:\n            action = 'create'\n\n        cmd.append(action)\n\n        if action in ['create', 'clone']:\n            cmd += ['-p']\n\n        if properties:\n            for prop, value in properties.items():\n                if prop == 'volsize':\n                    cmd += ['-V', value]\n                elif prop == 'volblocksize':\n                    cmd += ['-b', value]\n                else:\n                    cmd += ['-o', '%s=\"%s\"' % (prop, value)]\n        if origin and action == 'clone':\n            cmd.append(origin)\n        cmd.append(self.name)\n        (rc, out, err) = self.module.run_command(' '.join(cmd))\n        if rc == 0:\n            self.changed = True\n        else:\n            self.module.fail_json(msg=err)\n\n    def destroy(self):\n        if self.module.check_mode:\n            self.changed = True\n            return\n        cmd = [self.zfs_cmd, 'destroy', '-R', self.name]\n        (rc, out, err) = self.module.run_command(' '.join(cmd))\n        if rc == 0:\n            self.changed = True\n        else:\n            self.module.fail_json(msg=err)\n\n    def set_property(self, prop, value):\n        if self.module.check_mode:\n            self.changed = True\n            return\n        cmd = [self.zfs_cmd, 'set', prop + '=' + str(value), self.name]\n        (rc, out, err) = self.module.run_command(cmd)\n        if rc == 0:\n            self.changed = True\n        else:\n            self.module.fail_json(msg=err)\n\n    def set_properties_if_changed(self):\n        current_properties = self.get_current_properties()\n        for prop, value in self.properties.items():\n            if current_properties.get(prop, None) != value:\n                self.set_property(prop, value)\n\n    def get_current_properties(self):\n        cmd = [self.zfs_cmd, 'get', '-H']\n        if self.enhanced_sharing:\n            cmd += ['-e']\n        cmd += ['all', self.name]\n        rc, out, err = self.module.run_command(\" \".join(cmd))\n        properties = dict()\n        for prop, value, source in [l.split('\\t')[1:4] for l in out.splitlines()]:\n            # include source '-' so that creation-only properties are not removed\n            # to avoids errors when the dataset already exists and the property is not changed\n            # this scenario is most likely when the same playbook is run more than once\n            if source == 'local' or source == '-':\n                properties[prop] = value\n        # Add alias for enhanced sharing properties\n        if self.enhanced_sharing:\n            properties['sharenfs'] = properties.get('share.nfs', None)\n            properties['sharesmb'] = properties.get('share.smb', None)\n        return properties\n\n\ndef main():\n\n    module = AnsibleModule(\n        argument_spec=dict(\n            name=dict(type='str', required=True),\n            state=dict(type='str', required=True, choices=['absent', 'present']),\n            origin=dict(type='str', default=None),\n            extra_zfs_properties=dict(type='dict', default={}),\n        ),\n        supports_check_mode=True,\n    )\n\n    state = module.params.get('state')\n    name = module.params.get('name')\n\n    if module.params.get('origin') and '@' in name:\n        module.fail_json(msg='cannot specify origin when operating on a snapshot')\n\n    # Reverse the boolification of zfs properties\n    for prop, value in module.params['extra_zfs_properties'].items():\n        if isinstance(value, bool):\n            if value is True:\n                module.params['extra_zfs_properties'][prop] = 'on'\n            else:\n                module.params['extra_zfs_properties'][prop] = 'off'\n        else:\n            module.params['extra_zfs_properties'][prop] = value\n\n    result = dict(\n        name=name,\n        state=state,\n    )\n\n    zfs = Zfs(module, name, module.params['extra_zfs_properties'])\n\n    if state == 'present':\n        if zfs.exists():\n            zfs.set_properties_if_changed()\n        else:\n            zfs.create()\n\n    elif state == 'absent':\n        if zfs.exists():\n            zfs.destroy()\n\n    result.update(zfs.properties)\n    result['changed'] = zfs.changed\n    module.exit_json(**result)\n\n\nif __name__ == '__main__':\n    main()\n", "path": "plugins/modules/storage/zfs/zfs.py"}]}