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
This commit is contained in:
alexander 2024-10-05 16:02:01 +03:00 committed by GitHub
parent e7ccbc2f18
commit fea0ffa5aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1130 additions and 383 deletions

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__':