diff --git a/changelogs/fragments/8889-refactor-one-image-modules.yml b/changelogs/fragments/8889-refactor-one-image-modules.yml
new file mode 100644
index 0000000000..de552c17a6
--- /dev/null
+++ b/changelogs/fragments/8889-refactor-one-image-modules.yml
@@ -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).
diff --git a/plugins/module_utils/opennebula.py b/plugins/module_utils/opennebula.py
index 94732e4f7c..24833350c6 100644
--- a/plugins/module_utils/opennebula.py
+++ b/plugins/module_utils/opennebula.py
@@ -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
diff --git a/plugins/modules/one_image.py b/plugins/modules/one_image.py
index a0081a0fe0..86db3b0405 100644
--- a/plugins/modules/one_image.py
+++ b/plugins/modules/one_image.py
@@ -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__':
diff --git a/plugins/modules/one_image_info.py b/plugins/modules/one_image_info.py
index c9d7c4035f..2ad0f3c493 100644
--- a/plugins/modules/one_image_info.py
+++ b/plugins/modules/one_image_info.py
@@ -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__':
diff --git a/tests/integration/targets/one_image/aliases b/tests/integration/targets/one_image/aliases
new file mode 100644
index 0000000000..100ba0f979
--- /dev/null
+++ b/tests/integration/targets/one_image/aliases
@@ -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!
diff --git a/tests/integration/targets/one_image/tasks/main.yml b/tests/integration/targets/one_image/tasks/main.yml
new file mode 100644
index 0000000000..c8736d73d8
--- /dev/null
+++ b/tests/integration/targets/one_image/tasks/main.yml
@@ -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
diff --git a/tests/integration/targets/one_image_info/aliases b/tests/integration/targets/one_image_info/aliases
new file mode 100644
index 0000000000..100ba0f979
--- /dev/null
+++ b/tests/integration/targets/one_image_info/aliases
@@ -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!
diff --git a/tests/integration/targets/one_image_info/tasks/main.yml b/tests/integration/targets/one_image_info/tasks/main.yml
new file mode 100644
index 0000000000..fede116241
--- /dev/null
+++ b/tests/integration/targets/one_image_info/tasks/main.yml
@@ -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