community.general/plugins/modules/one_image.py

629 lines
18 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Milan Ilic <milani@nordeus.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
# 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()