Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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')])
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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__())
Exemple #8
0
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
Exemple #9
0
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__())
Exemple #10
0
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)
Exemple #11
0
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
Exemple #12
0
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
Exemple #13
0
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))
Exemple #14
0
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
Exemple #15
0
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))
Exemple #16
0
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)
Exemple #17
0
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,
    })
Exemple #18
0
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,
    })
Exemple #19
0
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))
Exemple #20
0
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
Exemple #21
0
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)
Exemple #22
0
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')
Exemple #23
0
def _get_user(username):
    user = User.by_user_name(username)
    if user is None:
        raise NotFound404('User %s does not exist' % username)
    return user
Exemple #24
0
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()
Exemple #25
0
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)
Exemple #26
0
def _get_rs_by_id(id):
    try:
        return RecipeSet.by_id(id)
    except NoResultFound:
        raise NotFound404('Recipe set %s not found' % id)
Exemple #27
0
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
Exemple #28
0
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)
Exemple #29
0
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')
Exemple #30
0
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')