summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/modules/beadm.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/beadm.py')
-rw-r--r--ansible_collections/community/general/plugins/modules/beadm.py415
1 files changed, 415 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/modules/beadm.py b/ansible_collections/community/general/plugins/modules/beadm.py
new file mode 100644
index 000000000..8857fd846
--- /dev/null
+++ b/ansible_collections/community/general/plugins/modules/beadm.py
@@ -0,0 +1,415 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016, Adam Števko <adam.stevko@gmail.com>
+# 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 = r'''
+---
+module: beadm
+short_description: Manage ZFS boot environments on FreeBSD/Solaris/illumos systems
+description:
+ - Create, delete or activate ZFS boot environments.
+ - Mount and unmount ZFS boot environments.
+author: Adam Števko (@xen0l)
+extends_documentation_fragment:
+ - community.general.attributes
+attributes:
+ check_mode:
+ support: full
+ diff_mode:
+ support: none
+options:
+ name:
+ description:
+ - ZFS boot environment name.
+ type: str
+ required: true
+ aliases: [ "be" ]
+ snapshot:
+ description:
+ - If specified, the new boot environment will be cloned from the given
+ snapshot or inactive boot environment.
+ type: str
+ description:
+ description:
+ - Associate a description with a new boot environment. This option is
+ available only on Solarish platforms.
+ type: str
+ options:
+ description:
+ - Create the datasets for new BE with specific ZFS properties.
+ - Multiple options can be specified.
+ - This option is available only on Solarish platforms.
+ type: str
+ mountpoint:
+ description:
+ - Path where to mount the ZFS boot environment.
+ type: path
+ state:
+ description:
+ - Create or delete ZFS boot environment.
+ type: str
+ choices: [ absent, activated, mounted, present, unmounted ]
+ default: present
+ force:
+ description:
+ - Specifies if the unmount should be forced.
+ type: bool
+ default: false
+'''
+
+EXAMPLES = r'''
+- name: Create ZFS boot environment
+ community.general.beadm:
+ name: upgrade-be
+ state: present
+
+- name: Create ZFS boot environment from existing inactive boot environment
+ community.general.beadm:
+ name: upgrade-be
+ snapshot: be@old
+ state: present
+
+- name: Create ZFS boot environment with compression enabled and description "upgrade"
+ community.general.beadm:
+ name: upgrade-be
+ options: "compression=on"
+ description: upgrade
+ state: present
+
+- name: Delete ZFS boot environment
+ community.general.beadm:
+ name: old-be
+ state: absent
+
+- name: Mount ZFS boot environment on /tmp/be
+ community.general.beadm:
+ name: BE
+ mountpoint: /tmp/be
+ state: mounted
+
+- name: Unmount ZFS boot environment
+ community.general.beadm:
+ name: BE
+ state: unmounted
+
+- name: Activate ZFS boot environment
+ community.general.beadm:
+ name: upgrade-be
+ state: activated
+'''
+
+RETURN = r'''
+name:
+ description: BE name
+ returned: always
+ type: str
+ sample: pre-upgrade
+snapshot:
+ description: ZFS snapshot to create BE from
+ returned: always
+ type: str
+ sample: rpool/ROOT/oi-hipster@fresh
+description:
+ description: BE description
+ returned: always
+ type: str
+ sample: Upgrade from 9.0 to 10.0
+options:
+ description: BE additional options
+ returned: always
+ type: str
+ sample: compression=on
+mountpoint:
+ description: BE mountpoint
+ returned: always
+ type: str
+ sample: /mnt/be
+state:
+ description: state of the target
+ returned: always
+ type: str
+ sample: present
+force:
+ description: If forced action is wanted
+ returned: always
+ type: bool
+ sample: false
+'''
+
+import os
+from ansible.module_utils.basic import AnsibleModule
+
+
+class BE(object):
+ def __init__(self, module):
+ self.module = module
+
+ self.name = module.params['name']
+ self.snapshot = module.params['snapshot']
+ self.description = module.params['description']
+ self.options = module.params['options']
+ self.mountpoint = module.params['mountpoint']
+ self.state = module.params['state']
+ self.force = module.params['force']
+ self.is_freebsd = os.uname()[0] == 'FreeBSD'
+
+ def _beadm_list(self):
+ cmd = [self.module.get_bin_path('beadm'), 'list', '-H']
+ if '@' in self.name:
+ cmd.append('-s')
+ return self.module.run_command(cmd)
+
+ def _find_be_by_name(self, out):
+ if '@' in self.name:
+ for line in out.splitlines():
+ if self.is_freebsd:
+ check = line.split()
+ if check == []:
+ continue
+ full_name = check[0].split('/')
+ if full_name == []:
+ continue
+ check[0] = full_name[len(full_name) - 1]
+ if check[0] == self.name:
+ return check
+ else:
+ check = line.split(';')
+ if check[0] == self.name:
+ return check
+ else:
+ for line in out.splitlines():
+ if self.is_freebsd:
+ check = line.split()
+ if check[0] == self.name:
+ return check
+ else:
+ check = line.split(';')
+ if check[0] == self.name:
+ return check
+ return None
+
+ def exists(self):
+ (rc, out, dummy) = self._beadm_list()
+
+ if rc == 0:
+ if self._find_be_by_name(out):
+ return True
+ else:
+ return False
+ else:
+ return False
+
+ def is_activated(self):
+ (rc, out, dummy) = self._beadm_list()
+
+ if rc == 0:
+ line = self._find_be_by_name(out)
+ if line is None:
+ return False
+ if self.is_freebsd:
+ if 'R' in line[1]:
+ return True
+ else:
+ if 'R' in line[2]:
+ return True
+
+ return False
+
+ def activate_be(self):
+ cmd = [self.module.get_bin_path('beadm'), 'activate', self.name]
+ return self.module.run_command(cmd)
+
+ def create_be(self):
+ cmd = [self.module.get_bin_path('beadm'), 'create']
+
+ if self.snapshot:
+ cmd.extend(['-e', self.snapshot])
+ if not self.is_freebsd:
+ if self.description:
+ cmd.extend(['-d', self.description])
+ if self.options:
+ cmd.extend(['-o', self.options])
+
+ cmd.append(self.name)
+
+ return self.module.run_command(cmd)
+
+ def destroy_be(self):
+ cmd = [self.module.get_bin_path('beadm'), 'destroy', '-F', self.name]
+ return self.module.run_command(cmd)
+
+ def is_mounted(self):
+ (rc, out, dummy) = self._beadm_list()
+
+ if rc == 0:
+ line = self._find_be_by_name(out)
+ if line is None:
+ return False
+ if self.is_freebsd:
+ # On FreeBSD, we exclude currently mounted BE on /, as it is
+ # special and can be activated even if it is mounted. That is not
+ # possible with non-root BEs.
+ if line[2] != '-' and line[2] != '/':
+ return True
+ else:
+ if line[3]:
+ return True
+
+ return False
+
+ def mount_be(self):
+ cmd = [self.module.get_bin_path('beadm'), 'mount', self.name]
+
+ if self.mountpoint:
+ cmd.append(self.mountpoint)
+
+ return self.module.run_command(cmd)
+
+ def unmount_be(self):
+ cmd = [self.module.get_bin_path('beadm'), 'unmount']
+ if self.force:
+ cmd.append('-f')
+ cmd.append(self.name)
+
+ return self.module.run_command(cmd)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ name=dict(type='str', required=True, aliases=['be']),
+ snapshot=dict(type='str'),
+ description=dict(type='str'),
+ options=dict(type='str'),
+ mountpoint=dict(type='path'),
+ state=dict(type='str', default='present', choices=['absent', 'activated', 'mounted', 'present', 'unmounted']),
+ force=dict(type='bool', default=False),
+ ),
+ supports_check_mode=True,
+ )
+
+ be = BE(module)
+
+ rc = None
+ out = ''
+ err = ''
+ result = {}
+ result['name'] = be.name
+ result['state'] = be.state
+
+ if be.snapshot:
+ result['snapshot'] = be.snapshot
+
+ if be.description:
+ result['description'] = be.description
+
+ if be.options:
+ result['options'] = be.options
+
+ if be.mountpoint:
+ result['mountpoint'] = be.mountpoint
+
+ if be.state == 'absent':
+ # beadm on FreeBSD and Solarish systems differs in delete behaviour in
+ # that we are not allowed to delete activated BE on FreeBSD while on
+ # Solarish systems we cannot delete BE if it is mounted. We add mount
+ # check for both platforms as BE should be explicitly unmounted before
+ # being deleted. On FreeBSD, we also check if the BE is activated.
+ if be.exists():
+ if not be.is_mounted():
+ if module.check_mode:
+ module.exit_json(changed=True)
+
+ if be.is_freebsd:
+ if be.is_activated():
+ module.fail_json(msg='Unable to remove active BE!')
+
+ (rc, out, err) = be.destroy_be()
+
+ if rc != 0:
+ module.fail_json(msg='Error while destroying BE: "%s"' % err,
+ name=be.name,
+ stderr=err,
+ rc=rc)
+ else:
+ module.fail_json(msg='Unable to remove BE as it is mounted!')
+
+ elif be.state == 'present':
+ if not be.exists():
+ if module.check_mode:
+ module.exit_json(changed=True)
+
+ (rc, out, err) = be.create_be()
+
+ if rc != 0:
+ module.fail_json(msg='Error while creating BE: "%s"' % err,
+ name=be.name,
+ stderr=err,
+ rc=rc)
+
+ elif be.state == 'activated':
+ if not be.is_activated():
+ if module.check_mode:
+ module.exit_json(changed=True)
+
+ # On FreeBSD, beadm is unable to activate mounted BEs, so we add
+ # an explicit check for that case.
+ if be.is_freebsd:
+ if be.is_mounted():
+ module.fail_json(msg='Unable to activate mounted BE!')
+
+ (rc, out, err) = be.activate_be()
+
+ if rc != 0:
+ module.fail_json(msg='Error while activating BE: "%s"' % err,
+ name=be.name,
+ stderr=err,
+ rc=rc)
+ elif be.state == 'mounted':
+ if not be.is_mounted():
+ if module.check_mode:
+ module.exit_json(changed=True)
+
+ (rc, out, err) = be.mount_be()
+
+ if rc != 0:
+ module.fail_json(msg='Error while mounting BE: "%s"' % err,
+ name=be.name,
+ stderr=err,
+ rc=rc)
+
+ elif be.state == 'unmounted':
+ if be.is_mounted():
+ if module.check_mode:
+ module.exit_json(changed=True)
+
+ (rc, out, err) = be.unmount_be()
+
+ if rc != 0:
+ module.fail_json(msg='Error while unmounting BE: "%s"' % err,
+ name=be.name,
+ stderr=err,
+ rc=rc)
+
+ if rc is None:
+ result['changed'] = False
+ else:
+ result['changed'] = True
+
+ if out:
+ result['stdout'] = out
+ if err:
+ result['stderr'] = err
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()