#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2018, Milan Ilic # 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 # Make coding more python3-ish from __future__ import (absolute_import, division, print_function) __metaclass__ = type DOCUMENTATION = r""" module: one_image short_description: Manages OpenNebula images description: - Manages OpenNebula images. requirements: - pyone extends_documentation_fragment: - community.general.opennebula - community.general.attributes attributes: check_mode: support: full diff_mode: support: none options: id: description: - A O(id) of the image you would like to manage. type: int name: description: - A O(name) of the image you would like to manage. - Required if O(create=true). type: str state: description: - V(present) - state that is used to manage the image. - V(absent) - delete the image. - V(cloned) - clone the image. - V(renamed) - rename the image to the O(new_name). choices: ["present", "absent", "cloned", "renamed"] default: present type: str enabled: description: - Whether the image should be enabled or disabled. type: bool new_name: description: - A name that will be assigned to the existing or new image. - In the case of cloning, by default O(new_name) will take the name of the origin image with the prefix 'Copy of'. type: str persistent: description: - Whether the image should be persistent or non-persistent. type: bool version_added: 9.5.0 create: description: - Whether the image should be created if not present. - This is ignored if O(state=absent). type: bool version_added: 10.0.0 template: description: - Use with O(create=true) to specify image template. type: str version_added: 10.0.0 datastore_id: description: - Use with O(create=true) to specify datastore for image. type: int version_added: 10.0.0 wait_timeout: description: - Seconds to wait until image is ready, deleted or cloned. type: int default: 60 version_added: 10.0.0 author: - "Milan Ilic (@ilicmilan)" """ EXAMPLES = r""" - name: Fetch the IMAGE by id community.general.one_image: id: 45 register: result - name: Print the IMAGE properties ansible.builtin.debug: var: result - name: Rename existing IMAGE community.general.one_image: id: 34 state: renamed new_name: bar-image - name: Disable the IMAGE by id community.general.one_image: id: 37 enabled: false - name: Make the IMAGE persistent community.general.one_image: id: 37 persistent: true - name: Enable the IMAGE by name community.general.one_image: name: bar-image enabled: true - name: Clone the IMAGE by name community.general.one_image: name: bar-image state: cloned new_name: bar-image-clone register: result - name: Delete the IMAGE by id community.general.one_image: id: '{{ result.id }}' state: absent - name: Make sure IMAGE is present community.general.one_image: name: myyy-image state: present create: true datastore_id: 100 template: | PATH = "/var/tmp/image" TYPE = "OS" SIZE = 20512 FORMAT = "qcow2" PERSISTENT = "Yes" DEV_PREFIX = "vd" - name: Make sure IMAGE is present with a longer timeout community.general.one_image: name: big-image state: present create: true datastore_id: 100 wait_timeout: 900 template: |- PATH = "https://192.0.2.200/repo/tipa_image.raw" TYPE = "OS" SIZE = 82048 FORMAT = "raw" PERSISTENT = "Yes" DEV_PREFIX = "vd" """ RETURN = r""" id: description: Image id. type: int returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: 153 name: description: Image name. type: str returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: app1 group_id: description: Image's group id. type: int returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: 1 group_name: description: Image's group name. type: str returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: one-users owner_id: description: Image's owner id. type: int returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: 143 owner_name: description: Image's owner name. type: str returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: ansible-test state: description: State of image instance. type: str returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: READY used: description: Is image in use. type: bool returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: true running_vms: description: Count of running vms that use this image. type: int returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: 7 permissions: description: The image's permissions. type: dict returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 contains: owner_u: description: The image's owner USAGE permissions. type: str sample: 1 owner_m: description: The image's owner MANAGE permissions. type: str sample: 0 owner_a: description: The image's owner ADMIN permissions. type: str sample: 0 group_u: description: The image's group USAGE permissions. type: str sample: 0 group_m: description: The image's group MANAGE permissions. type: str sample: 0 group_a: description: The image's group ADMIN permissions. type: str sample: 0 other_u: description: The image's other users USAGE permissions. type: str sample: 0 other_m: description: The image's other users MANAGE permissions. type: str sample: 0 other_a: description: The image's other users ADMIN permissions. type: str sample: 0 sample: owner_u: 1 owner_m: 0 owner_a: 0 group_u: 0 group_m: 0 group_a: 0 other_u: 0 other_m: 0 other_a: 0 type: description: The image's type. type: str sample: 0 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 disk_type: description: The image's format type. type: str sample: 0 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 persistent: description: The image's persistence status (1 means true, 0 means false). type: int sample: 1 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 source: description: The image's source. type: str sample: /var/lib/one//datastores/100/somerandomstringxd returned: when O(state=present), O(state=cloned), or O(state=renamed) path: description: The image's filesystem path. type: str sample: /var/tmp/hello.qcow2 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 fstype: description: The image's filesystem type. type: str sample: ext4 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 size: description: The image's size in MegaBytes. type: int sample: 10000 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 cloning_ops: description: The image's cloning operations per second. type: int sample: 0 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 cloning_id: description: The image's cloning ID. type: int sample: -1 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 target_snapshot: description: The image's target snapshot. type: int sample: 1 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 datastore_id: description: The image's datastore ID. type: int sample: 100 returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 datastore: description: The image's datastore name. type: int sample: image_datastore returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 vms: description: The image's list of vm ID's. type: list elements: int returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: - 1 - 2 - 3 version_added: 9.5.0 clones: description: The image's list of clones ID's. type: list elements: int returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: - 1 - 2 - 3 version_added: 9.5.0 app_clones: description: The image's list of app_clones ID's. type: list elements: int returned: when O(state=present), O(state=cloned), or O(state=renamed) sample: - 1 - 2 - 3 version_added: 9.5.0 snapshots: description: The image's list of snapshots. type: list returned: when O(state=present), O(state=cloned), or O(state=renamed) version_added: 9.5.0 sample: - date: 123123 parent: 1 size: 10228 allow_orphans: 1 children: 0 active: 1 name: SampleName """ from ansible_collections.community.general.plugins.module_utils.opennebula import OpenNebulaModule IMAGE_STATES = ['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS', 'LOCKED_USED', 'LOCKED_USED_PERS'] class ImageModule(OpenNebulaModule): def __init__(self): argument_spec = dict( id=dict(type='int'), name=dict(type='str'), state=dict(type='str', choices=['present', 'absent', 'cloned', 'renamed'], default='present'), enabled=dict(type='bool'), new_name=dict(type='str'), persistent=dict(type='bool'), create=dict(type='bool'), template=dict(type='str'), datastore_id=dict(type='int'), wait_timeout=dict(type='int', default=60), ) required_if = [ ['state', 'renamed', ['id']], ['create', True, ['template', 'datastore_id', 'name']], ] mutually_exclusive = [ ['id', 'name'], ] OpenNebulaModule.__init__(self, argument_spec, supports_check_mode=True, mutually_exclusive=mutually_exclusive, required_if=required_if) def run(self, one, module, result): params = module.params id = params.get('id') name = params.get('name') desired_state = params.get('state') enabled = params.get('enabled') new_name = params.get('new_name') persistent = params.get('persistent') create = params.get('create') template = params.get('template') datastore_id = params.get('datastore_id') wait_timeout = params.get('wait_timeout') self.result = {} image = self.get_image_instance(id, name) if not image and desired_state != 'absent': if create: self.result = self.create_image(name, template, datastore_id, wait_timeout) # Using 'if id:' doesn't work properly when id=0 elif id is not None: module.fail_json(msg="There is no image with id=" + str(id)) elif name is not None: module.fail_json(msg="There is no image with name=" + name) if desired_state == 'absent': self.result = self.delete_image(image, wait_timeout) else: if persistent is not None: self.result = self.change_persistence(image, persistent) if enabled is not None: self.result = self.enable_image(image, enabled) if desired_state == "cloned": self.result = self.clone_image(image, new_name, wait_timeout) elif desired_state == "renamed": self.result = self.rename_image(image, new_name) self.exit() def get_image(self, predicate): # Filter -2 means fetch all images user can Use pool = self.one.imagepool.info(-2, -1, -1, -1) for image in pool.IMAGE: if predicate(image): return image return None def get_image_by_name(self, image_name): return self.get_image(lambda image: (image.NAME == image_name)) def get_image_by_id(self, image_id): return self.get_image(lambda image: (image.ID == image_id)) def get_image_instance(self, requested_id, requested_name): # Using 'if requested_id:' doesn't work properly when requested_id=0 if requested_id is not None: return self.get_image_by_id(requested_id) else: return self.get_image_by_name(requested_name) def create_image(self, image_name, template, datastore_id, wait_timeout): if not self.module.check_mode: image_id = self.one.image.allocate("NAME = \"" + image_name + "\"\n" + template, datastore_id) self.wait_for_ready(image_id, wait_timeout) image = self.get_image_by_id(image_id) result = self.get_image_info(image) result['changed'] = True return result def wait_for_ready(self, image_id, wait_timeout=60): import time start_time = time.time() while (time.time() - start_time) < wait_timeout: image = self.one.image.info(image_id) state = image.STATE if state in [IMAGE_STATES.index('ERROR')]: self.module.fail_json(msg="Got an ERROR state: " + image.TEMPLATE['ERROR']) if state in [IMAGE_STATES.index('READY')]: return True time.sleep(1) self.module.fail_json(msg="Wait timeout has expired!") def wait_for_delete(self, image_id, wait_timeout=60): import time start_time = time.time() while (time.time() - start_time) < wait_timeout: # It might be already deleted by the time this function is called try: image = self.one.image.info(image_id) except Exception: check_image = self.get_image_instance(image_id) if not check_image: return True state = image.STATE if state in [IMAGE_STATES.index('DELETE')]: return True time.sleep(1) self.module.fail_json(msg="Wait timeout has expired!") def enable_image(self, image, enable): image = self.one.image.info(image.ID) changed = False state = image.STATE if state not in [IMAGE_STATES.index('READY'), IMAGE_STATES.index('DISABLED'), IMAGE_STATES.index('ERROR')]: if enable: self.module.fail_json(msg="Cannot enable " + IMAGE_STATES[state] + " image!") else: self.module.fail_json(msg="Cannot disable " + IMAGE_STATES[state] + " image!") if ((enable and state != IMAGE_STATES.index('READY')) or (not enable and state != IMAGE_STATES.index('DISABLED'))): changed = True if changed and not self.module.check_mode: self.one.image.enable(image.ID, enable) result = self.get_image_info(image) result['changed'] = changed return result def change_persistence(self, image, enable): image = self.one.image.info(image.ID) changed = False state = image.STATE if state not in [IMAGE_STATES.index('READY'), IMAGE_STATES.index('DISABLED'), IMAGE_STATES.index('ERROR')]: if enable: self.module.fail_json(msg="Cannot enable persistence for " + IMAGE_STATES[state] + " image!") else: self.module.fail_json(msg="Cannot disable persistence for " + IMAGE_STATES[state] + " image!") if ((enable and state != IMAGE_STATES.index('READY')) or (not enable and state != IMAGE_STATES.index('DISABLED'))): changed = True if changed and not self.module.check_mode: self.one.image.persistent(image.ID, enable) result = self.get_image_info(image) result['changed'] = changed return result def clone_image(self, image, new_name, wait_timeout): if new_name is None: new_name = "Copy of " + image.NAME tmp_image = self.get_image_by_name(new_name) if tmp_image: result = self.get_image_info(image) result['changed'] = False return result if image.STATE == IMAGE_STATES.index('DISABLED'): self.module.fail_json(msg="Cannot clone DISABLED image") if not self.module.check_mode: new_id = self.one.image.clone(image.ID, new_name) self.wait_for_ready(new_id, wait_timeout) image = self.one.image.info(new_id) result = self.get_image_info(image) result['changed'] = True return result def rename_image(self, image, new_name): if new_name is None: self.module.fail_json(msg="'new_name' option has to be specified when the state is 'renamed'") if new_name == image.NAME: result = self.get_image_info(image) result['changed'] = False return result tmp_image = self.get_image_by_name(new_name) if tmp_image: self.module.fail_json(msg="Name '" + new_name + "' is already taken by IMAGE with id=" + str(tmp_image.ID)) if not self.module.check_mode: self.one.image.rename(image.ID, new_name) result = self.get_image_info(image) result['changed'] = True return result def delete_image(self, image, wait_timeout): if not image: return {'changed': False} if image.RUNNING_VMS > 0: self.module.fail_json(msg="Cannot delete image. There are " + str(image.RUNNING_VMS) + " VMs using it.") if not self.module.check_mode: self.one.image.delete(image.ID) self.wait_for_delete(image.ID, wait_timeout) return {'changed': True} def main(): ImageModule().run_module() if __name__ == '__main__': main()