# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from ecl import exceptions
from ecl.image import image_service
from ecl import resource2
from ecl import utils
import json
[docs]class Image(resource2.Resource):
resources_key = 'images'
service = image_service.ImageService()
base_path = '/' + service.version + '/images'
# capabilities
allow_create = True
allow_get = True
allow_update = True
allow_delete = True
allow_list = True
patch_update = True
_query_mapping = resource2.QueryParameters(
"limit", "marker",
"name", "visibility",
"member_status", "owner",
"status", "size_min",
"size_max", "sort_key",
"sort_dir", "tag"
)
# NOTE: Do not add "self" support here. If you've used Python before,
# you know that self, while not being a reserved word, has special
# meaning. You can't call a class initializer with the self name
# as the first argument and then additionally in kwargs, as we
# do when we're constructing instances from the JSON body.
# Resource.list explicitly pops off any "self" keys from bodies so
# that we don't end up getting the following:
# TypeError: __init__() got multiple values for argument 'self'
# The image data (bytes or a file-like object)
data = None
# Properties
#: Descriptive name for the image
name = resource2.Body('name')
#: Status of the image
image_status = resource2.Body('status')
#: String related to the image
tags = resource2.Body('tags')
#: The container format refers to whether the VM image is in a file
#: format that also contains metadata about the actual VM.
#: Container formats include OVF and Amazon AMI. In addition,
#: a VM image might not have a container format - instead,
#: the image is just a blob of unstructured data.
container_format = resource2.Body('container_format')
#: Date and time of image registration
created_at = resource2.Body('created_at')
#: Valid values are: aki, ari, ami, raw, iso, vhd, vdi, qcow2, or vmdk.
#: The disk format of a VM image is the format of the underlying
#: disk image. Virtual appliance vendors have different formats
#: for laying out the information contained in a VM disk image.
disk_format = resource2.Body('disk_format')
#: Include location_url and location_metadata.
#: location_url: URL to access the image file kept in external store
#: (it is shown when 'show_multiple_locations' option is enabled)
locations = resource2.Body('locations')
#: The URL to access the image file kept in external store. It appears
#: when you set the show_image_direct_url option to true in the
#: Image service's configuration file.
direct_url = resource2.Body('direct_url')
#: Date and time of the last image modification.
updated_at = resource2.Body('updated_at')
#: The image visibility.
visibility = resource2.Body('visibility')
#: Amount of disk space (in GB) required to boot image.
min_disk = resource2.Body('min_disk')
#: If true, image will not be deletable.
protected = resource2.Body('protected')
#: An identifier for the image.
id = resource2.Body('id')
#: URL for the virtual machine image file.
file = resource2.Body('file')
#: md5 hash of image contents
checksum = resource2.Body('checksum')
#: Owner of the image.
owner = resource2.Body('owner')
#: Size of image file in bytes.
size = resource2.Body('size')
#: Amount of ram (in MB) required to boot image.
min_ram = resource2.Body('min_ram')
#: URL for schema of the virtual machine image.
image_schema = resource2.Body('image_schema')
##: URL for the virtual machine image.
self = resource2.Body('self')
#: URL for schema of the virtual machine images.
images_schema = resource2.Body('images_schema')
#: URL for the first page of response
first = resource2.Body('first')
#: The key of the extra properties.
extra_key = resource2.Body('extra_key')
#: The value of the extra properties.
extra_value = resource2.Body('extra_value')
#: OS information of this image.
os_type = resource2.Body('.os.type')
#: Service information of this image.
service_type = resource2.Body('.service.type')
# def _get_image_with_extra_key(self, attr):
# image = self.existing(**attr)
#
# for key in attr.keys():
# if not hasattr(image, key):
# image.__setattr__(key, attr[key])
# return image
#
# def list(self, session, **query):
# """Lists virtual machine (VM) images."""
#
# if query != None:
# query_params = self._query_mapping._transpose(query)
# uri = self.base_path
#
# params = []
# for key in query_params.keys():
# item = "%s=%s" % (str(key), str(query_params[key]))
# params.append(item)
#
# if len(params) != 0:
# uri += '?'
# for item in params:
# uri += item + '&'
# uri = uri[:-1]
#
# resp = session.get(
# uri,
# headers={"Accept": "application/json"},
# endpoint_filter=self.service
# )
# resp = resp.json()
# images_schema = resp['schema']
# first = resp['first']
# resp = resp[self.resources_key]
#
# for data in resp:
# # Do not allow keys called "self" through. Glance chose
# # to name a key "self", so we need to pop it out because
# # we can't send it through cls.existing and into the
# # Resource initializer. "self" is already the first
# # argument and is practically a reserved word.
# data.pop("self", None)
#
# image = self._get_image_with_extra_key(data)
# image.images_schema = images_schema
# image.first = first
#
# yield image
[docs] def get(self, session, image_id):
"""Get a single image's detailed information."""
url = utils.urljoin(self.base_path, image_id)
resp = session.get(
url,
headers={"Accept": "application/json"},
endpoint_filter=self.service
)
#For extra key/value use.
self.response = resp.json()
self._translate_response(resp, has_body=True)
return self
[docs] def update(self, session, image_id, attrs_list):
"""Updates a specified image."""
url = utils.urljoin(self.base_path, image_id)
attrs_list = json.dumps(attrs_list)
resp = session.patch(url, endpoint_filter=self.service, data=attrs_list, headers={"Content-Type": "application/openstack-images-v2.1-json-patch", "Accept": ""})
self._translate_response(resp, has_body=True)
return self
[docs] def upload(self, session, image_id, image_data):
"""Upload data into an existing image"""
url = utils.urljoin(self.base_path, image_id, 'file')
resp = session.put(url, endpoint_filter=self.service,
data=image_data,
headers={"Content-Type": "application/octet-stream",
"Accept": "application/json"})
self._translate_response(resp, has_body=False)
return self
[docs] def download(self, session, image_id):
"""Download the data contained in an image"""
# TODO(briancurtin): This method should probably offload the get
# operation into another thread or something of that nature.
url = utils.urljoin(self.base_path, image_id, 'file')
resp = session.get(url, endpoint_filter=self.service)
return resp.content
@classmethod
[docs] def find(cls, session, name_or_id, ignore_missing=True, **params):
"""Find a resource by its name or id.
:param session: The session to use for making this request.
:type session: :class:`~ecl.session.Session`
:param name_or_id: This resource's identifier, if needed by
the request. The default is ``None``.
:param bool ignore_missing: When set to ``False``
:class:`~ecl.exceptions.ResourceNotFound` will be
raised when the resource does not exist.
When set to ``True``, None will be returned when
attempting to find a nonexistent resource.
:param dict params: Any additional parameters to be passed into
underlying methods, such as to
:meth:`~ecl.resource2.Resource.existing`
in order to pass on URI parameters.
:return: The :class:`Resource` object matching the given name or id
or None if nothing matches.
:raises: :class:`ecl.exceptions.DuplicateResource` if more
than one resource is found for this request.
:raises: :class:`ecl.exceptions.ResourceNotFound` if nothing
is found and ignore_missing is ``False``.
"""
# Try to short-circuit by looking directly for a matching ID.
params["limit"] = 10000
data = cls.list(session, **params)
result = cls._get_one_match(name_or_id, data)
if result is not None:
return result
if ignore_missing:
return None
raise exceptions.ResourceNotFound(
"No %s found for %s" % (cls.__name__, name_or_id))