diff --git a/inventory/group_vars/taskotron-dev-client-hosts b/inventory/group_vars/taskotron-dev-client-hosts index 26f1594815..72ad37128e 100644 --- a/inventory/group_vars/taskotron-dev-client-hosts +++ b/inventory/group_vars/taskotron-dev-client-hosts @@ -69,7 +69,7 @@ buildmaster_pubkey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYzVmGYh6EHH8UAlgX6FI ############################################################ imagefactory_user: imgfac_user -imagefactory_user_home: /home/fedora/imgfac_user +imagefactory_user_home: '/home/{{imagefactory_user}}' imagefactory_shared_dir: '{{imagefactory_user_home}}/images' diff --git a/roles/taskotron/imagefactory/files/hotfix_imgfac_RESTv2.py b/roles/taskotron/imagefactory/files/hotfix_imgfac_RESTv2.py new file mode 100644 index 0000000000..e5fedc5451 --- /dev/null +++ b/roles/taskotron/imagefactory/files/hotfix_imgfac_RESTv2.py @@ -0,0 +1,390 @@ +# +# Copyright 2012 Red Hat, Inc. +# +# 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. + +import sys +import traceback + +sys.path.insert(1, '%s/imgfac/rest' % sys.path[0]) + +import logging +import os.path +from bottle import * +from imgfac.rest.RESTtools import * +from imgfac.rest.OAuthTools import oauth_protect +from imgfac.BuildDispatcher import BuildDispatcher +from imgfac.PluginManager import PluginManager +from imgfac.PersistentImageManager import PersistentImageManager +from imgfac.Version import VERSION as VERSION +from imgfac.picklingtools.xmldumper import * +from imgfac.Builder import Builder + +log = logging.getLogger(__name__) + +rest_api = Bottle(catchall=True) + +IMAGE_TYPES = {'BaseImage': 'base_image', 'TargetImage': 'target_image', 'ProviderImage': 'provider_image', + 'base_images': 'BaseImage', 'target_images': 'TargetImage', 'provider_images': 'ProviderImage'} + +def converted_response(resp_dict): + if('xml' in request.get_header('Accept', '')): + response.set_header('Content-Type', request.get_header('Accept', None)) + xml_options = XML_DUMP_STRINGS_AS_STRINGS | XML_DUMP_PRETTY | XML_DUMP_POD_LIST_AS_XML_LIST + string_stream = cStringIO.StringIO() + WriteToXMLStream(resp_dict, string_stream, options=xml_options) + converted_response = string_stream.getvalue() + return converted_response + else: + return resp_dict + +@rest_api.get('/imagefactory') +@log_request +@check_accept_header +def api_info(): + return converted_response({'name':'imagefactory', 'version':VERSION, 'api_version':'2.0'}) + +@rest_api.get('/imagefactory/base_images//raw_image') +@rest_api.get('/imagefactory/target_images//raw_image') +@rest_api.get('/imagefactory/provider_images//raw_image') +@rest_api.get('/imagefactory/base_images//target_images//raw_image') +@rest_api.get('/imagefactory/base_images//target_images//provider_images//raw_image') +@rest_api.get('/imagefactory/target_images//provider_images//raw_image') +@log_request +@oauth_protect +@check_accept_header +def get_image_file(image_id, base_image_id=None, target_image_id=None, provider_image_id=None): + try: + image = PersistentImageManager.default_manager().image_with_id(image_id) + if(not image): + raise HTTPResponse(status=404, output='No image found with id: %s' % image_id) + path, filename = os.path.split(image.data) + return static_file(filename, path, download=True) + except Exception as e: + log.exception(e) + raise HTTPResponse(status=500, output=e) + + +@rest_api.get('/imagefactory/') +@rest_api.get('/imagefactory/base_images//') +@rest_api.get('/imagefactory/target_images//') +@log_request +@oauth_protect +@check_accept_header +def list_images(image_collection, base_image_id=None, target_image_id=None, list_url=None): + try: + _type = IMAGE_TYPES[image_collection] + if _type: + fetch_spec = {'type': _type} + if base_image_id: + fetch_spec['base_image_id'] = base_image_id + if target_image_id: + fetch_spec['target_image_id'] = target_image_id + else: + raise HTTPResponse(status=404, output='%s not found' % image_collection) + + fetched_images = PersistentImageManager.default_manager().images_from_query(fetch_spec) + images = list() + _url = list_url if list_url else request.url + for image in fetched_images: + resp_item = {image_collection[0:-1]: + {'_type':type(image).__name__, + 'id':image.identifier, + 'href':'%s/%s' % (_url, image.identifier)} + } + images.append(resp_item) + + return converted_response({image_collection:images}) + except Exception as e: + log.exception(e) + raise HTTPResponse(status=500, output=e) + +@rest_api.post('/imagefactory/') +@rest_api.post('/imagefactory/base_images//') +@rest_api.post('/imagefactory/base_images//target_images//') +@rest_api.post('/imagefactory/target_images//') +@log_request +@oauth_protect +@check_accept_header +def create_image(image_collection, base_image_id=None, target_image_id=None): + try: + image_type = image_collection[0:-1] + content_type = request.headers.get('Content-Type') + form_data = form_data_for_content_type(content_type) + if(('application/x-www-form-urlencoded' in content_type) or ('multipart/form-data' in content_type)): + request_data = form_data + else: + request_data = form_data.get(image_type) + if(not request_data): + raise HTTPResponse(status=400, output='%s not found in request.' % image_type) + + req_base_img_id = request_data.get('base_image_id') + req_target_img_id = request_data.get('target_image_id') + base_img_id = req_base_img_id if req_base_img_id else base_image_id + target_img_id = req_target_img_id if req_target_img_id else target_image_id + + if(image_collection == 'base_images'): + builder = BuildDispatcher().builder_for_base_image(template=request_data.get('template'), + parameters=request_data.get('parameters')) + image = builder.base_image + elif(image_collection == 'target_images'): + builder = BuildDispatcher().builder_for_target_image(target=request_data.get('target'), + image_id=base_img_id, + template=request_data.get('template'), + parameters=request_data.get('parameters')) + image = builder.target_image + elif(image_collection == 'provider_images'): + _provider = request_data.get('provider') + _credentials = request_data.get('credentials') + _target = request_data.get('target') + if(_provider and _credentials and _target): + builder = BuildDispatcher().builder_for_provider_image(provider=_provider, + credentials=_credentials, + target=_target, + image_id=target_img_id, + template=request_data.get('template'), + parameters=request_data.get('parameters')) + image = builder.provider_image + else: + _credentials = 'REDACTED' if _credentials else None + raise HTTPResponse(status=400, output="Missing key/value pair: provider(%s), credentials(%s), target(%s)" % (_provider, _credentials, _target)) + else: + raise HTTPResponse(status=404, output="%s not found" % image_collection) + + _response = {'_type':type(image).__name__, + 'id':image.identifier, + 'href':'%s/%s' % (request.url, image.identifier)} + for key in image.metadata(): + if key not in ('identifier', 'data'): + _response[key] = getattr(image, key, None) + + response.status = 202 + return converted_response({image_collection[0:-1]:_response}) + except KeyError as e: + log.exception(e) + raise HTTPResponse(status=400, output='Missing value for key: %s' % e) + except Exception as e: + log.exception(e) + raise HTTPResponse(status=500, output=e) + +@rest_api.get('/imagefactory//') +@rest_api.get('/imagefactory/base_images///') +@rest_api.get('/imagefactory/base_images//target_images///') +@rest_api.get('/imagefactory/target_images///') +@log_request +@oauth_protect +@check_accept_header +def image_with_id(collection_type, image_id, base_image_id=None, target_image_id=None, provider_image_id=None): + try: + img_class = IMAGE_TYPES[collection_type] + if img_class: + fetch_spec = {'type': img_class, 'identifier': image_id} + try: + image = PersistentImageManager.default_manager().images_from_query(fetch_spec)[0] + _type = type(image).__name__ + _response = {'_type': _type, + 'id': image.identifier, + 'href': request.url} + for key in image.metadata(): + if key not in ('identifier', 'data', 'base_image_id', 'target_image_id'): + _response[key] = getattr(image, key, None) + + api_url = '%s://%s/imagefactory' % (request.urlparts[0], request.urlparts[1]) + + if (_type == "BaseImage"): + _objtype = 'base_image' + _response['target_images'] = list_images('target_images', + base_image_id=image.identifier, + list_url='%s/target_images' % api_url) + elif (_type == "TargetImage"): + _objtype = 'target_image' + base_image_id = image.base_image_id + if (base_image_id): + base_image_href = '%s/base_images/%s' % (api_url, base_image_id) + base_image_dict = {'_type': 'BaseImage', 'id': base_image_id, 'href': base_image_href} + _response['base_image'] = base_image_dict + else: + _response['base_image'] = None + _response['provider_images'] = list_images('provider_images', + target_image_id=image.identifier, + list_url='%s/provider_images' % api_url) + elif (_type == "ProviderImage"): + _objtype = 'provider_image' + target_image_id = image.target_image_id + if (target_image_id): + target_image_href = '%s/target_images/%s' % (api_url, target_image_id) + target_image_dict = {'_type': 'TargetImage', 'id': target_image_id, 'href': target_image_href} + _response['target_image'] = target_image_dict + else: + _response['target_image'] = None + else: + log.error("Returning HTTP status 500 due to unknown image type: %s" % _type) + raise HTTPResponse(status=500, output='Bad type for found object: %s' % _type) + + response.status = 200 + return converted_response({_objtype: _response}) + + except IndexError as e: + log.warning(e) + raise HTTPResponse(status=404, output='No %s found with id: %s' % (img_class, image_id)) + else: + raise HTTPResponse(status=404, output='Unknown resource type: %s' % collection_type) + except KeyError as e: + if collection_type == 'plugins': + return get_plugins(plugin_id=image_id) + else: + log.exception(e) + raise HTTPResponse(status=500, output=e) + except Exception as e: + log.exception(e) + raise HTTPResponse(status=500, output=e) + +@rest_api.delete('/imagefactory//') +@rest_api.delete('/imagefactory/base_images///') +@rest_api.delete('/imagefactory/base_images//target_images///') +@rest_api.delete('/imagefactory/target_images///') +@log_request +@oauth_protect +@check_accept_header +def delete_image_with_id(image_id, collection_type=None, base_image_id=None, target_image_id=None, provider_image_id=None): + try: + response.status = 204 + image = PersistentImageManager.default_manager().image_with_id(image_id) + if(not image): + raise HTTPResponse(status=404, output='No image found with id: %s' % image_id) + image_class = type(image).__name__ + builder = Builder() + content_type = request.headers.get('Content-Type') + form_data = form_data_for_content_type(content_type) + required_values = set(['provider', 'credentials', 'target']) + + if form_data: + root_object = form_data.get(IMAGE_TYPES.get(image_class)) + if root_object and (type(root_object) is dict): + request_data = root_object + else: + request_data = form_data + + if image_class == 'ProviderImage': + missing_values = required_values.difference(request_data) + if len(missing_values) > 0: + raise HTTPResponse(status=400, output='Missing required values: %s' % missing_values) + + builder.delete_image(provider=request_data.get('provider'), + credentials=request_data.get('credentials'), + target=request_data.get('target'), + image_object=image, + parameters=request_data.get('parameters')) + else: + if image_class == 'ProviderImage': + raise HTTPResponse(status=400, output='Missing required values:%s' % required_values) + else: + builder.delete_image(provider=None, credentials=None, target=None, image_object=image, parameters=None) + except Exception as e: + log.exception(e) + raise HTTPResponse(status=500, output=e) + +@rest_api.get('/imagefactory/plugins') +@rest_api.get('/imagefactory/plugins/') +@rest_api.get('/imagefactory/plugins/') +@log_request +@oauth_protect +@check_accept_header +def get_plugins(plugin_id=None): + try: + response.status = 200 + plugin_mgr = PluginManager() + if(plugin_id): + plugin = plugin_mgr.plugins[plugin_id].copy() + plugin.update({'_type':'plugin', + 'id':plugin_id, + 'href':'%s/%s' % (request.url, plugin_id)}) + return converted_response(plugin) + else: + plugins = plugin_mgr.plugins.copy() + for plugin in plugins: + plugins[plugin].update({'_type':'plugin', + 'id':plugin, + 'href':'%s/%s' % (request.url, plugin)}) + return converted_response({'plugins':plugins.values()}) + except Exception as e: + log.exception(e) + raise HTTPResponse(status=500, output='%s %s' % (e, traceback.format_exc())) + +@rest_api.get('/imagefactory/jeos') +@log_request +@check_accept_header +def get_jeos_config(): + try: + jeos_confs = dict() + jeos_conf_list = ApplicationConfiguration().configuration['jeos_config'] + for item in jeos_conf_list: + jeos_confs[jeos_conf_list.index(item)] = item + return converted_response(jeos_confs) + except Exception as e: + log.exception(e) + raise HTTPResponse(status=500, output='%s %s' % (e, traceback.format_exc())) + +@rest_api.get('/imagefactory/jeos/images') +@rest_api.get('/imagefactory/jeos/images/') +@log_request +@check_accept_header +def get_jeos_info(jeos_id=None): + try: + if jeos_id: + return HTTPResponse(status=501) + + response.status = 200 + return converted_response(ApplicationConfiguration().jeos_images) + except Exception as e: + log.exception(e) + raise HTTPResponse(status=500, output='%s %s' % (e, traceback.format_exc())) + +# Things we have not yet implemented +@rest_api.get('/imagefactory/targets') +@rest_api.get('/imagefactory/targets/') +@rest_api.get('/imagefactory/targets//providers') +@rest_api.get('/imagefactory/targets//providers/') +@log_request +@check_accept_header +def method_not_implemented(**kw): + """ + @return 501 Not Implemented + """ + raise HTTPResponse(status=501) + +# Things we don't plan to implement +#@rest_api.route('/imagefactory', method=('PUT','POST','DELETE')) +#@rest_api.route('/imagefactory/base_images', method=('PUT','DELETE')) +#@rest_api.route('/imagefactory/base_images/', method=('PUT','POST')) +#@rest_api.route('/imagefactory/target_images', method=('PUT','DELETE')) +#@rest_api.route('/imagefactory/target_images/', method=('PUT','POST')) +#@rest_api.route('/imagefactory/target_images//provider_images', method=('PUT','DELETE')) +#@rest_api.route('/imagefactory/provider_images', method=('PUT','DELETE')) +#@rest_api.route('/imagefactory/provider_images/', method=('PUT','POST')) +#@rest_api.route('/imagefactory/base_images//target_images', method=('PUT','DELETE')) +#@rest_api.route('/imagefactory/base_images//target_images/', method=('PUT','POST')) +#@rest_api.route('/imagefactory/base_images//target_images//provider_images', method=('PUT','DELETE')) +#@rest_api.route('/imagefactory/targets', method=('PUT','POST','DELETE')) +#@rest_api.route('/imagefactory/targets/', method=('PUT','POST','DELETE')) +#@rest_api.route('/imagefactory/targets//providers', method=('PUT','POST','DELETE')) +#@rest_api.route('/imagefactory/targets//providers/', method=('PUT','POST','DELETE')) +#@rest_api.route('/imagefactory/jeos', method=('PUT','POST','DELETE')) +#@rest_api.route('/imagefactory/jeos/', method=('PUT','POST','DELETE')) +#@rest_api.route('/imagefactory/plugins', method=('PUT','POST','DELETE')) +#@rest_api.route('/imagefactory/plugins/', method=('PUT','POST','DELETE')) +#def method_not_allowed(**kw): +# """ +# @return 405 Method Not Allowed +# """ +# raise HTTPResponse(status=405) diff --git a/roles/taskotron/imagefactory/tasks/main.yml b/roles/taskotron/imagefactory/tasks/main.yml index ba9ea33377..70c06028ab 100644 --- a/roles/taskotron/imagefactory/tasks/main.yml +++ b/roles/taskotron/imagefactory/tasks/main.yml @@ -9,6 +9,9 @@ - name: copy imagefactory.conf config file copy: src=imagefactory.conf dest=/etc/imagefactory/imagefactory.conf owner=root group=root mode=0644 +- name: hotfix imagefactory's REST api to allow file download + copy: src=hotfix_imgfac_RESTv2.py dest=/usr/lib/python2.7/site-packages/imgfac/rest/RESTv2.py owner=root group=root mode=0644 + - name: enable imagefactory service: name=imagefactoryd state=started enabled=yes