[PR #8889/fea0ffa5 backport][stable-9] one_image/one_image_info: refactor (#8984)

one_image/one_image_info: refactor (#8889)

* Refactor one_image

* Refactor one_image_info

* Add examples one_image

* Add CHANGELOG fragment

* Add integration tests for one_image

* Add integration tests for one_image_info

* Update one_image DOC

* Update one_image_info DOC

* Update one_image DOC

* Update one_image_info DOC

* Fix f-strings for one_image

* Update CHANGELOG fragment

* PR fixes

* PR fixes

(cherry picked from commit fea0ffa5aa)

Co-authored-by: alexander <79072457+abakanovskii@users.noreply.github.com>
pull/8993/head
patchback[bot] 2024-10-05 15:17:27 +02:00 committed by GitHub
parent d4c29e19c0
commit 02f123877a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1130 additions and 383 deletions

View File

@ -0,0 +1,6 @@
minor_changes:
- one_image - add option ``persistent`` to manage image persistence (https://github.com/ansible-collections/community.general/issues/3578, https://github.com/ansible-collections/community.general/pull/8889).
- one_image - refactor code to make it more similar to ``one_template`` and ``one_vnet`` (https://github.com/ansible-collections/community.general/pull/8889).
- one_image_info - refactor code to make it more similar to ``one_template`` and ``one_vnet`` (https://github.com/ansible-collections/community.general/pull/8889).
- one_image - extend xsd scheme to make it return a lot more info about image (https://github.com/ansible-collections/community.general/pull/8889).
- one_image_info - extend xsd scheme to make it return a lot more info about image (https://github.com/ansible-collections/community.general/pull/8889).

View File

@ -16,6 +16,7 @@ from ansible.module_utils.six import string_types
from ansible.module_utils.basic import AnsibleModule
IMAGE_STATES = ['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS', 'LOCKED_USED', 'LOCKED_USED_PERS']
HAS_PYONE = True
try:
@ -347,3 +348,90 @@ class OpenNebulaModule:
result: the Ansible result
"""
raise NotImplementedError("Method requires implementation")
def get_image_list_id(self, image, element):
"""
This is a helper function for get_image_info to iterate over a simple list of objects
"""
list_of_id = []
if element == 'VMS':
image_list = image.VMS
if element == 'CLONES':
image_list = image.CLONES
if element == 'APP_CLONES':
image_list = image.APP_CLONES
for iter in image_list.ID:
list_of_id.append(
# These are optional so firstly check for presence
getattr(iter, 'ID', 'Null'),
)
return list_of_id
def get_image_snapshots_list(self, image):
"""
This is a helper function for get_image_info to iterate over a dictionary
"""
list_of_snapshots = []
for iter in image.SNAPSHOTS.SNAPSHOT:
list_of_snapshots.append({
'date': iter['DATE'],
'parent': iter['PARENT'],
'size': iter['SIZE'],
# These are optional so firstly check for presence
'allow_orhans': getattr(image.SNAPSHOTS, 'ALLOW_ORPHANS', 'Null'),
'children': getattr(iter, 'CHILDREN', 'Null'),
'active': getattr(iter, 'ACTIVE', 'Null'),
'name': getattr(iter, 'NAME', 'Null'),
})
return list_of_snapshots
def get_image_info(self, image):
"""
This method is used by one_image and one_image_info modules to retrieve
information from XSD scheme of an image
Returns: a copy of the parameters that includes the resolved parameters.
"""
info = {
'id': image.ID,
'name': image.NAME,
'state': IMAGE_STATES[image.STATE],
'running_vms': image.RUNNING_VMS,
'used': bool(image.RUNNING_VMS),
'user_name': image.UNAME,
'user_id': image.UID,
'group_name': image.GNAME,
'group_id': image.GID,
'permissions': {
'owner_u': image.PERMISSIONS.OWNER_U,
'owner_m': image.PERMISSIONS.OWNER_M,
'owner_a': image.PERMISSIONS.OWNER_A,
'group_u': image.PERMISSIONS.GROUP_U,
'group_m': image.PERMISSIONS.GROUP_M,
'group_a': image.PERMISSIONS.GROUP_A,
'other_u': image.PERMISSIONS.OTHER_U,
'other_m': image.PERMISSIONS.OTHER_M,
'other_a': image.PERMISSIONS.OTHER_A
},
'type': image.TYPE,
'disk_type': image.DISK_TYPE,
'persistent': image.PERSISTENT,
'regtime': image.REGTIME,
'source': image.SOURCE,
'path': image.PATH,
'fstype': getattr(image, 'FSTYPE', 'Null'),
'size': image.SIZE,
'cloning_ops': image.CLONING_OPS,
'cloning_id': image.CLONING_ID,
'target_snapshot': image.TARGET_SNAPSHOT,
'datastore_id': image.DATASTORE_ID,
'datastore': image.DATASTORE,
'vms': self.get_image_list_id(image, 'VMS'),
'clones': self.get_image_list_id(image, 'CLONES'),
'app_clones': self.get_image_list_id(image, 'APP_CLONES'),
'snapshots': self.get_image_snapshots_list(image),
'template': image.TEMPLATE,
}
return info

View File

@ -17,6 +17,7 @@ description:
requirements:
- pyone
extends_documentation_fragment:
- community.general.opennebula
- community.general.attributes
attributes:
check_mode:
@ -24,23 +25,6 @@ attributes:
diff_mode:
support: none
options:
api_url:
description:
- URL of the OpenNebula RPC server.
- It is recommended to use HTTPS so that the username/password are not
- transferred over the network unencrypted.
- If not set then the value of the E(ONE_URL) environment variable is used.
type: str
api_username:
description:
- Name of the user to login into the OpenNebula RPC server. If not set
- then the value of the E(ONE_USERNAME) environment variable is used.
type: str
api_password:
description:
- Password of the user to login into OpenNebula RPC server. If not set
- then the value of the E(ONE_PASSWORD) environment variable is used.
type: str
id:
description:
- A O(id) of the image you would like to manage.
@ -67,6 +51,11 @@ options:
- 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
author:
- "Milan Ilic (@ilicmilan)"
'''
@ -92,6 +81,11 @@ EXAMPLES = '''
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
@ -114,300 +108,448 @@ RETURN = '''
id:
description: image id
type: int
returned: success
returned: when O(state=present), O(state=cloned), or O(state=renamed)
sample: 153
name:
description: image name
type: str
returned: success
returned: when O(state=present), O(state=cloned), or O(state=renamed)
sample: app1
group_id:
description: image's group id
type: int
returned: success
returned: when O(state=present), O(state=cloned), or O(state=renamed)
sample: 1
group_name:
description: image's group name
type: str
returned: success
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: success
returned: when O(state=present), O(state=cloned), or O(state=renamed)
sample: 143
owner_name:
description: image's owner name
type: str
returned: success
returned: when O(state=present), O(state=cloned), or O(state=renamed)
sample: ansible-test
state:
description: state of image instance
type: str
returned: success
returned: when O(state=present), O(state=cloned), or O(state=renamed)
sample: READY
used:
description: is image in use
type: bool
returned: success
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: success
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
'''
try:
import pyone
HAS_PYONE = True
except ImportError:
HAS_PYONE = False
from ansible.module_utils.basic import AnsibleModule
import os
def get_image(module, client, predicate):
# Filter -2 means fetch all images user can Use
pool = client.imagepool.info(-2, -1, -1, -1)
for image in pool.IMAGE:
if predicate(image):
return image
return None
def get_image_by_name(module, client, image_name):
return get_image(module, client, lambda image: (image.NAME == image_name))
def get_image_by_id(module, client, image_id):
return get_image(module, client, lambda image: (image.ID == image_id))
def get_image_instance(module, client, requested_id, requested_name):
if requested_id:
return get_image_by_id(module, client, requested_id)
else:
return get_image_by_name(module, client, requested_name)
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']
def get_image_info(image):
info = {
'id': image.ID,
'name': image.NAME,
'state': IMAGE_STATES[image.STATE],
'running_vms': image.RUNNING_VMS,
'used': bool(image.RUNNING_VMS),
'user_name': image.UNAME,
'user_id': image.UID,
'group_name': image.GNAME,
'group_id': image.GID,
}
class ImageModule(OpenNebulaModule):
def __init__(self):
argument_spec = dict(
id=dict(type='int', required=False),
name=dict(type='str', required=False),
state=dict(type='str', choices=['present', 'absent', 'cloned', 'renamed'], default='present'),
enabled=dict(type='bool', required=False),
new_name=dict(type='str', required=False),
persistent=dict(type='bool', required=False),
)
required_if = [
['state', 'renamed', ['id']]
]
mutually_exclusive = [
['id', 'name'],
]
return info
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')
def wait_for_state(module, client, image_id, wait_timeout, state_predicate):
import time
start_time = time.time()
self.result = {}
image = self.get_image_instance(id, name)
if not image and desired_state != 'absent':
# Using 'if id:' doesn't work properly when id=0
if 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)
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)
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 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
while (time.time() - start_time) < wait_timeout:
image = client.image.info(image_id)
state = image.STATE
if state_predicate(state):
return image
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!")
time.sleep(1)
if ((enable and state != IMAGE_STATES.index('READY')) or
(not enable and state != IMAGE_STATES.index('DISABLED'))):
changed = True
module.fail_json(msg="Wait timeout has expired!")
if changed and not self.module.check_mode:
self.one.image.enable(image.ID, enable)
result = OpenNebulaModule.get_image_info(image)
result['changed'] = changed
def wait_for_ready(module, client, image_id, wait_timeout=60):
return wait_for_state(module, client, image_id, wait_timeout, lambda state: (state in [IMAGE_STATES.index('READY')]))
def wait_for_delete(module, client, image_id, wait_timeout=60):
return wait_for_state(module, client, image_id, wait_timeout, lambda state: (state in [IMAGE_STATES.index('DELETE')]))
def enable_image(module, client, image, enable):
image = client.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:
module.fail_json(msg="Cannot enable " + IMAGE_STATES[state] + " image!")
else:
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 module.check_mode:
client.image.enable(image.ID, enable)
result = get_image_info(image)
result['changed'] = changed
return result
def clone_image(module, client, image, new_name):
if new_name is None:
new_name = "Copy of " + image.NAME
tmp_image = get_image_by_name(module, client, new_name)
if tmp_image:
result = get_image_info(tmp_image)
result['changed'] = False
return result
if image.STATE == IMAGE_STATES.index('DISABLED'):
module.fail_json(msg="Cannot clone DISABLED image")
def change_persistence(self, image, enable):
image = self.one.image.info(image.ID)
changed = False
if not module.check_mode:
new_id = client.image.clone(image.ID, new_name)
wait_for_ready(module, client, new_id)
image = client.image.info(new_id)
state = image.STATE
result = get_image_info(image)
result['changed'] = True
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!")
return result
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)
def rename_image(module, client, image, new_name):
if new_name is None:
module.fail_json(msg="'new_name' option has to be specified when the state is 'renamed'")
result = OpenNebulaModule.get_image_info(image)
result['changed'] = changed
if new_name == image.NAME:
result = get_image_info(image)
result['changed'] = False
return result
tmp_image = get_image_by_name(module, client, new_name)
if tmp_image:
module.fail_json(msg="Name '" + new_name + "' is already taken by IMAGE with id=" + str(tmp_image.ID))
def clone_image(self, image, new_name):
if new_name is None:
new_name = "Copy of " + image.NAME
if not module.check_mode:
client.image.rename(image.ID, new_name)
tmp_image = self.get_image_by_name(new_name)
if tmp_image:
result = OpenNebulaModule.get_image_info(tmp_image)
result['changed'] = False
return result
result = get_image_info(image)
result['changed'] = True
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)
image = self.one.image.info(new_id)
def delete_image(module, client, image):
result = OpenNebulaModule.get_image_info(image)
result['changed'] = True
if not image:
return {'changed': False}
return result
if image.RUNNING_VMS > 0:
module.fail_json(msg="Cannot delete image. There are " + str(image.RUNNING_VMS) + " VMs using it.")
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 not module.check_mode:
client.image.delete(image.ID)
wait_for_delete(module, client, image.ID)
if new_name == image.NAME:
result = OpenNebulaModule.get_image_info(image)
result['changed'] = False
return result
return {'changed': True}
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)
def get_connection_info(module):
result = OpenNebulaModule.get_image_info(image)
result['changed'] = True
return result
url = module.params.get('api_url')
username = module.params.get('api_username')
password = module.params.get('api_password')
def delete_image(self, image):
if not image:
return {'changed': False}
if not url:
url = os.environ.get('ONE_URL')
if image.RUNNING_VMS > 0:
self.module.fail_json(msg="Cannot delete image. There are " + str(image.RUNNING_VMS) + " VMs using it.")
if not username:
username = os.environ.get('ONE_USERNAME')
if not self.module.check_mode:
self.one.image.delete(image.ID)
self.wait_for_delete(image.ID)
if not password:
password = os.environ.get('ONE_PASSWORD')
if not (url and username and password):
module.fail_json(msg="One or more connection parameters (api_url, api_username, api_password) were not specified")
from collections import namedtuple
auth_params = namedtuple('auth', ('url', 'username', 'password'))
return auth_params(url=url, username=username, password=password)
return {'changed': True}
def main():
fields = {
"api_url": {"required": False, "type": "str"},
"api_username": {"required": False, "type": "str"},
"api_password": {"required": False, "type": "str", "no_log": True},
"id": {"required": False, "type": "int"},
"name": {"required": False, "type": "str"},
"state": {
"default": "present",
"choices": ['present', 'absent', 'cloned', 'renamed'],
"type": "str"
},
"enabled": {"required": False, "type": "bool"},
"new_name": {"required": False, "type": "str"},
}
module = AnsibleModule(argument_spec=fields,
mutually_exclusive=[['id', 'name']],
supports_check_mode=True)
if not HAS_PYONE:
module.fail_json(msg='This module requires pyone to work!')
auth = get_connection_info(module)
params = module.params
id = params.get('id')
name = params.get('name')
state = params.get('state')
enabled = params.get('enabled')
new_name = params.get('new_name')
client = pyone.OneServer(auth.url, session=auth.username + ':' + auth.password)
result = {}
if not id and state == 'renamed':
module.fail_json(msg="Option 'id' is required when the state is 'renamed'")
image = get_image_instance(module, client, id, name)
if not image and state != 'absent':
if id:
module.fail_json(msg="There is no image with id=" + str(id))
else:
module.fail_json(msg="There is no image with name=" + name)
if state == 'absent':
result = delete_image(module, client, image)
else:
result = get_image_info(image)
changed = False
result['changed'] = False
if enabled is not None:
result = enable_image(module, client, image, enabled)
if state == "cloned":
result = clone_image(module, client, image, new_name)
elif state == "renamed":
result = rename_image(module, client, image, new_name)
changed = changed or result['changed']
result['changed'] = changed
module.exit_json(**result)
ImageModule().run_module()
if __name__ == '__main__':

View File

@ -17,29 +17,14 @@ description:
requirements:
- pyone
extends_documentation_fragment:
- community.general.opennebula
- community.general.attributes
- community.general.attributes.info_module
options:
api_url:
description:
- URL of the OpenNebula RPC server.
- It is recommended to use HTTPS so that the username/password are not
- transferred over the network unencrypted.
- If not set then the value of the E(ONE_URL) environment variable is used.
type: str
api_username:
description:
- Name of the user to login into the OpenNebula RPC server. If not set
- then the value of the E(ONE_USERNAME) environment variable is used.
type: str
api_password:
description:
- Password of the user to login into OpenNebula RPC server. If not set
- then the value of the E(ONE_PASSWORD) environment variable is used.
type: str
ids:
description:
- A list of images ids whose facts you want to gather.
- Module can use integers too.
aliases: ['id']
type: list
elements: str
@ -66,9 +51,16 @@ EXAMPLES = '''
msg: result
- name: Gather facts about an image using ID
community.general.one_image_info:
ids: 123
- name: Gather facts about an image using list of ID
community.general.one_image_info:
ids:
- 123
- 456
- 789
- 0
- name: Gather facts about an image using the name
community.general.one_image_info:
@ -93,182 +85,285 @@ images:
returned: success
contains:
id:
description: image id
description: The image's id.
type: int
sample: 153
name:
description: image name
description: The image's name.
type: str
sample: app1
group_id:
description: image's group id
description: The image's group id
type: int
sample: 1
group_name:
description: image's group name
description: The image's group name.
type: str
sample: one-users
owner_id:
description: image's owner id
description: The image's owner id.
type: int
sample: 143
owner_name:
description: image's owner name
description: The image's owner name.
type: str
sample: ansible-test
state:
description: state of image instance
description: The image's state.
type: str
sample: READY
used:
description: is image in use
description: The image's usage status.
type: bool
sample: true
running_vms:
description: count of running vms that use this image
description: The image's count of running vms that use this image.
type: int
sample: 7
permissions:
description: The image's permissions.
type: dict
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: int
sample: 0
version_added: 9.5.0
disk_type:
description: The image's format type.
type: int
sample: 0
version_added: 9.5.0
persistent:
description: The image's persistence status (1 means true, 0 means false).
type: int
sample: 1
version_added: 9.5.0
source:
description: The image's source.
type: str
sample: /var/lib/one//datastores/100/somerandomstringxd
version_added: 9.5.0
path:
description: The image's filesystem path.
type: str
sample: /var/tmp/hello.qcow2
version_added: 9.5.0
fstype:
description: The image's filesystem type.
type: str
sample: ext4
version_added: 9.5.0
size:
description: The image's size in MegaBytes.
type: int
sample: 10000
version_added: 9.5.0
cloning_ops:
description: The image's cloning operations per second.
type: int
sample: 0
version_added: 9.5.0
cloning_id:
description: The image's cloning ID.
type: int
sample: -1
version_added: 9.5.0
target_snapshot:
description: The image's target snapshot.
type: int
sample: 1
version_added: 9.5.0
datastore_id:
description: The image's datastore ID.
type: int
sample: 100
version_added: 9.5.0
datastore:
description: The image's datastore name.
type: int
sample: image_datastore
version_added: 9.5.0
vms:
description: The image's list of vm ID's.
type: list
elements: int
version_added: 9.5.0
sample:
- 1
- 2
- 3
clones:
description: The image's list of clones ID's.
type: list
elements: int
version_added: 9.5.0
sample:
- 1
- 2
- 3
app_clones:
description: The image's list of app_clones ID's.
type: list
elements: int
version_added: 9.5.0
sample:
- 1
- 2
- 3
snapshots:
description: The image's list of snapshots.
type: list
version_added: 9.5.0
sample:
- date: 123123
parent: 1
size: 10228
allow_orphans: 1
children: 0
active: 1
name: SampleName
'''
try:
import pyone
HAS_PYONE = True
except ImportError:
HAS_PYONE = False
from ansible.module_utils.basic import AnsibleModule
import os
def get_all_images(client):
pool = client.imagepool.info(-2, -1, -1, -1)
# Filter -2 means fetch all images user can Use
return pool
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']
def get_image_info(image):
info = {
'id': image.ID,
'name': image.NAME,
'state': IMAGE_STATES[image.STATE],
'running_vms': image.RUNNING_VMS,
'used': bool(image.RUNNING_VMS),
'user_name': image.UNAME,
'user_id': image.UID,
'group_name': image.GNAME,
'group_id': image.GID,
}
return info
class ImageInfoModule(OpenNebulaModule):
def __init__(self):
argument_spec = dict(
ids=dict(type='list', aliases=['id'], elements='str', required=False),
name=dict(type='str', required=False),
)
mutually_exclusive = [
['ids', 'name'],
]
OpenNebulaModule.__init__(self,
argument_spec,
supports_check_mode=True,
mutually_exclusive=mutually_exclusive)
def get_images_by_ids(module, client, ids):
images = []
pool = get_all_images(client)
def run(self, one, module, result):
params = module.params
ids = params.get('ids')
name = params.get('name')
for image in pool.IMAGE:
if str(image.ID) in ids:
images.append(image)
ids.remove(str(image.ID))
if len(ids) == 0:
if ids:
images = self.get_images_by_ids(ids)
elif name:
images = self.get_images_by_name(name)
else:
images = self.get_all_images().IMAGE
self.result = {
'images': [OpenNebulaModule.get_image_info(image) for image in images]
}
self.exit()
def get_all_images(self):
pool = self.one.imagepool.info(-2, -1, -1, -1)
# Filter -2 means fetch all images user can Use
return pool
def get_images_by_ids(self, ids):
images = []
pool = self.get_all_images()
for image in pool.IMAGE:
if str(image.ID) in ids:
images.append(image)
ids.remove(str(image.ID))
if len(ids) == 0:
break
if len(ids) > 0:
self.module.fail_json(msg='There is no IMAGE(s) with id(s)=' + ', '.join('{id}'.format(id=str(image_id)) for image_id in ids))
return images
def get_images_by_name(self, name_pattern):
images = []
pattern = None
pool = self.get_all_images()
if name_pattern.startswith('~'):
import re
if name_pattern[1] == '*':
pattern = re.compile(name_pattern[2:], re.IGNORECASE)
else:
pattern = re.compile(name_pattern[1:])
for image in pool.IMAGE:
if pattern is not None:
if pattern.match(image.NAME):
images.append(image)
elif name_pattern == image.NAME:
images.append(image)
break
if len(ids) > 0:
module.fail_json(msg='There is no IMAGE(s) with id(s)=' + ', '.join('{id}'.format(id=str(image_id)) for image_id in ids))
# if the specific name is indicated
if pattern is None and len(images) == 0:
self.module.fail_json(msg="There is no IMAGE with name=" + name_pattern)
return images
def get_images_by_name(module, client, name_pattern):
images = []
pattern = None
pool = get_all_images(client)
if name_pattern.startswith('~'):
import re
if name_pattern[1] == '*':
pattern = re.compile(name_pattern[2:], re.IGNORECASE)
else:
pattern = re.compile(name_pattern[1:])
for image in pool.IMAGE:
if pattern is not None:
if pattern.match(image.NAME):
images.append(image)
elif name_pattern == image.NAME:
images.append(image)
break
# if the specific name is indicated
if pattern is None and len(images) == 0:
module.fail_json(msg="There is no IMAGE with name=" + name_pattern)
return images
def get_connection_info(module):
url = module.params.get('api_url')
username = module.params.get('api_username')
password = module.params.get('api_password')
if not url:
url = os.environ.get('ONE_URL')
if not username:
username = os.environ.get('ONE_USERNAME')
if not password:
password = os.environ.get('ONE_PASSWORD')
if not (url and username and password):
module.fail_json(msg="One or more connection parameters (api_url, api_username, api_password) were not specified")
from collections import namedtuple
auth_params = namedtuple('auth', ('url', 'username', 'password'))
return auth_params(url=url, username=username, password=password)
return images
def main():
fields = {
"api_url": {"required": False, "type": "str"},
"api_username": {"required": False, "type": "str"},
"api_password": {"required": False, "type": "str", "no_log": True},
"ids": {"required": False, "aliases": ['id'], "type": "list", "elements": "str"},
"name": {"required": False, "type": "str"},
}
module = AnsibleModule(argument_spec=fields,
mutually_exclusive=[['ids', 'name']],
supports_check_mode=True)
if not HAS_PYONE:
module.fail_json(msg='This module requires pyone to work!')
auth = get_connection_info(module)
params = module.params
ids = params.get('ids')
name = params.get('name')
client = pyone.OneServer(auth.url, session=auth.username + ':' + auth.password)
if ids:
images = get_images_by_ids(module, client, ids)
elif name:
images = get_images_by_name(module, client, name)
else:
images = get_all_images(client).IMAGE
result = {
'images': [get_image_info(image) for image in images],
}
module.exit_json(**result)
ImageInfoModule().run_module()
if __name__ == '__main__':

View File

@ -0,0 +1,7 @@
# Copyright (c) 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
azp/generic/1
cloud/opennebula
disabled # FIXME - when this is fixed, also re-enable the generic tests in CI!

View File

@ -0,0 +1,210 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
# Copyright (c) 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
# Checks for existence
- name: Make sure image is present by ID
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
state: present
register: result
- name: Assert that image is present
assert:
that:
- result is not changed
- name: Make sure image is present by ID
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
name: my_image
state: present
register: result
- name: Assert that image is present
assert:
that:
- result is not changed
# Updating an image
- name: Clone image without name
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
state: cloned
register: result
- name: Assert that image is cloned
assert:
that:
- result is changed
- name: Clone image with name
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
state: renamed
new_name: new_image
register: result
- name: Assert that image is cloned
assert:
that:
- result is changed
- name: Disable image
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
enabled: false
register: result
- name: Assert that network is disabled
assert:
that:
- result is changed
- name: Enable image
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
enabled: true
register: result
- name: Assert that network is enabled
assert:
that:
- result is changed
- name: Make image persistent
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
persistent: true
register: result
- name: Assert that network is persistent
assert:
that:
- result is changed
- name: Make image non-persistent
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
persistent: false
register: result
- name: Assert that network is non-persistent
assert:
that:
- result is changed
# Testing idempotence using the same tasks
- name: Make image non-persistent
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
persistent: false
enabled: true
register: result
- name: Assert that network not changed
assert:
that:
- result is not changed
# Delete images
- name: Deleting non-existing image
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 228
state: absent
register: result
- name: Assert that network not changed
assert:
that:
- result is not changed
- name: Delete an existing image
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
state: absent
register: result
- name: Assert that image was deleted
assert:
that:
- result is changed
# Trying to run with wrong arguments
- name: Try to use name and ID at the same time
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
name: name
register: result
ignore_errors: true
- name: Assert that task failed
assert:
that:
- result is failed
- name: Try to rename image without specifying new name
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
state: rename
register: result
ignore_errors: true
- name: Assert that task failed
assert:
that:
- result is failed
- name: Try to rename image without specifying new name
one_image:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
state: rename
register: result
ignore_errors: true

View File

@ -0,0 +1,7 @@
# Copyright (c) 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
azp/generic/1
cloud/opennebula
disabled # FIXME - when this is fixed, also re-enable the generic tests in CI!

View File

@ -0,0 +1,192 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
# Copyright (c) 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
# Checks for existence
- name: Get info by ID
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
register: result
- name: Assert that image is present
assert:
that:
- result is not changed
- name: Get info by list of ID
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
ids:
- 2
- 2
- 8
register: result
- name: Assert that image is present
assert:
that:
- result is not changed
- name: Get info by list of ID
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
name: somename
register: result
- name: Assert that image is present
assert:
that:
- result is not changed
- name: Gather all info
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
register: result
- name: Assert that images are present
assert:
that:
- result is not changed
- name: Gather info by regex
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
name: '~my_image-[0-9].*'
register: result
- name: Assert that images are present
assert:
that:
- result is not changed
- name: Gather info by regex and ignore upper/lower cases
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
name: '~*my_image-[0-9].*'
register: result
- name: Assert that images are present
assert:
that:
- result is not changed
# Updating an image
- name: Clone image without name
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
state: cloned
register: result
- name: Assert that image is cloned
assert:
that:
- result is changed
- name: Clone image with name
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
state: renamed
new_name: new_image
register: result
- name: Assert that image is cloned
assert:
that:
- result is changed
- name: Disable image
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
enabled: false
register: result
- name: Assert that network is disabled
assert:
that:
- result is changed
- name: Enable image
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
enabled: true
register: result
- name: Assert that network is enabled
assert:
that:
- result is changed
- name: Make image persistent
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
persistent: true
register: result
- name: Assert that network is persistent
assert:
that:
- result is changed
- name: Make image non-persistent
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
persistent: false
register: result
- name: Assert that network is non-persistent
assert:
that:
- result is changed
# Testing errors
- name: Try to use name and ID a the same time
one_image_info:
api_url: "{{ opennebula_url }}"
api_username: "{{ opennebula_username }}"
api_password: "{{ opennebula_password }}"
id: 0
name: somename
register: result
ignore_errors: true
- name: Assert that network not changed
assert:
that:
- result is failed