Add support for required images file
If a package contains a file with a list of required images check if they are present in glance and attempt to download them if not. Supports v1 glanceclient only. Change-Id: Ibb39fbbadef109b117094afefd383fe24de0a792 Partially implements: blueprint muranoclient-marketplace-support
This commit is contained in:
parent
51058b6238
commit
82a1d10162
|
@ -255,25 +255,47 @@ class Package(FileWrapperMixin):
|
|||
file_obj = File(file_obj)
|
||||
return Package(file_obj)
|
||||
|
||||
@property
|
||||
def contents(self):
|
||||
"""Contents of a package."""
|
||||
if not hasattr(self, '_contents'):
|
||||
try:
|
||||
self._file.seek(0)
|
||||
self._zip_obj = zipfile.ZipFile(
|
||||
StringIO.StringIO(self._file.read()))
|
||||
except Exception:
|
||||
LOG.exception("An error occurred,"
|
||||
" while parsint the package")
|
||||
raise
|
||||
return self._zip_obj
|
||||
|
||||
@property
|
||||
def manifest(self):
|
||||
"""Parsed manifest file of a package."""
|
||||
if not hasattr(self, '_manifest'):
|
||||
try:
|
||||
self._file.seek(0)
|
||||
zip_obj = zipfile.ZipFile(
|
||||
StringIO.StringIO(self._file.read()))
|
||||
self._manifest = yaml.safe_load(zip_obj.open('manifest.yaml'))
|
||||
except (zipfile.BadZipfile, KeyError, yaml.error.YAMLError) as e:
|
||||
self._manifest = yaml.safe_load(
|
||||
self.contents.open('manifest.yaml'))
|
||||
except Exception:
|
||||
LOG.exception("An error occurred,"
|
||||
" while extracting manifest from package")
|
||||
raise ValueError(e)
|
||||
raise
|
||||
return self._manifest
|
||||
|
||||
def images(self):
|
||||
"""Returns a list of required image specifications."""
|
||||
if 'images.lst' not in self.contents.namelist():
|
||||
return []
|
||||
try:
|
||||
return yaml.safe_load(
|
||||
self.contents.open('images.lst')).get('Images', [])
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def requirements(self, base_url, dep_dict=None):
|
||||
"""Recursively scan Require section of manifests of all the
|
||||
dependencies. Returns a dict with FQPNs as keys and respective
|
||||
PackageFiles as values
|
||||
Package objects as values
|
||||
"""
|
||||
if not dep_dict:
|
||||
dep_dict = {}
|
||||
|
@ -297,6 +319,76 @@ class Package(FileWrapperMixin):
|
|||
return dep_dict
|
||||
|
||||
|
||||
def ensure_images(glance_client, image_specs, base_url):
|
||||
"""Ensure that images from image_specs are available in glance. If not
|
||||
attempts: instructs glance to download the images and sets murano-specific
|
||||
metadata for it.
|
||||
"""
|
||||
def _image_valid(image, keys):
|
||||
for key in keys:
|
||||
if key not in image:
|
||||
LOG.warning("Image specification invalid: "
|
||||
"No {0} key in image ".format(key))
|
||||
return False
|
||||
return True
|
||||
|
||||
keys = ['Name', 'Hash', 'DiskFormat', 'ContainerFormat', ]
|
||||
for image_spec in image_specs:
|
||||
if not _image_valid(image_spec, keys):
|
||||
continue
|
||||
filters = {
|
||||
'name': image_spec["Name"],
|
||||
'disk_format': image_spec["DiskFormat"],
|
||||
'container_format': image_spec["ContainerFormat"],
|
||||
}
|
||||
# NOTE(kzaitsev): glance v1 client does not allow checksum in
|
||||
# a filter, so we have to filter ourselves
|
||||
for img_obj in glance_client.images.list(filters=filters):
|
||||
img = img_obj.to_dict()
|
||||
if img['checksum'] == image_spec['Hash']:
|
||||
break
|
||||
else:
|
||||
img = None
|
||||
|
||||
update_metadata = False
|
||||
if img:
|
||||
LOG.info("Found desired image {0}, id {1}".format(
|
||||
img['name'], img['id']))
|
||||
# check for murano meta-data
|
||||
if 'murano_image_info' in img.get('properties', {}):
|
||||
LOG.info("Image {0} already has murano meta-data".format(
|
||||
image_spec['Name']))
|
||||
else:
|
||||
update_metadata = True
|
||||
else:
|
||||
LOG.info("Desired image {0} not found attempting "
|
||||
"to download".format(image_spec['Name']))
|
||||
update_metadata = True
|
||||
|
||||
download_url = to_url(
|
||||
image_spec.get("Url", image_spec['Name']),
|
||||
base_url=base_url,
|
||||
path='/images/',
|
||||
)
|
||||
|
||||
LOG.info("Instructing glance to download image {0}".format(
|
||||
image_spec['Name']))
|
||||
img = glance_client.images.create(
|
||||
name=image_spec["Name"],
|
||||
container_format=image_spec['ContainerFormat'],
|
||||
disk_format=image_spec['DiskFormat'],
|
||||
copy_from=download_url)
|
||||
img = img.to_dict()
|
||||
|
||||
if update_metadata and 'Meta' in image_spec:
|
||||
LOG.info("Updating image {0} metadata".format(
|
||||
image_spec['Name']))
|
||||
murano_image_info = jsonutils.dumps(image_spec['Meta'])
|
||||
glance_client.images.update(
|
||||
img['id'], properties={'murano_image_info':
|
||||
murano_image_info})
|
||||
|
||||
|
||||
class Bundle(FileWrapperMixin):
|
||||
"""Represents murano bundle contents."""
|
||||
@staticmethod
|
||||
|
|
|
@ -22,6 +22,7 @@ import argparse
|
|||
import logging
|
||||
import sys
|
||||
|
||||
import glanceclient
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
from oslo.utils import encodeutils
|
||||
import six
|
||||
|
@ -135,6 +136,10 @@ class MuranoShell(object):
|
|||
default=utils.env('MURANO_URL'),
|
||||
help='Defaults to env[MURANO_URL]')
|
||||
|
||||
parser.add_argument('--glance-url',
|
||||
default=utils.env('GLANCE_URL'),
|
||||
help='Defaults to env[GLANCE_URL]')
|
||||
|
||||
parser.add_argument('--murano-api-version',
|
||||
default=utils.env(
|
||||
'MURANO_API_VERSION', default='1'),
|
||||
|
@ -320,8 +325,11 @@ class MuranoShell(object):
|
|||
'cacert': args.os_cacert,
|
||||
'include_pass': args.include_password
|
||||
}
|
||||
glance_kwargs = kwargs
|
||||
glance_kwargs = kwargs.copy()
|
||||
|
||||
endpoint = args.murano_url
|
||||
glance_endpoint = args.glance_url
|
||||
|
||||
if not args.os_no_client_auth:
|
||||
_ksclient = self._get_ksclient(**kwargs)
|
||||
|
@ -338,9 +346,11 @@ class MuranoShell(object):
|
|||
'endpoint_type': args.os_endpoint_type,
|
||||
'include_pass': args.include_password
|
||||
}
|
||||
glance_kwargs = kwargs.copy()
|
||||
|
||||
if args.os_region_name:
|
||||
kwargs['region_name'] = args.os_region_name
|
||||
glance_kwargs['region_name'] = args.os_region_name
|
||||
|
||||
if not endpoint:
|
||||
endpoint = self._get_endpoint(_ksclient, **kwargs)
|
||||
|
@ -348,6 +358,27 @@ class MuranoShell(object):
|
|||
if args.api_timeout:
|
||||
kwargs['timeout'] = args.api_timeout
|
||||
|
||||
if not glance_endpoint:
|
||||
try:
|
||||
glance_endpoint = self._get_endpoint(
|
||||
_ksclient, service_type='image')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
glance_client = None
|
||||
if glance_endpoint:
|
||||
try:
|
||||
glance_client = glanceclient.Client(
|
||||
'1', glance_endpoint, **glance_kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
if glance_client:
|
||||
kwargs['glance_client'] = glance_client
|
||||
else:
|
||||
logger.warning("Could not initialise glance client. "
|
||||
"Image creation will be unavailable.")
|
||||
kwargs['glance_client'] = None
|
||||
|
||||
client = apiclient.Client(api_version, endpoint, **kwargs)
|
||||
|
||||
args.func(client, args)
|
||||
|
|
|
@ -35,6 +35,7 @@ class Client(http.HTTPClient):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a new client for the Murano v1 API."""
|
||||
self.glance_client = kwargs.pop('glance_client', None)
|
||||
super(Client, self).__init__(*args, **kwargs)
|
||||
self.environments = environments.EnvironmentManager(self)
|
||||
self.sessions = sessions.SessionManager(self)
|
||||
|
|
|
@ -217,6 +217,14 @@ def do_package_import(mc, args):
|
|||
package = utils.Package.fromFile(filename)
|
||||
reqs = package.requirements(base_url=args.murano_repo_url)
|
||||
for name, package in reqs.iteritems():
|
||||
try:
|
||||
utils.ensure_images(
|
||||
glance_client=mc.glance_client,
|
||||
image_specs=package.images(),
|
||||
base_url=args.murano_repo_url)
|
||||
except Exception as e:
|
||||
print("Error {0} occurred while installing "
|
||||
"images for {1}".format(e, name))
|
||||
try:
|
||||
mc.packages.create(data, {name: package.file()})
|
||||
except Exception as e:
|
||||
|
@ -254,10 +262,19 @@ def do_bundle_import(mc, args):
|
|||
except Exception as e:
|
||||
print("Error {0} occurred while "
|
||||
"parsing package {1}".format(e, package_info['Name']))
|
||||
|
||||
reqs = package.requirements(base_url=args.murano_repo_url)
|
||||
for name, package in reqs.iteritems():
|
||||
for name, dep_package in reqs.iteritems():
|
||||
try:
|
||||
mc.packages.create(data, {name: package.file()})
|
||||
utils.ensure_images(
|
||||
glance_client=mc.glance_client,
|
||||
image_specs=dep_package.images(),
|
||||
base_url=args.murano_repo_url)
|
||||
except Exception as e:
|
||||
print("Error {0} occurred while installing "
|
||||
"images for {1}".format(e, name))
|
||||
try:
|
||||
mc.packages.create(data, {name: dep_package.file()})
|
||||
except Exception as e:
|
||||
print("Error {0} occurred while "
|
||||
"installing package {1}".format(e, name))
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
pbr>=0.6,!=0.7,<1.0
|
||||
argparse
|
||||
PrettyTable>=0.7,<0.8
|
||||
python-glanceclient>=0.15.0
|
||||
python-keystoneclient>=0.11.1
|
||||
httplib2>=0.7.5
|
||||
iso8601>=0.1.9
|
||||
|
|
Loading…
Reference in New Issue