def post(self, data): """Create a new pipeline.""" policy.check('create_pipeline', pecan.request) js_data = data.as_dict(objects.registry.Pipeline) host_url = pecan.request.application_url.rstrip('/') if data.plan_uri is not wsme.Unset: plan_uri = data.plan_uri if plan_uri.startswith(host_url): pl_uuid = plan_uri.split('/')[-1] pl = objects.registry.Plan.get_by_uuid( pecan.request.security_context, pl_uuid) js_data['plan_id'] = pl.id else: # TODO(asalkeld) we are not hosting the plan so # download the plan and insert it into our db. raise exception.BadRequest(reason=_( 'The plan was not hosted in solum')) if js_data.get('plan_id') is None: raise exception.BadRequest(reason=_( 'The plan was not given or could not be found')) handler = pipeline_handler.PipelineHandler( pecan.request.security_context) return pipeline.Pipeline.from_db_model( handler.create(js_data), host_url)
def _update_app_scale_config(self, app, data): scale_config = dict() target = data.get('scale_target', '1') try: target = int(target) except ValueError: msg = "Must provide integer value for scale target." raise exception.BadRequest(reason=msg) if target <= 0: msg = "Scale target must be greater than zero." raise exception.BadRequest(reason=msg) if target > cfg.CONF.api.max_instances_per_app: msg = "Target scale '%s' exceeds maximum scale limit '%s'." % ( target, cfg.CONF.api.max_instances_per_app) raise exception.ResourceLimitExceeded(reason=msg) current_config = app.scale_config if current_config: current_config[app.name]['target'] = str(target) scale_config['scale_config'] = current_config else: config_data = dict() config_data['target'] = str(target) app_scale_config = dict() app_scale_config[app.name] = config_data scale_config = dict() scale_config['scale_config'] = app_scale_config objects.registry.App.update_and_save(self.context, app.id, scale_config)
def post(self, data): """Create a new app.""" request.check_request_for_https() if not data: raise exception.BadRequest(reason='No data.') self._validate(data) handler = app_handler.AppHandler(pecan.request.security_context) app_data = data.as_dict(app.App) try: raw_content = yamlutils.load(pecan.request.body) except ValueError: try: raw_content = json.loads(pecan.request.body) except ValueError as exp: LOG.exception(exp) raise exception.BadRequest(reason='Invalid app data.') app_data['raw_content'] = json.dumps(raw_content) new_app = handler.create(app_data) created_app = app.App.from_db_model(new_app, pecan.request.host_url) return created_app
def patch(self, uuid): """Patch an existing CAMP-style plan.""" handler = (plan_handler. PlanHandler(pecan.request.security_context)) plan_obj = handler.get(uuid) # TODO([email protected]) check if there are any assemblies that # refer to this plan and raise an PlanStillReferenced exception if # there are. if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest(reason='empty request body') # check to make sure the request has the right Content-Type if (pecan.request.content_type is None or pecan.request.content_type != 'application/json-patch+json'): raise exception.UnsupportedMediaType( name=pecan.request.content_type, method='PATCH') try: patch = jsonpatch.JsonPatch.from_string(pecan.request.body) patched_obj = patch.apply(plan_obj.refined_content()) db_obj = handler.update(uuid, patched_obj) except KeyError: # a key error indicates one of the patch operations is missing a # component raise exception.BadRequest(reason=MAL_PATCH_ERR) except jsonpatch.JsonPatchConflict: raise exception.Unprocessable except jsonpatch.JsonPatchException as jpe: raise JsonPatchProcessingException(reason=six.text_type(jpe)) return fluff_plan(db_obj.refined_content(), db_obj.uuid)
def _validate(self, app_data): # check max apps created for given tenant handler = app_handler.AppHandler(pecan.request.security_context) if len(handler.get_all()) >= cfg.CONF.api.max_apps_per_tenant: msg = "Cannot create application as maximum allowed limit reached." raise exception.ResourceLimitExceeded(reason=msg) if not app_data.languagepack: raise exception.BadRequest(reason="Languagepack not specified.") if not app_data.name: raise exception.BadRequest(reason='App name cannot be empty.') msg = ("Application name must be 1-100 characters long, only contain " "a-z,0-9,-,_ and start with an alphabet character.") # check if app name contains any invalid characters if not app_data.name or not app_data.name[0].isalpha(): raise exception.BadRequest(reason=msg) try: re.match(r'^([a-z0-9-_]{1,100})$', app_data.name).group(0) except AttributeError: raise exception.BadRequest(reason=msg) # check if languagepack exists or not if str(app_data.languagepack).lower() != "false": try: objects.registry.Image.get_lp_by_name_or_uuid( pecan.request.security_context, app_data.languagepack, include_operators_lp=True) except exception.ResourceNotFound: raise exception.ObjectNotFound(name="Languagepack", id=app_data.languagepack)
def init_plan_by_version(yml_input_plan): version = yml_input_plan.get('version') if version is None: raise exception.BadRequest( reason='Version attribute is missing in Plan') mod = sys.modules[__name__] if not hasattr(mod, 'init_plan_v%s' % version): raise exception.BadRequest(reason='Plan version %s is invalid.' % version) return getattr(mod, 'init_plan_v%s' % version)(yml_input_plan)
def init_plan_v1(yml_input_plan): if not yml_input_plan.get('name'): raise exception.BadRequest(reason="Name field is missing.") try: pp = plan.Plan(**yml_input_plan) except ValueError as ve: raise exception.BadRequest(reason=six.text_type(ve)) try: name_regex = re.compile(r'^([a-zA-Z0-9-_]{1,100})$') assert name_regex.match(pp.name), 'Plan name is invalid.' except AssertionError as ae: raise exception.BadRequest(reason=six.text_type(ae)) return pp
def post(self, data): """Create a new workflow.""" request.check_request_for_https() if not data: raise exception.BadRequest(reason='No data.') ahandler = app_handler.AppHandler(pecan.request.security_context) app_model = ahandler.get(self.app_id) handler = wf_handler.WorkflowHandler(pecan.request.security_context) data.app_id = app_model.id data.config = app_model.workflow_config data.source = app_model.source wf_data = data.as_dict(workflow.Workflow) du_id = None if data.du_id: du_id = data.du_id self._verify_du_exists(pecan.request.security_context, du_id) return workflow.Workflow.from_db_model( handler.create(wf_data, commit_sha='', status_url='', du_id=du_id), pecan.request.host_url)
def put(self): """Modify this plan.""" policy.check('update_plan', pecan.request.security_context) # make sure the plan exists before parsing the request handler = plan_handler.PlanHandler(pecan.request.security_context) handler.get(self._id) host_url = pecan.request.application_url.rstrip('/') if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest(reason="No data.") if (pecan.request.content_type is not None and 'yaml' in pecan.request.content_type): data = init_yml_plan_by_version() updated_plan_yml = yamlutils.dump(yaml_content(handler.update( self._id, data.as_dict(objects.registry.Plan)))) else: data = init_json_plan_by_version() plan_obj = handler.update(self._id, data.as_dict(objects.registry.Plan)) updated_plan_yml = wsme_json.encode_result(plan.Plan.from_db_model( plan_obj, host_url), plan.Plan) pecan.response.status = 200 return updated_plan_yml
def init_json_plan_by_version(): try: json_input_plan = json.loads(pecan.request.body) except ValueError as excp: raise exception.BadRequest(reason='Plan is invalid. ' + six.text_type(excp)) return init_plan_by_version(json_input_plan)
def init_yml_plan_by_version(): try: yml_input_plan = yamlutils.load(pecan.request.body) except ValueError as excp: LOG.error("Invalid plan.") raise exception.BadRequest(reason='Plan is invalid. ' + six.text_type(excp)) return init_plan_by_version(yml_input_plan)
def check_url(data): # try to use a correct git uri pt = re.compile(r'^(http://|https://|git@)(.+)(/|:/)(.+)(.+)(\.git)') match = pt.search(data) if not match: msg = ("Bad git url. Provide git url in the following format: \n" "Public repo: https://github.com/<USER>/<REPO>.git\n" "Private repo: <http://|git@><HOST>:<USER>/<REPO>.git\n") raise exc.BadRequest(reason=msg)
def _verify_du_exists(self, ctxt, du_id): du_image_backend = cfg.CONF.worker.image_storage if du_image_backend.lower() == 'glance': self._verify_du_image_exists_in_glance(ctxt, du_id) elif du_image_backend.lower() == 'swift': self._verify_du_image_exists_in_swift(ctxt, du_id) else: raise exception.BadRequest(message="DU image id not recognized.") return
def post(self, trigger_id): """Trigger a new event on Solum.""" policy.check('create_trigger', pecan.request.security_context) commit_sha = '' status_url = None collab_url = None try: query = query_dict(pecan.request.query_string) workflow = self._get_workflow(query) body = json.loads(pecan.request.body) if ('sender' in body and 'url' in body['sender'] and 'api.github.com' in body['sender']['url']): action = body.get('action', None) if 'comment' in body: # Process a request for rebuilding commit_sha, collab_url = self._process_request(body) elif 'pull_request' in body: # Process a GitHub pull request commit_sha = body['pull_request']['head']['sha'] else: raise exception.NotImplemented() # An example of Github statuses_url # https://api.github.com/repos/:user/:repo/statuses/{sha} if commit_sha: status_url = body['repository']['statuses_url'].format( sha=commit_sha) else: # Request NOT from a Github repo raise exception.NotImplemented() except Exception as exc: if isinstance(exc, exception.SolumException): raise info_msg = "Expected fields not found in request body." LOG.info(info_msg) raise exception.BadRequest(reason=info_msg) try: # Trigger workflow only on PR create and on rebuild request if action in [ 'created', 'opened', 'edited', 'reopened', 'synchronize', 'closed' ]: handler = app_handler.AppHandler(None) handler.trigger_workflow(trigger_id, commit_sha, status_url, collab_url, workflow=workflow) except exception.ResourceNotFound: LOG.error("Incorrect trigger url.") raise pecan.response.status = 202
def test_wrap_under_500(self, mockpost, mockreq, mockresp): mockpost.__name__ = 'post' mockreq.__name__ = 'request' mockresp.__name__ = 'response' mockpost.side_effect = exception.BadRequest(reason="Testing") mockreq.body = '{"foo":"bar"}' mockreq.content_type = "application/json" resp = TestWsmePecanController().post() self.assertEqual(exception.BadRequest.code, mockresp.status) self.assertIn('Testing', resp.get('faultstring'))
def post(self, data): """Create a new languagepack.""" handler = language_pack_handler.LanguagePackHandler( pecan.request.security_context) host_url = pecan.request.host_url msg = ("Languagepack name must be 1-100 characters long, only contain " "a-z,0-9,-,_ and start with an alphabet character.") if not data.name or not data.name[0].isalpha(): raise exception.BadRequest(reason=msg) try: re.match(r'^([a-z0-9-_]{1,100})$', data.name).group(0) except AttributeError: raise exception.BadRequest(reason=msg) return language_pack.LanguagePack.from_db_model( handler.create(data.as_dict(objects.registry.Image), data.lp_metadata, data.lp_params), host_url)
def _validate(self, app_data): # check max apps created for given tenant handler = app_handler.AppHandler(pecan.request.security_context) if len(handler.get_all()) >= cfg.CONF.api.max_apps_per_tenant: msg = "Cannot create application as maximum allowed limit reached." raise exception.ResourceLimitExceeded(reason=msg) if not app_data.languagepack: raise exception.BadRequest(reason="Languagepack not specified.") if not app_data.name: raise exception.BadRequest(reason='App name cannot be empty.') # check if languagepack exists or not try: objects.registry.Image.get_lp_by_name_or_uuid( pecan.request.security_context, app_data.languagepack, include_operators_lp=True) except exception.ResourceNotFound: raise exception.ObjectNotFound(name="Languagepack", id=app_data.languagepack)
def post(self): """Create a new plan.""" if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest try: yml_input_plan = yamlutils.load(pecan.request.body) except ValueError as excp: raise exception.BadRequest(reason='Plan is invalid. ' + excp.message) handler, data = init_plan_by_version(yml_input_plan) create_plan_yml = yamlutils.dump( yaml_content(handler.create(data.as_dict(objects.registry.Plan)))) pecan.response.status = 201 return create_plan_yml
def patch(self, data): """Modify this app.""" request.check_request_for_https() handler = app_handler.AppHandler(pecan.request.security_context) handler.get(self._id) if not data: raise exception.BadRequest(reason="No body detected") updated_app = handler.patch(self._id, data) updated_app = app.App.from_db_model(updated_app, pecan.request.host_url) return updated_app
def post(self, data): """Create a new assembly.""" js_data = data.as_dict(objects.registry.Assembly) if data.plan_uri is not wsme.Unset: plan_uri = data.plan_uri if plan_uri.startswith(pecan.request.host_url): pl_uuid = plan_uri.split('/')[-1] pl = objects.registry.Plan.get_by_uuid( pecan.request.security_context, pl_uuid) js_data['plan_id'] = pl.id else: # TODO(asalkeld) we are not hosting the plan so # download the plan and insert it into our db. raise exception.BadRequest( reason=_('The plan was not hosted in solum')) if js_data.get('plan_id') is None: raise exception.BadRequest( reason=_('The plan was not given or could not be found')) handler = assembly_handler.AssemblyHandler( pecan.request.security_context) return assembly.Assembly.from_db_model(handler.create(js_data), pecan.request.host_url)
def post(self, data): """Create a new app.""" request.check_request_for_https() if not data: raise exception.BadRequest(reason='No data.') self._validate(data) handler = app_handler.AppHandler(pecan.request.security_context) app_data = data.as_dict(app.App) new_app = handler.create(app_data) created_app = app.App.from_db_model(new_app, pecan.request.host_url) return created_app
def create(self, data, commit_sha, status_url, du_id): """Create a new workflow.""" db_obj = objects.registry.Workflow() db_obj.id = uuidutils.generate_uuid() db_obj.user_id = self.context.user db_obj.project_id = self.context.tenant db_obj.deleted = False db_obj.app_id = data['app_id'] db_obj.source = data['source'] db_obj.config = data['config'] db_obj.actions = data['actions'] now = datetime.datetime.utcnow() db_obj.created_at = now db_obj.updated_at = now app_obj = objects.registry.App.get_by_id(self.context, db_obj.app_id) if str(app_obj.languagepack).lower() == 'false' and not du_id: msg = ("App {app} registered without a languagepack and no " "du id specified. Either register the app with" " a languagepack, or if there is already a pre" "built du for the app, specify its id with --du-id" " command line flag.") msg = msg.format(app=app_obj.name) raise exception.BadRequest(reason=msg) self._update_app_scale_config(app_obj, data) plan, assem = PlanAssemblyAdapter(self.context, db_obj, app_obj).create_dummies() db_obj.assembly = assem.id workflow.Workflow.insert(self.context, db_obj) self._execute_workflow_actions(db_obj, app_obj, assem, commit_sha=commit_sha, status_url=status_url, du_id=du_id) # TODO(devkulkarni): Update status of actions return db_obj
def post(self): """Create a new plan.""" if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest(reason="No data.") handler = plan_handler.PlanHandler(pecan.request.security_context) if (pecan.request.content_type is not None and 'yaml' in pecan.request.content_type): data = init_yml_plan_by_version() created_plan = yamlutils.dump(yaml_content(handler.create( data.as_dict(objects.registry.Plan)))) else: data = init_json_plan_by_version() plan_wsme = plan.Plan.from_db_model(handler.create( data.as_dict(objects.registry.Plan)), pecan.request.host_url) created_plan = wsme_json.encode_result(plan_wsme, plan.Plan) pecan.response.status = 201 return created_plan
def post(self): """Create a new application. There are a number of ways to use this method to create a new application. See Section 6.11 of the CAMP v1.1 specification for an explanation of each. Use the Content-Type of request to determine what the client is trying to do. """ if pecan.request.content_type is None: raise exception.UnsupportedMediaType( name=pecan.request.content_type, method='POST') req_content_type = pecan.request.content_type # deploying by reference uses a JSON payload if req_content_type == 'application/json': payload = pecan.request.body if not payload or len(payload) < 1: raise exception.BadRequest(reason='empty request body') try: json_ref_doc = json.loads(payload) except ValueError as excp: raise exception.BadRequest(reason='JSON object is invalid. ' + six.text_type(excp)) if 'plan_uri' in json_ref_doc: plan_uri_str = json_ref_doc['plan_uri'] # figure out if the plan uri is relative or absolute plan_uri = urllib.parse.urlparse(plan_uri_str) uri_path = plan_uri.path if not plan_uri.netloc: # should be something like "../plans/<uuid>" or # "/camp/v1_1/plans/<uuid> (include Solum plans) if (not uri_path.startswith('../plans/') and not uri_path.startswith('../../../v1/plans/') and not uri_path.startswith('/camp/v1_1/plans/') and not uri_path.startswith('/v1/plans/')): msg = 'plan_uri does not reference a plan resource' raise exception.BadRequest(reason=msg) plan_uuid = plan_uri.path.split('/')[-1] else: # We have an absolute URI. Try to figure out if it refers # to a plan on this Solum instance. Note the following code # does not support URI aliases. A request that contains # a 'plan_uri' with a network location that is different # than network location used to make this request but # which, nevertheless, still refers to this Solum instance # will experience a false negative. This code will treat # that plan as if it existed on another CAMP-compliant # server. if plan_uri_str.startswith(pecan.request.host_url): if (not uri_path.startswith('/camp/v1_1/plans/') and not uri_path.startswith('/v1/plans/')): msg = 'plan_uri does not reference a plan resource' raise exception.BadRequest(reason=msg) plan_uuid = plan_uri.path.split('/')[-1] else: # The plan exists on another server. # TODO(gpilz): support references to plans on other # servers raise exception.NotImplemented() # resolve the local plan by its uuid. this will raise a # ResourceNotFound exception if there is no plan with # this uuid phandler = plan_handler.PlanHandler( pecan.request.security_context) plan_obj = phandler.get(plan_uuid) elif 'pdp_uri' in json_ref_doc: # TODO(gpilz): support references to PDPs raise exception.NotImplemented() else: # must have either 'plan_uri' or 'pdp_uri' msg = 'JSON object must contain either plan_uri or pdp_uri' raise exception.BadRequest(reason=msg) else: # TODO(gpilz): support deploying an application by value raise exception.NotImplemented() # at this point we expect to have a reference to a plan database object # for the plan that will be used to create the application ahandler = assembly_handler.AssemblyHandler( pecan.request.security_context) assem_db_obj = ahandler.create_from_plan(plan_obj) assem_model = model.Assembly.from_db_model(assem_db_obj, pecan.request.host_url) pecan.response.status = 201 pecan.response.location = assem_model.uri return wsme_json.tojson(model.Assembly, assem_model)
def post(self, trigger_id): """Trigger a new event on Solum.""" commit_sha = '' status_url = None collab_url = None workflow = None try: query = query_dict(pecan.request.query_string) if 'workflow' in query: valid_stages = ['unittest', 'build', 'deploy'] workflow = query['workflow'].replace('+', ' ').split(' ') workflow = filter(lambda x: x in valid_stages, workflow) if not workflow: workflow = None body = json.loads(pecan.request.body) if ('sender' in body and 'url' in body['sender'] and 'api.github.com' in body['sender']['url']): if 'comment' in body: # Process a request for rebuilding phrase = body['comment']['body'] commenter = body['comment']['user']['login'] private_repo = body['repository']['private'] # An example of collab_url # https://api.github.com/repos/:user/:repo/collaborators{/collaborator} if not private_repo: # Only verify collaborator for public repos collab_url = ( body['repository']['collaborators_url'].format( **{'/collaborator': '/' + commenter})) if (phrase.strip('. ').lower() != CONF.api.rebuild_phrase.lower()): err_msg = 'Rebuild phrase does not match' raise exception.RequestForbidden(reason=err_msg) else: commit_sha = body['comment']['commit_id'] elif 'pull_request' in body: # Process a GitHub pull request commit_sha = body['pull_request']['head']['sha'] else: raise exception.NotImplemented() # An example of Github statuses_url # https://api.github.com/repos/:user/:repo/statuses/{sha} if commit_sha: status_url = body['repository']['statuses_url'].format( sha=commit_sha) else: # Request NOT from a Github repo raise exception.NotImplemented() except Exception as exc: if isinstance(exc, exception.SolumException): raise info_msg = "Expected fields not found in request body." LOG.info(info_msg) raise exception.BadRequest(reason=info_msg) try: handler = app_handler.AppHandler(None) handler.trigger_workflow(trigger_id, commit_sha, status_url, collab_url, workflow=workflow) except exception.ResourceNotFound as e: LOG.error("Incorrect trigger url.") raise e pecan.response.status = 202
def post(self): """Create a new CAMP-style plan.""" if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest # check to make sure the request has the right Content-Type if (pecan.request.content_type is None or pecan.request.content_type != 'application/x-yaml'): raise exception.UnsupportedMediaType( name=pecan.request.content_type, method='POST') try: yaml_input_plan = yamlutils.load(pecan.request.body) except ValueError as excp: raise exception.BadRequest(reason='Plan is invalid. ' + six.text_type(excp)) camp_version = yaml_input_plan.get('camp_version') if camp_version is None: raise exception.BadRequest( reason='camp_version attribute is missing from submitted Plan') elif camp_version != 'CAMP 1.1': raise exception.BadRequest(reason=UNSUP_VER_ERR % camp_version) # Use Solum's handler as the point of commonality. We can do this # because Solum stores plans in the DB in their JSON form. handler = (plan_handler. PlanHandler(pecan.request.security_context)) model_plan = model.Plan(**yaml_input_plan) # Move any inline Service Specifications to the "services" section. # This avoids an issue where WSME can't properly handle multi-typed # attributes (e.g. 'fulfillment'). It also smoothes out the primary # difference between CAMP plans and Solum plans, namely that Solum # plans don't have inline Service Specifications. for art in model_plan.artifacts: if art.requirements != wsme.Unset: for req in art.requirements: if (req.fulfillment != wsme.Unset and isinstance(req.fulfillment, model.ServiceSpecification)): s_spec = req.fulfillment # if the inline service spec doesn't have an id # generate one if s_spec.id == wsme.Unset: s_spec.id = uuidutils.generate_uuid() # move the inline service spec to the 'services' # section if model_plan.services == wsme.Unset: model_plan.services = [s_spec] else: model_plan.services.append(s_spec) # set the fulfillment to the service spec id req.fulfillment = "id:%s" % s_spec.id db_obj = handler.create(clean_plan(wjson.tojson(model.Plan, model_plan))) plan_dict = fluff_plan(db_obj.refined_content(), db_obj.uuid) pecan.response.status = 201 pecan.response.location = plan_dict['uri'] return plan_dict
def error_func(): raise exception.BadRequest(**error_args)
def _validate(self, app_data): if not app_data.languagepack: raise exception.BadRequest(reason="Languagepack not specified.")