diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/general/plugins/modules/zfs.py | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/zfs.py')
-rw-r--r-- | ansible_collections/community/general/plugins/modules/zfs.py | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/modules/zfs.py b/ansible_collections/community/general/plugins/modules/zfs.py new file mode 100644 index 000000000..4cd79c36e --- /dev/null +++ b/ansible_collections/community/general/plugins/modules/zfs.py @@ -0,0 +1,295 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2013, Johan Wiren <johan.wiren.se@gmail.com> +# Copyright (c) 2017, Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: zfs +short_description: Manage zfs +description: + - Manages ZFS file systems, volumes, clones and snapshots +extends_documentation_fragment: + - community.general.attributes +attributes: + check_mode: + support: partial + details: + - In certain situations it may report a task as changed that will not be reported + as changed when C(check_mode) is disabled. + - For example, this might occur when the zpool C(altroot) option is set or when + a size is written using human-readable notation, such as C(1M) or C(1024K), + instead of as an unqualified byte count, such as C(1048576). + diff_mode: + support: full +options: + name: + description: + - File system, snapshot or volume name e.g. C(rpool/myfs). + required: true + type: str + state: + description: + - Whether to create (C(present)), or remove (C(absent)) a + file system, snapshot or volume. All parents/children + will be created/destroyed as needed to reach the desired state. + choices: [ absent, present ] + required: true + type: str + origin: + description: + - Snapshot from which to create a clone. + type: str + extra_zfs_properties: + description: + - A dictionary of zfs properties to be set. + - See the zfs(8) man page for more information. + type: dict + default: {} +author: +- Johan Wiren (@johanwiren) +''' + +EXAMPLES = ''' +- name: Create a new file system called myfs in pool rpool with the setuid property turned off + community.general.zfs: + name: rpool/myfs + state: present + extra_zfs_properties: + setuid: 'off' + +- name: Create a new volume called myvol in pool rpool. + community.general.zfs: + name: rpool/myvol + state: present + extra_zfs_properties: + volsize: 10M + +- name: Create a snapshot of rpool/myfs file system. + community.general.zfs: + name: rpool/myfs@mysnapshot + state: present + +- name: Create a new file system called myfs2 with snapdir enabled + community.general.zfs: + name: rpool/myfs2 + state: present + extra_zfs_properties: + snapdir: enabled + +- name: Create a new file system by cloning a snapshot + community.general.zfs: + name: rpool/cloned_fs + state: present + origin: rpool/myfs@mysnapshot + +- name: Destroy a filesystem + community.general.zfs: + name: rpool/myfs + state: absent +''' + +import os + +from ansible.module_utils.basic import AnsibleModule + + +class Zfs(object): + + def __init__(self, module, name, properties): + self.module = module + self.name = name + self.properties = properties + self.changed = False + self.zfs_cmd = module.get_bin_path('zfs', True) + self.zpool_cmd = module.get_bin_path('zpool', True) + self.pool = name.split('/')[0].split('@')[0] + self.is_solaris = os.uname()[0] == 'SunOS' + self.is_openzfs = self.check_openzfs() + self.enhanced_sharing = self.check_enhanced_sharing() + + def check_openzfs(self): + cmd = [self.zpool_cmd] + cmd.extend(['get', 'version']) + cmd.append(self.pool) + (rc, out, err) = self.module.run_command(cmd, check_rc=True) + version = out.splitlines()[-1].split()[2] + if version == '-': + return True + if int(version) == 5000: + return True + return False + + def check_enhanced_sharing(self): + if self.is_solaris and not self.is_openzfs: + cmd = [self.zpool_cmd] + cmd.extend(['get', 'version']) + cmd.append(self.pool) + (rc, out, err) = self.module.run_command(cmd, check_rc=True) + version = out.splitlines()[-1].split()[2] + if int(version) >= 34: + return True + return False + + def exists(self): + cmd = [self.zfs_cmd, 'list', '-t', 'all', self.name] + rc, dummy, dummy = self.module.run_command(cmd) + return rc == 0 + + def create(self): + if self.module.check_mode: + self.changed = True + return + properties = self.properties + origin = self.module.params.get('origin') + cmd = [self.zfs_cmd] + + if "@" in self.name: + action = 'snapshot' + elif origin: + action = 'clone' + else: + action = 'create' + + cmd.append(action) + + if action in ['create', 'clone']: + cmd += ['-p'] + + if properties: + for prop, value in properties.items(): + if prop == 'volsize': + cmd += ['-V', value] + elif prop == 'volblocksize': + cmd += ['-b', value] + else: + cmd += ['-o', '%s=%s' % (prop, value)] + if origin and action == 'clone': + cmd.append(origin) + cmd.append(self.name) + self.module.run_command(cmd, check_rc=True) + self.changed = True + + def destroy(self): + if self.module.check_mode: + self.changed = True + return + cmd = [self.zfs_cmd, 'destroy', '-R', self.name] + self.module.run_command(cmd, check_rc=True) + self.changed = True + + def set_property(self, prop, value): + if self.module.check_mode: + self.changed = True + return + cmd = [self.zfs_cmd, 'set', prop + '=' + str(value), self.name] + self.module.run_command(cmd, check_rc=True) + + def set_properties_if_changed(self): + diff = {'before': {'extra_zfs_properties': {}}, 'after': {'extra_zfs_properties': {}}} + current_properties = self.get_current_properties() + for prop, value in self.properties.items(): + current_value = current_properties.get(prop, None) + if current_value != value: + self.set_property(prop, value) + diff['before']['extra_zfs_properties'][prop] = current_value + diff['after']['extra_zfs_properties'][prop] = value + if self.module.check_mode: + return diff + updated_properties = self.get_current_properties() + for prop in self.properties: + value = updated_properties.get(prop, None) + if value is None: + self.module.fail_json(msg="zfsprop was not present after being successfully set: %s" % prop) + if current_properties.get(prop, None) != value: + self.changed = True + if prop in diff['after']['extra_zfs_properties']: + diff['after']['extra_zfs_properties'][prop] = value + return diff + + def get_current_properties(self): + cmd = [self.zfs_cmd, 'get', '-H', '-p', '-o', "property,value,source"] + if self.enhanced_sharing: + cmd += ['-e'] + cmd += ['all', self.name] + rc, out, err = self.module.run_command(cmd) + properties = dict() + for line in out.splitlines(): + prop, value, source = line.split('\t') + # 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 in ('local', 'received', '-'): + properties[prop] = value + # Add alias for enhanced sharing properties + if self.enhanced_sharing: + properties['sharenfs'] = properties.get('share.nfs', None) + properties['sharesmb'] = properties.get('share.smb', None) + return properties + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + state=dict(type='str', required=True, choices=['absent', 'present']), + origin=dict(type='str'), + extra_zfs_properties=dict(type='dict', default={}), + ), + supports_check_mode=True, + ) + + state = module.params.get('state') + name = module.params.get('name') + + if module.params.get('origin') and '@' in name: + module.fail_json(msg='cannot specify origin when operating on a snapshot') + + # Reverse the boolification of zfs properties + for prop, value in module.params['extra_zfs_properties'].items(): + if isinstance(value, bool): + if value is True: + module.params['extra_zfs_properties'][prop] = 'on' + else: + module.params['extra_zfs_properties'][prop] = 'off' + else: + module.params['extra_zfs_properties'][prop] = value + + result = dict( + name=name, + state=state, + ) + + zfs = Zfs(module, name, module.params['extra_zfs_properties']) + + if state == 'present': + if zfs.exists(): + result['diff'] = zfs.set_properties_if_changed() + else: + zfs.create() + result['diff'] = {'before': {'state': 'absent'}, 'after': {'state': state}} + + elif state == 'absent': + if zfs.exists(): + zfs.destroy() + result['diff'] = {'before': {'state': 'present'}, 'after': {'state': state}} + else: + result['diff'] = {} + + result['diff']['before_header'] = name + result['diff']['after_header'] = name + + result.update(zfs.properties) + result['changed'] = zfs.changed + module.exit_json(**result) + + +if __name__ == '__main__': + main() |