Denys Mishchenko 53da712635 Existing images update name, visibility etc
Dropped default values of min_disk and min_ram parameters because
it interferes with the update mechanism and glance uses those values
anyway [1], [2].

If the image is already present and visibility param is defined we
should check its visibility and correct it if needed. Added tests
to verify that both is_public and visibility can change the image.

If both name and id are specified for the image, we might want to
update the image name. This rely on fact that id pram is checked first.
Added rename tests to verify this.

For some reason if image object is used for the image update, 409
error produced and exception trown, but the change is in place. So
instead of image object, update query rely on the image.id

[1] 75051dd5a2/glance/db/simple/api.py (L226)
[2] 75051dd5a2/glance/domain/__init__.py (L125)

Change-Id: I9ca6b78bec96b69e6946b65796f12314a1ec6ab4
2022-11-02 14:08:17 +01:00

613 lines
19 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
# Copyright (c) 2013, Benno Joy <benno@ansible.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: image
short_description: Add/Delete images from OpenStack Cloud
author: OpenStack Ansible SIG
description:
- Add or Remove images from the OpenStack Image Repository
options:
name:
description:
- The name of the image when uploading - or the name/ID of the image if deleting
- If provided with the id, it can be used to change the name of existing image
required: true
type: str
id:
description:
- The ID of the image when uploading an image
- This image attribute cannot be changed.
type: str
checksum:
description:
- The checksum of the image
type: str
disk_format:
description:
- The format of the disk that is getting uploaded
- This image attribute cannot be changed.
default: qcow2
choices: ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso', 'vhdx', 'ploop']
type: str
container_format:
description:
- The format of the container
- This image attribute cannot be changed.
default: bare
choices: ['ami', 'aki', 'ari', 'bare', 'ovf', 'ova', 'docker']
type: str
owner:
description:
- The name or ID of the project owning the image
type: str
aliases: ['project']
owner_domain:
description:
- The name or id of the domain the project owning the image belongs to
- May be used to identify a unique project when providing a name to the project argument and multiple projects with such name exist
type: str
aliases: ['project_domain']
min_disk:
description:
- The minimum disk space (in GB) required to boot this image
type: int
min_ram:
description:
- The minimum ram (in MB) required to boot this image
type: int
is_public:
description:
- Whether the image can be accessed publicly.
Note that publicizing an image requires admin role by default.
- Use I(visibility) instead of I(is_public),
the latter has been deprecated.
type: bool
default: false
is_protected:
description:
- Prevent image from being deleted
aliases: ['protected']
type: bool
filename:
description:
- The path to the file which has to be uploaded
- This image attribute cannot be changed.
type: str
ramdisk:
description:
- The name of an existing ramdisk image that will be associated with this image
type: str
kernel:
description:
- The name of an existing kernel image that will be associated with this image
type: str
properties:
description:
- Additional properties to be associated with this image
default: {}
type: dict
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
tags:
description:
- List of tags to be applied to the image
default: []
type: list
elements: str
visibility:
description:
- The image visibility
type: str
choices: [public, private, shared, community]
volume:
description:
- ID of a volume to create an image from.
- The volume must be in AVAILABLE state.
- Switch to module M(openstack.cloud.volume) instead of using I(volume),
the latter has been deprecated.
type: str
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Upload an image from a local file named cirros-0.3.0-x86_64-disk.img
- openstack.cloud.image:
auth:
auth_url: https://identity.example.com
username: admin
password: passme
project_name: admin
openstack.cloud.identity_user_domain_name: Default
openstack.cloud.project_domain_name: Default
name: cirros
container_format: bare
disk_format: qcow2
state: present
filename: cirros-0.3.0-x86_64-disk.img
kernel: cirros-vmlinuz
ramdisk: cirros-initrd
tags:
- custom
properties:
cpu_arch: x86_64
distro: ubuntu
'''
RETURN = '''
id:
description: ID of the image.
returned: On success when I(state) is 'present'.
type: str
image:
description: Dictionary describing the image.
type: dict
returned: On success when I(state) is 'present'.
contains:
id:
description: Unique UUID.
returned: success
type: str
name:
description: Name given to the image.
returned: success
type: str
status:
description: Image status.
returned: success
type: str
architecture:
description: |
The CPU architecture that must be supported by the hypervisor.
returned: success
type: str
created_at:
description: Image created at timestamp.
returned: success
type: str
container_format:
description: Container format of the image.
returned: success
type: str
direct_url:
description: URL to access the image file kept in external store.
returned: success
type: str
min_ram:
description: Min amount of RAM required for this image.
returned: success
type: int
disk_format:
description: Disk format of the image.
returned: success
type: str
file:
description: The URL for the virtual machine image file.
returned: success
type: str
has_auto_disk_config:
description: >
If root partition on disk is automatically resized before the instance
boots.
returned: success
type: bool
hash_algo:
description: |
The algorithm used to compute a secure hash of the image data.
returned: success
type: str
hash_value:
description: >
The hexdigest of the secure hash of the image data computed using the
algorithm whose name is the value of the os_hash_algo property.
returned: success
type: str
hw_cpu_cores:
description: >
Used to pin the virtual CPUs (vCPUs) of instances to the host's physical
CPU cores (pCPUs).
returned: success
type: str
hw_cpu_policy:
description: The hexdigest of the secure hash of the image data.
returned: success
type: str
hw_cpu_sockets:
description: Preferred number of sockets to expose to the guest.
returned: success
type: str
hw_cpu_thread_policy:
description: >
Defines how hardware CPU threads in a simultaneous multithreading-based
(SMT) architecture be used.
returned: success
type: str
hw_cpu_threads:
description: |
The preferred number of threads to expose to the guest.
returned: success
type: str
hw_disk_bus:
description: |
Specifies the type of disk controller to attach disk devices to.
returned: success
type: str
hw_machine_type:
description: |
Enables booting an ARM system using the specified machine type.
returned: success
type: str
hw_qemu_guest_agent:
description: >
A string boolean, which if "true", QEMU guest agent will be exposed to
the instance.
returned: success
type: str
hw_rng_model:
description: Adds a random-number generator device to the image's instances.
returned: success
type: str
hw_scsi_model:
description: >
Enables the use of VirtIO SCSI (virtio-scsi) to provide block device
access for compute instances.
returned: success
type: str
hw_video_model:
description: The video image driver used.
returned: success
type: str
hw_video_ram:
description: Maximum RAM for the video image.
returned: success
type: str
hw_vif_model:
description: Specifies the model of virtual network interface device to use.
returned: success
type: str
hw_watchdog_action:
description: >
Enables a virtual hardware watchdog device that carries out the
specified action if the server hangs.
returned: success
type: str
hypervisor_type:
description: The hypervisor type.
returned: success
type: str
instance_type_rxtx_factor:
description: >
Optional property allows created servers to have a different bandwidth
cap than that defined in the network they are attached to.
returned: success
type: str
instance_uuid:
description: >
For snapshot images, this is the UUID of the server used to create this
image.
returned: success
type: str
is_hidden:
description: >-
Controls whether an image is displayed in the default image-list
response
returned: success
type: bool
is_hw_boot_menu_enabled:
description: Enables the BIOS bootmenu.
returned: success
type: bool
is_hw_vif_multiqueue_enabled:
description: |
Enables the virtio-net multiqueue feature.
returned: success
type: bool
kernel_id:
description: >
The ID of an image stored in the Image service that should be used as
the kernel when booting an AMI-style image.
returned: success
type: str
locations:
description: A list of URLs to access the image file in external store.
returned: success
type: str
metadata:
description: The location metadata.
returned: success
type: str
needs_config_drive:
description: Specifies whether the image needs a config drive.
returned: success
type: bool
needs_secure_boot:
description: Whether Secure Boot is needed.
returned: success
type: bool
os_admin_user:
description: The operating system admin username.
returned: success
type: str
os_command_line:
description: The kernel command line to be used by libvirt driver.
returned: success
type: str
os_distro:
description: |
The common name of the operating system distribution in lowercase.
returned: success
type: str
os_require_quiesce:
description: |
If true, require quiesce on snapshot via QEMU guest agent.
returned: success
type: str
os_shutdown_timeout:
description: Time for graceful shutdown.
returned: success
type: str
os_type:
description: The operating system installed on the image.
returned: success
type: str
os_version:
description: |
The operating system version as specified by the distributor.
returned: success
type: str
owner_id:
description: 'The ID of the owner, or project, of the image.'
returned: success
type: str
ramdisk_id:
description: >
The ID of image stored in the Image service that should be used as the
ramdisk when booting an AMI-style image.
returned: success
type: str
schema:
description: URL for the schema describing a virtual machine image.
returned: success
type: str
store:
description: >
Glance will attempt to store the disk image data in the backing store
indicated by the value of the header.
returned: success
type: str
updated_at:
description: Image updated at timestamp.
returned: success
type: str
url:
description: URL to access the image file kept in external store.
returned: success
type: str
virtual_size:
description: The virtual size of the image.
returned: success
type: str
vm_mode:
description: The virtual machine mode.
returned: success
type: str
vmware_adaptertype:
description: |
The virtual SCSI or IDE controller used by the hypervisor.
returned: success
type: str
vmware_ostype:
description: Operating system installed in the image.
returned: success
type: str
filters:
description: Additional properties associated with the image.
returned: success
type: dict
min_disk:
description: Min amount of disk space required for this image.
returned: success
type: int
is_protected:
description: Image protected flag.
returned: success
type: bool
checksum:
description: Checksum for the image.
returned: success
type: str
owner:
description: Owner for the image.
returned: success
type: str
visibility:
description: Indicates who has access to the image.
returned: success
type: str
size:
description: Size of the image.
returned: success
type: int
tags:
description: List of tags assigned to the image
returned: success
type: list
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class ImageModule(OpenStackModule):
argument_spec = dict(
name=dict(required=True),
id=dict(),
checksum=dict(),
disk_format=dict(default='qcow2',
choices=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso', 'vhdx', 'ploop']),
container_format=dict(default='bare', choices=['ami', 'aki', 'ari', 'bare', 'ovf', 'ova', 'docker']),
owner=dict(aliases=['project']),
owner_domain=dict(aliases=['project_domain']),
min_disk=dict(type='int'),
min_ram=dict(type='int'),
is_public=dict(type='bool', default=False),
is_protected=dict(type='bool', aliases=['protected']),
filename=dict(),
ramdisk=dict(),
kernel=dict(),
properties=dict(type='dict', default={}),
volume=dict(),
tags=dict(type='list', default=[], elements='str'),
state=dict(default='present', choices=['absent', 'present']),
visibility=dict(choices=['public', 'private', 'shared', 'community']),
)
module_kwargs = dict(
mutually_exclusive=[
('filename', 'volume'),
('visibility', 'is_public'),
],
)
# resource attributes obtainable directly from params
attr_params = ('id', 'name', 'filename', 'disk_format',
'container_format', 'wait', 'timeout', 'is_public',
'is_protected', 'min_disk', 'min_ram', 'volume', 'tags')
def _resolve_visibility(self):
"""resolve a visibility value to be compatible with older versions"""
if self.params['visibility']:
return self.params['visibility']
if self.params['is_public'] is not None:
return 'public' if self.params['is_public'] else 'private'
return None
def _build_params(self, owner):
params = {attr: self.params[attr] for attr in self.attr_params}
if owner:
params['owner_id'] = owner.id
params['visibility'] = self._resolve_visibility()
params = {k: v for k, v in params.items() if v is not None}
return params
def _return_value(self, image_name_or_id):
image = self.conn.image.find_image(image_name_or_id)
if image:
image = image.to_dict(computed=False)
return image
def _build_update(self, image):
update_payload = {'visibility': self._resolve_visibility()}
for k in ('is_protected', 'min_disk', 'min_ram'):
update_payload[k] = self.params[k]
for k in ('kernel', 'ramdisk'):
if not self.params[k]:
continue
k_id = '{0}_id'.format(k)
k_image = self.conn.image.find_image(
name_or_id=self.params[k], ignore_missing=False)
update_payload[k_id] = k_image.id
update_payload = {k: v for k, v in update_payload.items()
if v is not None and image[k] != v}
for p, v in self.params['properties'].items():
if p not in image or image[p] != v:
update_payload[p] = v
if (self.params['tags']
and set(image['tags']) != set(self.params['tags'])):
update_payload['tags'] = self.params['tags']
# If both name and id are defined,then we might change the name
if self.params['id'] and \
self.params['name'] and \
self.params['name'] != image['name']:
update_payload['name'] = self.params['name']
return update_payload
def run(self):
changed = False
image_name_or_id = self.params['id'] or self.params['name']
owner_name_or_id = self.params['owner']
owner_domain_name_or_id = self.params['owner_domain']
owner_filters = {}
if owner_domain_name_or_id:
owner_domain = self.conn.identity.find_domain(
owner_domain_name_or_id)
if owner_domain:
owner_filters['domain_id'] = owner_domain.id
else:
# else user may not be able to enumerate domains
owner_filters['domain_id'] = owner_domain_name_or_id
owner = None
if owner_name_or_id:
owner = self.conn.identity.find_project(
owner_name_or_id, ignore_missing=False, **owner_filters)
image = None
if image_name_or_id:
image = self.conn.get_image(
image_name_or_id,
filters={(k, self.params[k])
for k in ['checksum'] if self.params[k] is not None})
changed = False
if self.params['state'] == 'present':
attrs = self._build_params(owner)
if not image:
# self.conn.image.create_image cannot be used because
# self.conn.create_image provides a volume parameter
# Ref.: https://opendev.org/openstack/openstacksdk/src/commit/a41d04ea197439c2f134ce3554995693933a46ac/openstack/cloud/_image.py#L306
image = self.conn.create_image(**attrs)
changed = True
if not self.params['wait']:
self.exit_json(changed=changed,
image=self._return_value(image.id),
id=image.id)
update_payload = self._build_update(image)
if update_payload:
self.conn.image.update_image(image.id, **update_payload)
changed = True
self.exit_json(changed=changed, image=self._return_value(image.id),
id=image.id)
elif self.params['state'] == 'absent' and image is not None:
# self.conn.image.delete_image() does not offer a wait parameter
self.conn.delete_image(
name_or_id=image['id'],
wait=self.params['wait'],
timeout=self.params['timeout'])
changed = True
self.exit_json(changed=changed)
def main():
module = ImageModule()
module()
if __name__ == '__main__':
main()