def _get_recipe_task_by_id(recipeid, taskid): try: task = RecipeTask.by_id(taskid) except NoResultFound: raise NotFound404('Recipe task not found') if recipeid != '_' and str(task.recipe.id) != recipeid: raise NotFound404('Recipe task not found') return task
def _get_recipe_task_result_by_id(recipeid, taskid, resultid): try: result = RecipeTaskResult.by_id(resultid) except NoResultFound: raise NotFound404('Recipe task result not found') if recipeid != '_' and str(result.recipetask.recipe.id) != recipeid: raise NotFound404('Recipe task result not found') if taskid != '_' and str(result.recipetask.id) != taskid: raise NotFound404('Recipe task result not found') return result
def delete_powertype(id): """ Deletes a power type by the given id. :param id: The id of the power type to be deleted. :status 204: Power type successfully deleted. :status 400: Power type is referenced by systems. :status 404: Power type can not be found. """ try: powertype = PowerType.by_id(id) except NoResultFound: raise NotFound404('Power type: %s does not exist' % id) systems_referenced = System.query.join( System.power).filter(Power.power_type == powertype).count() if systems_referenced: raise BadRequest400('Power type %s still referenced by %i systems' % (powertype.name, systems_referenced)) session.delete(powertype) activity = Activity(identity.current.user, u'HTTP', u'Deleted', u'PowerType', powertype.name) session.add(activity) return '', 204
def ipxe_script(uuid): try: resource = VirtResource.by_instance_id(uuid) except (NoResultFound, ValueError): raise NotFound404('Instance is not known to Beaker') if resource.kernel_options is None: # recipe.provision() hasn't been called yet # We need to handle this case because the VM is created and boots up # *before* we generate the kickstart etc raise ServiceUnavailable503('Recipe has not been provisioned yet') distro_tree = resource.recipe.distro_tree distro_tree_url = distro_tree.url_in_lab(resource.lab_controller, scheme=['http', 'ftp']) kernel = distro_tree.image_by_type(ImageType.kernel, KernelType.by_name(u'default')) if not kernel: raise BadRequest400('Kernel image not found for distro tree %s' % distro_tree.id) initrd = resource.recipe.distro_tree.image_by_type( ImageType.initrd, KernelType.by_name(u'default')) if not initrd: raise BadRequest400('Initrd image not found for distro tree %s' % distro_tree.id) kernel_url = urlparse.urljoin(distro_tree_url, kernel.path) initrd_url = urlparse.urljoin(distro_tree_url, initrd.path) kernel_options = resource.kernel_options + ' netboot_method=ipxe' return ('#!ipxe\nkernel %s %s\ninitrd %s\nboot\n' % (kernel_url, kernel_options, initrd_url), 200, [('Content-Type', 'text/plain')])
def update_task(task_id): """ Updates a task - only handles disabling at this time. :param task_id: The task id to update/disable :jsonparam bool disabled: Whether the task should be disabled. :status 200: Task was successfully updated/disabled :status 404: Task was not found (to be disabled) """ try: task = Task.by_id(task_id) except DatabaseLookupError as e: # This should be NotFound404 but due to still using cherrypy # 404's are handled there which then will then do a GET /tasks/id # which will resolve correctly, which isn't desired raise NotFound404('Task %s does not exist' % task_id) data = read_json_request(request) if data: with convert_internal_errors(): if data.get('disabled', False) and task.valid: task.disable() response = jsonify(task.to_dict()) return response
def add_submission_delegate(username): """ Adds a submission delegate for a user account. Submission delegates are other users who are allowed to submit jobs on behalf of this user. :param username: The user's username. :jsonparam string user_name: The submission delegate's username. """ user = User.by_user_name(username) if user is None: raise NotFound404('User %s does not exist' % username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) data = read_json_request(request) if 'user_name' not in data: raise BadRequest400( 'Missing "user_name" key to specify submission delegate') submission_delegate = User.by_user_name(data['user_name']) if submission_delegate is None: raise BadRequest400('Submission delegate %s does not exist' % data['user_name']) try: user.add_submission_delegate(submission_delegate, service=u'HTTP') except NoChangeException as e: raise Conflict409(unicode(e)) return 'Added', 201
def add_ssh_public_key(username): """ Adds a new SSH public key for the given user account. Accepts mimetype:`text/plain` request bodies containing the SSH public key in the conventional OpenSSH format: <keytype> <key> <ident>. :param username: The user's username. """ user = User.by_user_name(username) if user is None: raise NotFound404('User %s does not exist' % username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) if request.mimetype != 'text/plain': raise UnsupportedMediaType415( 'Request content type must be text/plain') with convert_internal_errors(): keytext = request.data.strip() if '\n' in keytext: raise ValueError('SSH public keys may not contain newlines') elements = keytext.split(None, 2) if len(elements) != 3: raise ValueError('Invalid SSH public key') key = SSHPubKey(*elements) user.sshpubkeys.append(key) session.flush() # to populate id return jsonify(key.__json__())
def exclude_user(group_name): """ Exclude a user from an inverted group. Then the user will not have the group membership. :param group_name: Group's name. :jsonparam string user_name: User's username. """ u = identity.current.user data = read_json_request(request) group = _get_group_by_name(group_name, lockmode='update') if not group.can_modify_membership(identity.current.user): raise Forbidden403('Cannot edit membership of group %s' % group_name) if group.membership_type == GroupMembershipType.normal: raise NotFound404('Normal group %s do not have excluded users' % group_name) if 'user_name' not in data: raise BadRequest400('User not specified') user = _get_user_by_username(data['user_name']) if user in group.users: if not group.can_exclude_member(u, user.id): raise Forbidden403('Cannot exclude user %s from group %s' % (user, group_name)) with convert_internal_errors(): group.exclude_user(user, agent=identity.current.user) else: raise Conflict409('User %s is already excluded from group %s' % (user.user_name, group_name)) return '', 204
def update_recipeset_by_taskspec(taskspec): """ Updates the attributes of a recipe set identified by a taskspec. The valid type of a taskspec is either J(job) or RS(recipe-set). If a taskspec format is J:<id>, all the recipe sets in this job will be updated. The request must be :mimetype:`application/json`. :param taskspec: A taskspec argument that identifies a job or recipe set. :jsonparam string priority: Priority for the recipe set. Must be one of 'Low', 'Medium', 'Normal', 'High', or 'Urgent'. This can only be changed while a recipe set is still queued. Job owners can generally only *decrease* the priority of their recipe set, queue admins can increase it. :jsonparam boolean waived: If true, the recipe set will be waived, regardless of its result. """ if not taskspec.startswith(('J', 'RS')): raise BadRequest400('Taskspec type must be one of [J, RS]') try: obj = TaskBase.get_by_t_id(taskspec) except BeakerException as exc: raise NotFound404(unicode(exc)) data = read_json_request(request) if isinstance(obj, Job): for rs in obj.recipesets: _update_recipeset(rs, data) elif isinstance(obj, RecipeSet): _update_recipeset(obj, data) return jsonify(obj.__json__())
def get_task(task_id): # Dummy handler to fall back to CherryPy # so that other methods such as PATCH/DELETE work. # --- # Because Flask defined 404 has priority before CherryPy's 404, # message defined in here will be presented to user when CherryPy's 404 is raised. raise NotFound404('No such task with ID: %s' % task_id)
def find_labcontroller_or_raise404(fqdn): """Returns a lab controller object or raises a NotFound404 error if the lab controller does not exist in the database.""" try: labcontroller = LabController.by_name(fqdn) except NoResultFound: raise NotFound404('Lab controller %s does not exist' % fqdn) return labcontroller
def delete_ssh_public_key(username, id): """ Deletes a public SSH public key belonging to the given user account. :param username: The user's username. :param id: Database id of the SSH public key to be deleted. """ user = User.by_user_name(username) if user is None: raise NotFound404('User %s does not exist' % username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) matching_keys = [k for k in user.sshpubkeys if k.id == id] if not matching_keys: raise NotFound404('SSH public key id %s does not belong to user %s' % (id, user)) key = matching_keys[0] session.delete(key) return '', 204
def get_recipe_log(id, path): """ Redirects to the actual storage location for the requested recipe log. :param id: Recipe's id. :param path: Log path. """ recipe = _get_recipe_by_id(id) for log in recipe.logs: if log.combined_path == path: return flask_redirect(log.absolute_url, code=307) return NotFound404('Recipe log %s for recipe %s not found' % (path, id))
def delete_submission_delegate(username): """ Deletes a public SSH public key belonging to the given user account. :param username: The user's username. :param id: Database id of the SSH public key to be deleted. """ user = User.by_user_name(username) #XXX lockmode='update' if user is None: raise NotFound404('User %s does not exist' % username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) if 'user_name' not in request.args: raise MethodNotAllowed405 submission_delegate = User.by_user_name(request.args['user_name']) if submission_delegate is None: raise NotFound404('Submission delegate %s does not exist' % request.args['user_name']) if not submission_delegate.is_delegate_for(user): raise Conflict409('User %s is not a submission delegate for %s' % (submission_delegate, user)) user.remove_submission_delegate(submission_delegate) return '', 204
def get_recipe_task_log(recipeid, taskid, path): """ Redirects to the actual storage location for the requested task log. :param recipeid: Recipe id. :param taskid: Recipe task id. :param path: Log path. """ task = _get_recipe_task_by_id(recipeid, taskid) for log in task.logs: if log.combined_path == path: return redirect(log.absolute_url, code=307) return NotFound404('Task log %s for recipe %s task %s not found' % (path, recipeid, taskid))
def extend_watchdog_by_fqdn(fqdn): """ Extend the watchdog for a recipe that is running on the system. :param fqdn: The system's fully-qualified domain name. :jsonparam string kill_time: Time in seconds to extend the watchdog by. """ try: recipe = Recipe.query.join(Recipe.watchdog, Recipe.resource)\ .filter(RecipeResource.fqdn == fqdn)\ .filter(Recipe.status == TaskStatus.running).one() except NoResultFound: raise NotFound404('Cannot find any recipe running on %s' % fqdn) data = read_json_request(request) return _extend_watchdog(recipe.id, data)
def get_user(username): """ Returns details about a Beaker user account. :param username: The user's username. """ user = User.by_user_name(username) if user is None: raise NotFound404('User %s does not exist' % username) attributes = user_full_json(user) if request_wants_json(): return jsonify(attributes) return render_tg_template('bkr.server.templates.user_edit_form', { 'attributes': attributes, 'url': user.href, })
def get_recipe(id): """ Provides detailed information about a recipe in JSON format. :param id: Recipe's id. """ recipe = _get_recipe_by_id(id) if request_wants_json(): return jsonify(recipe.to_json(include_recipeset=True)) if identity.current.user and identity.current.user.use_old_job_page: return NotFound404('Fall back to old recipe page') if identity.current.user: recipe.set_reviewed_state(identity.current.user, True) return render_tg_template('bkr.server.templates.recipe', { 'title': recipe.t_id, 'recipe': recipe, })
def get_recipe_task_log(recipeid, taskid, path): """ Redirects to the actual storage location for the requested task log. :param recipeid: Recipe id. :param taskid: Recipe task id. :param path: Log path. """ task = _get_recipe_task_by_id(recipeid, taskid) for log in task.logs: if log.combined_path == path: return redirect(log.absolute_url, code=307) # If the caller requested TESTOUT.log but only taskout.log exists, give them that instead. if path == 'TESTOUT.log': for log in task.logs: if log.combined_path == 'taskout.log': return redirect(log.absolute_url, code=307) return NotFound404('Task log %s for recipe %s task %s not found' % (path, recipeid, taskid))
def delete_submission_delegate(username): """ Deletes a submission delegate for a user account. :param username: The user's username. :query string user_name: The submission delegate's username. """ user = _get_user(username) if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) if 'user_name' not in request.args: raise MethodNotAllowed405 submission_delegate = User.by_user_name(request.args['user_name']) if submission_delegate is None: raise NotFound404('Submission delegate %s does not exist' % request.args['user_name']) if not submission_delegate.is_delegate_for(user): raise Conflict409('User %s is not a submission delegate for %s' % (submission_delegate, user)) user.remove_submission_delegate(submission_delegate) return '', 204
def extend_watchdog_by_taskspec(taskspec): """ Extend the watchdog for a recipe identified by a taskspec. The valid type of a taskspec is either R(recipe) or T(recipe-task). See :ref:`Specifying tasks <taskspec>` in :manpage:`bkr(1)`. :param taskspec: A taskspec argument that identifies a recipe or recipe task. :jsonparam string kill_time: Time in seconds to extend the watchdog by. """ if not taskspec.startswith(('R', 'T')): raise BadRequest400('Taskspec type must be one of [R, T]') try: obj = TaskBase.get_by_t_id(taskspec) except BeakerException as exc: raise NotFound404(unicode(exc)) if isinstance(obj, Recipe): recipe = obj else: recipe = obj.recipe data = read_json_request(request) return _extend_watchdog(recipe.id, data)
def _get_recipe_by_id(id): """Get recipe by id, reporting HTTP 404 if the recipe is not found""" try: return Recipe.by_id(id) except NoResultFound: raise NotFound404('Recipe not found')
def _get_user(username): user = User.by_user_name(username) if user is None: raise NotFound404('User %s does not exist' % username) return user
def old_get_group(): if 'id' in request.args: user = User.by_id(request.args['id']) if user is not None: return flask_redirect(absolute_url(user.href)) raise NotFound404()
def _get_pool_by_name(pool_name, lockmode=False): """Get system pool by name, reporting HTTP 404 if the system pool is not found""" try: return SystemPool.by_name(pool_name, lockmode) except NoResultFound: raise NotFound404('System pool %s does not exist' % pool_name)
def _get_rs_by_id(id): try: return RecipeSet.by_id(id) except NoResultFound: raise NotFound404('Recipe set %s not found' % id)
def update_user(username): """ Updates a Beaker user account. :param username: The user's username. :jsonparam string user_name: New username. If the username is changed, the response will include a Location header referring to the new URL for newly renamed user resource. :jsonparam string display_name: New display name. :jsonparam string email_address: New email address. :jsonparam string password: New password. Only valid when Beaker is not using external authentication for this account. :jsonparam string root_password: Root password to be set on systems provisioned by Beaker. :jsonparam boolean use_old_job_page: True if the user has opted to use the old, deprecated pre-Beaker-23 job page. :jsonparam boolean disabled: Whether the user should be temporarily disabled. Disabled users cannot log in or submit jobs, and any running jobs are cancelled when their account is disabled. :jsonparam string removed: Pass the string 'now' to remove a user account. Pass null to un-remove a removed user account. """ user = User.by_user_name(username) if user is None: raise NotFound404('User %s does not exist' % username) data = read_json_request(request) renamed = False if data.get('password') is not None: if not user.can_change_password(identity.current.user): raise Forbidden403('Cannot change password for user %s' % user) with convert_internal_errors(): user.password = data.pop('password') if data: if not user.can_edit(identity.current.user): raise Forbidden403('Cannot edit user %s' % user) with convert_internal_errors(): if 'user_name' in data: new_user_name = data['user_name'].strip() if user.user_name != new_user_name: if not user.can_rename(identity.current.user): raise Forbidden403('Cannot rename user %s to %s' % (user, new_user_name)) if User.by_user_name(new_user_name) is not None: raise Conflict409('User %s already exists' % new_user_name) user.user_name = new_user_name renamed = True if 'display_name' in data: user.display_name = data['display_name'].strip() if 'email_address' in data: user.email_address = data['email_address'].strip() if 'root_password' in data: new_root_password = data['root_password'] if user.root_password != new_root_password: user.root_password = new_root_password if 'use_old_job_page' in data: user.use_old_job_page = data['use_old_job_page'] if 'disabled' in data: user.disabled = data['disabled'] if user.disabled: _disable(user, method=u'HTTP') if 'removed' in data: if data['removed'] is None: _unremove(user) elif data['removed'] == 'now': _remove(user, method=u'HTTP') else: raise ValueError('"removed" value must be "now" or null') response = jsonify(user_full_json(user)) if renamed: response.headers.add('Location', absolute_url(user.href)) return response
def _get_group_by_name(group_name, lockmode=False): """Get group by name, reporting HTTP 404 if the group is not found""" try: return Group.by_name(group_name, lockmode) except NoResultFound: raise NotFound404('Group %s does not exist' % group_name)
def get_task(task_id): # Dummy handler to fall back to CherryPy # so that other methods such as PATCH/DELETE work. raise NotFound404('Fall back to CherryPy')
def _get_system_by_FQDN(fqdn): """Get system by FQDN, reporting HTTP 404 if the system is not found""" try: return System.by_fqdn(fqdn, identity.current.user) except NoResultFound: raise NotFound404('System not found')