mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-25 05:23:58 -07:00 
			
		
		
		
	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:
		
					parent
					
						
							
								e7ccbc2f18
							
						
					
				
			
			
				commit
				
					
						fea0ffa5aa
					
				
			
		
					 8 changed files with 1130 additions and 383 deletions
				
			
		
							
								
								
									
										6
									
								
								changelogs/fragments/8889-refactor-one-image-modules.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								changelogs/fragments/8889-refactor-one-image-modules.yml
									
										
									
									
									
										Normal 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). | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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__': | ||||
|  |  | |||
|  | @ -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__': | ||||
|  |  | |||
							
								
								
									
										7
									
								
								tests/integration/targets/one_image/aliases
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/integration/targets/one_image/aliases
									
										
									
									
									
										Normal 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! | ||||
							
								
								
									
										210
									
								
								tests/integration/targets/one_image/tasks/main.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								tests/integration/targets/one_image/tasks/main.yml
									
										
									
									
									
										Normal 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 | ||||
							
								
								
									
										7
									
								
								tests/integration/targets/one_image_info/aliases
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/integration/targets/one_image_info/aliases
									
										
									
									
									
										Normal 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! | ||||
							
								
								
									
										192
									
								
								tests/integration/targets/one_image_info/tasks/main.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								tests/integration/targets/one_image_info/tasks/main.yml
									
										
									
									
									
										Normal 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 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue