
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
613 lines
19 KiB
Python
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()
|