Beispiel #1
0
def get_metrics():
    # start_date: start date for period, in YYYY-mm-dd format (UTC)
    # end_date: end date for period, in YYYY-mm-dd format (UTC)
    # package_name (optional): return metrics for package_name
    start_date = request.args.get('start_date', None)
    end_date = request.args.get('end_date', None)
    package_name = request.args.get('package_name', None)

    if request.headers.get('Content-Type') == 'application/json':
        # This is the old, deprecated method of in-body parameters
        # We will keep it for backwards compatibility
        if start_date is None:
            start_date = request.json.get('start_date', None)
        if end_date is None:
            end_date = request.json.get('end_date', None)
        if package_name is None:
            package_name = request.json.get('package_name', None)

    if start_date is None or end_date is None:
        raise InvalidUsage('Missing parameters', status_code=400)

    # Convert dates to timestamp
    fmt = '%Y-%m-%d'
    try:
        start_timestamp = int(calendar.timegm(time.strptime(start_date, fmt)))
        end_timestamp = int(calendar.timegm(time.strptime(end_date, fmt)))
    except ValueError:
        raise InvalidUsage('Invalid date format, it must be YYYY-mm-dd',
                           status_code=400)

    # Find the commits count for each metric
    session = _get_db()
    commits = session.query(Commit).filter(
        Commit.status == 'SUCCESS',
        Commit.dt_build >= start_timestamp,
        Commit.dt_build < end_timestamp)

    if package_name:
        commits = commits.filter(
            Commit.project_name == package_name)

    successful_commits = commits.count()

    commits = session.query(Commit).filter(
        Commit.status == 'FAILED',
        Commit.dt_build >= start_timestamp,
        Commit.dt_build <= end_timestamp)

    if package_name:
        commits = commits.filter(
            Commit.project_name == package_name)

    failed_commits = commits.count()
    total_commits = successful_commits + failed_commits

    result = {'succeeded': successful_commits,
              'failed': failed_commits,
              'total': total_commits}
    return jsonify(result), 200
Beispiel #2
0
def repo_status():
    # commit_hash: commit hash
    # distro_hash: distro hash
    # extended_hash(optional): extended hash
    # success(optional): only report successful/unsuccessful votes

    commit_hash = request.args.get('commit_hash', None)
    distro_hash = request.args.get('distro_hash', None)
    extended_hash = request.args.get('extended_hash', None)
    success = request.args.get('success', None)

    if request.headers.get('Content-Type') == 'application/json':
        # This is the old, deprecated method of in-body parameters
        # We will keep it for backwards compatibility
        if commit_hash is None:
            commit_hash = request.json.get('commit_hash', None)
        if distro_hash is None:
            distro_hash = request.json.get('distro_hash', None)
        if extended_hash is None:
            extended_hash = request.json.get('extended_hash', None)
        if success is None:
            success = request.json.get('success', None)

    if (commit_hash is None or distro_hash is None):
        raise InvalidUsage('Missing parameters', status_code=400)

    if success is not None:
        success = bool(strtobool(success))

    # Find the commit id for commit_hash/distro_hash
    session = _get_db()
    commit = _get_commit(session, commit_hash, distro_hash, extended_hash)

    if commit is None:
        raise InvalidUsage('commit_hash+distro_hash+extended_hash combination'
                           ' not found', status_code=404)
    commit_id = commit.id

    # Now find every vote for this commit_hash/distro_hash combination
    votes = session.query(CIVote).filter(CIVote.commit_id == commit_id)
    if success is not None:
        votes = votes.filter(CIVote.ci_vote == int(success))

    # And format the output
    data = []
    for vote in votes:
        d = {'timestamp': vote.timestamp,
             'commit_hash': commit_hash,
             'distro_hash': distro_hash,
             'extended_hash': commit.extended_hash,
             'job_id': vote.ci_name,
             'success': bool(vote.ci_vote),
             'in_progress': vote.ci_in_progress,
             'url': vote.ci_url,
             'notes': vote.notes,
             'user': vote.user,
             'component': vote.component}
        data.append(d)
    return jsonify(data)
Beispiel #3
0
def report_result():
    # job_id: name of CI
    # commit_hash: commit hash
    # distro_hash: distro hash
    # url: URL where more information can be found
    # timestamp: CI execution timestamp
    # success: boolean
    # notes(optional): notes

    if request.headers['Content-Type'] != 'application/json':
        raise InvalidUsage('Unsupported Media Type, use JSON', status_code=415)

    try:
        commit_hash = request.json['commit_hash']
        distro_hash = request.json['distro_hash']
        timestamp = request.json['timestamp']
        job_id = request.json['job_id']
        success = request.json['success']
        url = request.json['url']
    except KeyError:
        raise InvalidUsage('Missing parameters', status_code=400)

    notes = request.json.get('notes', '')

    session = getSession(app.config['DB_PATH'])
    commit = session.query(Commit).filter(
        Commit.status == 'SUCCESS', Commit.commit_hash == commit_hash,
        Commit.distro_hash == distro_hash).first()
    if commit is None:
        raise InvalidUsage('commit_hash+distro_hash combination not found',
                           status_code=404)

    commit_id = commit.id

    vote = CIVote(commit_id=commit_id,
                  ci_name=job_id,
                  ci_url=url,
                  ci_vote=bool(strtobool(success)),
                  ci_in_progress=False,
                  timestamp=int(timestamp),
                  notes=notes,
                  user=auth.username())
    session.add(vote)
    session.commit()

    result = {
        'commit_hash': commit_hash,
        'distro_hash': distro_hash,
        'timestamp': timestamp,
        'job_id': job_id,
        'success': bool(strtobool(success)),
        'in_progress': False,
        'url': url,
        'notes': notes,
        'user': auth.username()
    }
    closeSession(session)
    return jsonify(result), 201
Beispiel #4
0
def get_metrics():
    # start_date: start date for period, in YYYY-mm-dd format (UTC)
    # end_date: end date for period, in YYYY-mm-dd format (UTC)
    # package_name (optional): return metrics for package_name
    if request.headers['Content-Type'] != 'application/json':
        raise InvalidUsage('Unsupported Media Type, use JSON', status_code=415)

    try:
        start_date = request.json['start_date']
        end_date = request.json['end_date']
    except KeyError:
        raise InvalidUsage('Missing parameters', status_code=400)
    package_name = request.json.get('package_name', None)

    # Convert dates to timestamp
    fmt = '%Y-%m-%d'
    try:
        start_timestamp = int(calendar.timegm(time.strptime(start_date, fmt)))
        end_timestamp = int(calendar.timegm(time.strptime(end_date, fmt)))
    except ValueError:
        raise InvalidUsage('Invalid date format, it must be YYYY-mm-dd',
                           status_code=400)

    # Find the commits count for each metric
    session = getSession(app.config['DB_PATH'])
    commits = session.query(Commit).filter(Commit.status == 'SUCCESS',
                                           Commit.dt_build >= start_timestamp,
                                           Commit.dt_build < end_timestamp)

    if package_name:
        commits = commits.filter(Commit.project_name == package_name)

    successful_commits = commits.count()

    commits = session.query(Commit).filter(Commit.status == 'FAILED',
                                           Commit.dt_build >= start_timestamp,
                                           Commit.dt_build <= end_timestamp)

    if package_name:
        commits = commits.filter(Commit.project_name == package_name)

    failed_commits = commits.count()
    total_commits = successful_commits + failed_commits

    result = {
        'succeeded': successful_commits,
        'failed': failed_commits,
        'total': total_commits
    }
    closeSession(session)
    return jsonify(result), 200
Beispiel #5
0
def repo_status():
    # commit_hash: commit hash
    # distro_hash: distro hash
    # success(optional): only report successful/unsuccessful votes
    if request.headers['Content-Type'] != 'application/json':
        raise InvalidUsage('Unsupported Media Type, use JSON', status_code=415)

    commit_hash = request.json.get('commit_hash', None)
    distro_hash = request.json.get('distro_hash', None)
    success = request.json.get('success', None)
    if (commit_hash is None or distro_hash is None):
        raise InvalidUsage('Missing parameters', status_code=400)

    if success is not None:
        success = bool(strtobool(success))

    # Find the commit id for commit_hash/distro_hash
    session = getSession(app.config['DB_PATH'])
    commit = session.query(Commit).filter(
        Commit.status == 'SUCCESS', Commit.commit_hash == commit_hash,
        Commit.distro_hash == distro_hash).first()
    if commit is None:
        raise InvalidUsage('commit_hash+distro_hash combination not found',
                           status_code=404)
    commit_id = commit.id

    # Now find every vote for this commit_hash/distro_hash combination
    votes = session.query(CIVote).filter(CIVote.commit_id == commit_id)
    if success is not None:
        votes = votes.filter(CIVote.ci_vote == int(success))

    # And format the output
    data = []
    for vote in votes:
        d = {
            'timestamp': vote.timestamp,
            'commit_hash': commit_hash,
            'distro_hash': distro_hash,
            'job_id': vote.ci_name,
            'success': bool(vote.ci_vote),
            'in_progress': vote.ci_in_progress,
            'url': vote.ci_url,
            'notes': vote.notes,
            'user': vote.user
        }
        data.append(d)
    closeSession(session)
    return jsonify(data)
Beispiel #6
0
def get_civotes_agg_detail():
    ref_hash = request.args.get('ref_hash', None)
    ci_name = request.args.get('ci_name', None)
    success = request.args.get('success', None)
    offset = request.args.get('offset', 0)

    session = _get_db()
    votes = session.query(CIVote_Aggregate)

    if ref_hash:
        votes = votes.from_self().filter(CIVote_Aggregate.ref_hash == ref_hash)
    elif ci_name:
        votes = votes.filter(CIVote_Aggregate.ci_name == ci_name)
    else:
        raise InvalidUsage("Please specify either ref_hash or "
                           "ci_name as parameters.", status_code=400)

    votes = votes.offset(offset).limit(pagination_limit)

    if success is not None:
        votes = votes.from_self().filter(
            CIVote_Aggregate.ci_vote == bool(strtobool(success)))

    votelist = votes.all()
    count = votes.count()

    config_options = _get_config_options(app.config['CONFIG_FILE'])

    return render_template('votes_agg.j2',
                           target=config_options.target,
                           votes=votelist,
                           count=count,
                           limit=pagination_limit)
Beispiel #7
0
def remote_import():
    # repo_url: repository URL to import from
    try:
        repo_url = request.json['repo_url']
    except KeyError:
        raise InvalidUsage('Missing parameters', status_code=400)

    try:
        import_commit(repo_url, app.config['CONFIG_FILE'],
                      db_connection=app.config['DB_PATH'])
    except Exception as e:
        raise InvalidUsage("Remote import failed with error: %s" %
                           e, status_code=500)

    result = {'repo_url': repo_url}
    return jsonify(result), 201
Beispiel #8
0
def getVote(session, timestamp, success=None, job_id=None, component=None,
            fallback=True):
    votes = session.query(CIVote)
    votes = votes.filter(CIVote.timestamp > timestamp)
    # Initially we want to get any tested repo, excluding consistent repos
    votes = votes.filter(CIVote.ci_name != 'consistent')
    if success is not None:
        votes = votes.filter(CIVote.ci_vote == int(success))
    if job_id is not None:
        votes = votes.filter(CIVote.ci_name == job_id)
    if component is not None:
        votes = votes.filter(CIVote.component == component)
    vote = votes.order_by(desc(CIVote.timestamp)).first()

    if vote is None and not fallback:
        # This is the sequential use case. We do not want to find any vote
        # for a different CI
        raise InvalidUsage('No vote found', status_code=404)

    if vote is None and job_id is not None:
        # Second chance: no votes found for job_id. Let's find any real CI
        # vote, other than 'consistent'
        votes = session.query(CIVote).filter(CIVote.timestamp > timestamp)
        if success is not None:
            votes = votes.filter(CIVote.ci_vote == success)
        votes.filter(CIVote.ci_name != 'consistent')
        vote = votes.order_by(desc(CIVote.timestamp)).first()

    if vote is None:
        # No votes found, let's try to find one for consistent
        votes = session.query(CIVote).filter(CIVote.timestamp > timestamp)
        if success is not None:
            votes = votes.filter(CIVote.ci_vote == success)
        if component is not None:
            votes = votes.filter(CIVote.component == component)
        votes.filter(CIVote.ci_name == 'consistent')
        vote = votes.order_by(desc(CIVote.timestamp)).first()

    if vote is None:
        # No Votes found at all
        raise InvalidUsage('No vote found', status_code=404)

    return vote
Beispiel #9
0
def remote_import():
    # repo_url: repository URL to import from
    if request.headers['Content-Type'] != 'application/json':
        raise InvalidUsage('Unsupported Media Type, use JSON', status_code=415)

    try:
        repo_url = request.json['repo_url']
    except KeyError:
        raise InvalidUsage('Missing parameters', status_code=400)

    try:
        import_commit(repo_url,
                      app.config['CONFIG_FILE'],
                      db_connection=app.config['DB_PATH'])
    except Exception as e:
        raise InvalidUsage("Remote import failed with error: %s" % e,
                           status_code=500)

    result = {'repo_url': repo_url}
    return jsonify(result), 201
Beispiel #10
0
def get_civotes_detail():
    commit_hash = request.args.get('commit_hash', None)
    distro_hash = request.args.get('distro_hash', None)
    ci_name = request.args.get('ci_name', None)
    success = request.args.get('success', None)
    offset = request.args.get('offset', 0)

    session = getSession(app.config['DB_PATH'])
    votes = session.query(CIVote)
    votes = votes.filter(CIVote.ci_name != 'consistent')

    if commit_hash and distro_hash:
        commit = session.query(Commit).filter(
            Commit.status == 'SUCCESS', Commit.commit_hash == commit_hash,
            Commit.distro_hash == distro_hash).first()
        votes = votes.from_self().filter(CIVote.commit_id == commit.id)
    elif ci_name:
        votes = votes.filter(CIVote.ci_name == ci_name)
    else:
        raise InvalidUsage(
            "Please specify either commit_hash+distro_hash or "
            "ci_name as parameters.",
            status_code=400)

    votes = votes.offset(offset).limit(pagination_limit)

    if success is not None:
        votes = votes.from_self().filter(
            CIVote.ci_vote == bool(strtobool(success)))

    votelist = votes.all()
    count = votes.count()

    for i in range(len(votelist)):
        commit = getCommits(
            session,
            limit=0).filter(Commit.id == votelist[i].commit_id).first()
        votelist[i].commit_hash = commit.commit_hash
        votelist[i].distro_hash = commit.distro_hash
        votelist[i].distro_hash_short = commit.distro_hash[:8]

    closeSession(session)
    config_options = _get_config_options(app.config['CONFIG_FILE'])

    return render_template('votes.j2',
                           target=config_options.target,
                           votes=votelist,
                           count=count,
                           limit=pagination_limit)
Beispiel #11
0
def agg_status():
    # aggregate_hash: aggregate hash
    # success(optional): only report successful/unsuccessful votes
    agg_hash = request.args.get('aggregate_hash', None)
    success = request.args.get('success', None)

    if request.headers.get('Content-Type') == 'application/json':
        # This is the old, deprecated method of in-body parameters
        # We will keep it for backwards compatibility
        if agg_hash is None:
            agg_hash = request.json.get('aggregate_hash', None)
        if success is None:
            success = request.json.get('success', None)

    if agg_hash is None:
        raise InvalidUsage('Missing parameters', status_code=400)

    if success is not None:
        success = bool(strtobool(success))

    # Find the aggregates
    session = _get_db()
    votes = session.query(CIVote_Aggregate)
    votes = votes.filter(CIVote_Aggregate.ref_hash == agg_hash)
    if success is not None:
        votes = votes.filter(CIVote_Aggregate.ci_vote == int(success))

    # And format the output
    data = []
    for vote in votes:
        d = {'timestamp': vote.timestamp,
             'aggregate_hash': agg_hash,
             'job_id': vote.ci_name,
             'success': bool(vote.ci_vote),
             'in_progress': vote.ci_in_progress,
             'url': vote.ci_url,
             'notes': vote.notes,
             'user': vote.user}
        data.append(d)
    return jsonify(data)
Beispiel #12
0
 def decorated_function(*args, **kwargs):
     if request.headers.get('Content-Type') != 'application/json':
         raise InvalidUsage('Unsupported Media Type, use JSON',
                            status_code=415)
     return f(*args, **kwargs)
Beispiel #13
0
def promote():
    # commit_hash: commit hash
    # distro_hash: distro hash
    # extended_hash (optional): extended hash
    # promote_name: symlink name
    try:
        commit_hash = request.json['commit_hash']
        distro_hash = request.json['distro_hash']
        promote_name = request.json['promote_name']
    except KeyError:
        raise InvalidUsage('Missing parameters', status_code=400)

    extended_hash = request.json.get('extended_hash', None)

    # Check for invalid promote names
    if (promote_name == 'consistent' or promote_name == 'current'):
        raise InvalidUsage('Invalid promote_name %s' % promote_name,
                           status_code=403)

    config_options = _get_config_options(app.config['CONFIG_FILE'])

    session = _get_db()
    commit = _get_commit(session, commit_hash, distro_hash, extended_hash)
    if commit is None:
        raise InvalidUsage('commit_hash+distro_hash+extended_hash combination'
                           ' not found', status_code=404)

    # If the commit has been purged, do not move on
    if commit.flags & FLAG_PURGED:
        raise InvalidUsage('commit_hash+distro_hash+extended_hash has been '
                           'purged, cannot promote it', status_code=410)

    if config_options.use_components:
        base_directory = os.path.join(app.config['REPO_PATH'], "component/%s" %
                                      commit.component)
    else:
        base_directory = app.config['REPO_PATH']

    target_link = os.path.join(base_directory, promote_name)
    # Check for invalid target links, like ../promotename
    target_dir = os.path.dirname(os.path.abspath(target_link))
    if not os.path.samefile(target_dir, base_directory):
        raise InvalidUsage('Invalid promote_name %s' % promote_name,
                           status_code=403)

    # We should create a relative symlink
    yumrepodir = commit.getshardedcommitdir()
    if config_options.use_components:
        # In this case, the relative path should not include
        # the component part
        yumrepodir = yumrepodir.replace("component/%s/" % commit.component, '')

    # Remove symlink if it exists, so we can create it again
    if os.path.lexists(os.path.abspath(target_link)):
        os.remove(target_link)
    try:
        os.symlink(yumrepodir, target_link)
    except Exception as e:
        raise InvalidUsage("Symlink creation failed with error: %s" %
                           e, status_code=500)

    # Once the updated symlink is created, if we are using components
    # we need to update the top-level repo file
    repo_checksum = None
    if config_options.use_components:
        datadir = os.path.realpath(config_options.datadir)
        repo_checksum = aggregate_repo_files(promote_name, datadir, session,
                                             config_options.reponame,
                                             hashed_dir=True)

    timestamp = time.mktime(datetime.now().timetuple())
    promotion = Promotion(commit_id=commit.id, promotion_name=promote_name,
                          timestamp=timestamp, user=auth.username(),
                          component=commit.component,
                          aggregate_hash=repo_checksum)

    session.add(promotion)
    session.commit()

    repo_hash = _repo_hash(commit)
    repo_url = "%s/%s" % (config_options.baseurl, commit.getshardedcommitdir())

    result = {'commit_hash': commit_hash,
              'distro_hash': distro_hash,
              'extended_hash': commit.extended_hash,
              'repo_hash': repo_hash,
              'repo_url': repo_url,
              'promote_name': promote_name,
              'component': commit.component,
              'timestamp': timestamp,
              'user': auth.username(),
              'aggregate_hash': repo_checksum}
    return jsonify(result), 201
Beispiel #14
0
def report_result():
    # job_id: name of CI
    # commit_hash: commit hash
    # distro_hash: distro hash
    # extended_hash(optional): extended hash
    # aggregate_hash: hash of aggregate.
    # url: URL where more information can be found
    # timestamp: CI execution timestamp
    # success: boolean
    # notes(optional): notes
    # Either commit_hash+distro_hash or aggregate_hash must be provided
    try:
        timestamp = request.json['timestamp']
        job_id = request.json['job_id']
        success = request.json['success']
        url = request.json['url']
    except KeyError:
        raise InvalidUsage('Missing parameters', status_code=400)

    commit_hash = request.json.get('commit_hash', None)
    distro_hash = request.json.get('distro_hash', None)
    extended_hash = request.json.get('extended_hash', None)
    aggregate_hash = request.json.get('aggregate_hash', None)

    if not commit_hash and not distro_hash and not aggregate_hash:
        raise InvalidUsage('Missing parameters', status_code=400)

    if commit_hash and not distro_hash:
        raise InvalidUsage('If commit_hash is provided, distro_hash '
                           'must be provided too', status_code=400)

    if distro_hash and not commit_hash:
        raise InvalidUsage('If distro_hash is provided, commit_hash '
                           'must be provided too', status_code=400)

    if (aggregate_hash and distro_hash) or (aggregate_hash and commit_hash):
        raise InvalidUsage('aggregate_hash and commit/distro_hash cannot be '
                           'combined', status_code=400)

    notes = request.json.get('notes', '')

    session = _get_db()
    # We have two paths here: one for votes on commit/distro/extended hash,
    # another for votes on aggregate_hash
    component = None
    if commit_hash:
        commit = _get_commit(session, commit_hash, distro_hash, extended_hash)
        if commit is None:
            raise InvalidUsage('commit_hash+distro_hash+extended_hash '
                               'combination not found', status_code=404)

        commit_id = commit.id
        out_ext_hash = commit.extended_hash
        component = commit.component
        vote = CIVote(commit_id=commit_id, ci_name=job_id, ci_url=url,
                      ci_vote=bool(strtobool(success)), ci_in_progress=False,
                      timestamp=int(timestamp), notes=notes,
                      user=auth.username(), component=component)
    else:
        out_ext_hash = None
        prom = session.query(Promotion).filter(
            Promotion.aggregate_hash == aggregate_hash).first()
        if prom is None:
            raise InvalidUsage('aggregate_hash not found',
                               status_code=400)

        vote = CIVote_Aggregate(ref_hash=aggregate_hash, ci_name=job_id,
                                ci_url=url, ci_vote=bool(strtobool(success)),
                                ci_in_progress=False, timestamp=int(timestamp),
                                notes=notes, user=auth.username())

    session.add(vote)
    session.commit()

    result = {'commit_hash': commit_hash,
              'distro_hash': distro_hash,
              'extended_hash': out_ext_hash,
              'aggregate_hash': aggregate_hash,
              'timestamp': timestamp,
              'job_id': job_id,
              'success': bool(strtobool(success)),
              'in_progress': False,
              'url': url,
              'notes': notes,
              'user': auth.username(),
              'component': component}
    return jsonify(result), 201
Beispiel #15
0
def last_tested_repo_POST():
    # max_age: Maximum age in hours, used as base for the search
    # success(optional): find repos with a successful/unsuccessful vote
    # job_id(optional); name of the CI that sent the vote
    # reporting_job_id: name of the CI that will test this repo
    # sequential_mode(optional): if set to true, change the search algorithm
    #                            to only use previous_job_id as CI name to
    #                            search for. Defaults to false
    # previous_job_id(optional): CI name to search for, if sequential_mode is
    #                            True
    # component(optional): only get votes for this component
    max_age = request.json.get('max_age', None)
    my_job_id = request.json.get('reporting_job_id', None)
    job_id = request.json.get('job_id', None)
    success = request.json.get('success', None)
    sequential_mode = request.json.get('sequential_mode', None)
    previous_job_id = request.json.get('previous_job_id', None)
    component = request.json.get('component', None)

    if success is not None:
        success = bool(strtobool(success))

    if sequential_mode is not None:
        sequential_mode = bool(strtobool(sequential_mode))

    if sequential_mode and previous_job_id is None:
        raise InvalidUsage('Missing parameter previous_job_id',
                           status_code=400)

    if (max_age is None or my_job_id is None):
        raise InvalidUsage('Missing parameters', status_code=400)

    # Calculate timestamp as now - max_age
    if int(max_age) == 0:
        timestamp = 0
    else:
        oldest_time = datetime.now() - timedelta(hours=int(max_age))
        timestamp = time.mktime(oldest_time.timetuple())

    session = _get_db()
    try:
        if sequential_mode:
            # CI pipeline case
            vote = getVote(session, timestamp, success, previous_job_id,
                           component=component, fallback=False)
        else:
            # Normal case
            vote = getVote(session, timestamp, success, job_id,
                           component=component)
    except Exception as e:
        raise e

    newvote = CIVote(commit_id=vote.commit_id, ci_name=my_job_id,
                     ci_url='', ci_vote=False, ci_in_progress=True,
                     timestamp=int(time.time()), notes='',
                     user=auth.username(), component=vote.component)
    session.add(newvote)
    session.commit()

    commit = session.query(Commit).filter(
        Commit.status == 'SUCCESS',
        Commit.id == vote.commit_id).first()

    result = {'commit_hash': commit.commit_hash,
              'distro_hash': commit.distro_hash,
              'extended_hash': commit.extended_hash,
              'timestamp': newvote.timestamp,
              'job_id': newvote.ci_name,
              'success': newvote.ci_vote,
              'in_progress': newvote.ci_in_progress,
              'user': newvote.user,
              'component': newvote.component}
    return jsonify(result), 201
Beispiel #16
0
def promotions_GET():
    # commit_hash(optional): commit hash
    # distro_hash(optional): distro hash
    # promote_name(optional): only report promotions for promote_name
    # offset(optional): skip the first X promotions (only 100 are shown
    #                   per query)
    if request.headers['Content-Type'] != 'application/json':
        raise InvalidUsage('Unsupported Media Type, use JSON', status_code=415)

    commit_hash = request.json.get('commit_hash', None)
    distro_hash = request.json.get('distro_hash', None)
    promote_name = request.json.get('promote_name', None)
    offset = request.json.get('offset', 0)
    limit = request.json.get('limit', 100)

    config_options = _get_config_options(app.config['CONFIG_FILE'])

    # Make sure we do not exceed
    if limit > max_limit:
        limit = max_limit

    if ((commit_hash and not distro_hash)
            or (distro_hash and not commit_hash)):

        raise InvalidUsage(
            'Both commit_hash and distro_hash must be '
            'specified if any of them is.',
            status_code=400)

    # Find the commit id for commit_hash/distro_hash
    session = getSession(app.config['DB_PATH'])
    if commit_hash and distro_hash:
        commit = session.query(Commit).filter(
            Commit.status == 'SUCCESS', Commit.commit_hash == commit_hash,
            Commit.distro_hash == distro_hash).first()
        if commit is None:
            raise InvalidUsage('commit_hash+distro_hash combination not found',
                               status_code=404)
        commit_id = commit.id
    else:
        commit_id = None

    # Now find the promotions, and filter if necessary
    promotions = session.query(Promotion)
    if commit_id is not None:
        promotions = promotions.filter(Promotion.commit_id == commit_id)
    if promote_name is not None:
        promotions = promotions.filter(
            Promotion.promotion_name == promote_name)

    promotions = promotions.order_by(desc(Promotion.timestamp)).limit(limit).\
        offset(offset)

    # And format the output
    data = []
    for promotion in promotions:
        commit = getCommits(
            session, limit=0).filter(Commit.id == promotion.commit_id).first()

        repo_hash = _repo_hash(commit)
        repo_url = "%s/%s" % (config_options.baseurl,
                              commit.getshardedcommitdir())

        d = {
            'timestamp': promotion.timestamp,
            'commit_hash': commit.commit_hash,
            'distro_hash': commit.distro_hash,
            'repo_hash': repo_hash,
            'repo_url': repo_url,
            'promote_name': promotion.promotion_name,
            'user': promotion.user
        }
        data.append(d)
    closeSession(session)
    return jsonify(data)
Beispiel #17
0
def promotions_GET():
    # commit_hash(optional): commit hash
    # distro_hash(optional): distro hash
    # extended_hash(optional): extended hash
    # aggregate_hash(optional): aggregate hash
    # promote_name(optional): only report promotions for promote_name
    # offset(optional): skip the first X promotions (only 100 are shown
    #                   per query)
    # limit(optional): maximum number of entries to return
    # component(optional): only report promotions for this component
    commit_hash = request.args.get('commit_hash', None)
    distro_hash = request.args.get('distro_hash', None)
    extended_hash = request.args.get('extended_hash', None)
    agg_hash = request.args.get('aggregate_hash', None)
    promote_name = request.args.get('promote_name', None)
    offset = int(request.args.get('offset', 0))
    limit = int(request.args.get('limit', 100))
    component = request.args.get('component', None)

    if request.headers.get('Content-Type') == 'application/json':
        # This is the old, deprecated method of in-body parameters
        # We will keep it for backwards compatibility
        if commit_hash is None:
            commit_hash = request.json.get('commit_hash', None)
        if distro_hash is None:
            distro_hash = request.json.get('distro_hash', None)
        if extended_hash is None:
            extended_hash = request.json.get('extended_hash', None)
        if agg_hash is None:
            agg_hash = request.json.get('aggregate_hash', None)
        if promote_name is None:
            promote_name = request.json.get('promote_name', None)
        if offset == 0:
            offset = int(request.json.get('offset', 0))
        if limit == 100:
            limit = int(request.json.get('limit', 100))
        if component is None:
            component = request.json.get('component', None)

    config_options = _get_config_options(app.config['CONFIG_FILE'])

    # Make sure we do not exceed
    if limit > max_limit:
        limit = max_limit

    if ((commit_hash and not distro_hash) or
            (distro_hash and not commit_hash)):

        raise InvalidUsage('Both commit_hash and distro_hash must be '
                           'specified if any of them is.',
                           status_code=400)

    # Find the commit id for commit_hash/distro_hash
    session = _get_db()
    if commit_hash and distro_hash:
        commit = _get_commit(session, commit_hash, distro_hash, extended_hash)
        if commit is None:
            raise InvalidUsage('commit_hash+distro_hash+extended_hash '
                               'combination not found', status_code=404)
        commit_id = commit.id
    else:
        commit_id = None

    # Now find the promotions, and filter if necessary
    promotions = session.query(Promotion)
    if commit_id is not None:
        promotions = promotions.filter(Promotion.commit_id == commit_id)
    if promote_name is not None:
        promotions = promotions.filter(
            Promotion.promotion_name == promote_name)
    if agg_hash is not None:
        promotions = promotions.filter(Promotion.aggregate_hash == agg_hash)
    if component is not None:
        promotions = promotions.filter(Promotion.component == component)

    promotions = promotions.order_by(desc(Promotion.id)).limit(limit).\
        offset(offset)

    # And format the output
    data = []
    for promotion in promotions:
        commit = getCommits(session, limit=0).filter(
            Commit.id == promotion.commit_id).first()

        repo_hash = _repo_hash(commit)
        repo_url = "%s/%s" % (config_options.baseurl,
                              commit.getshardedcommitdir())

        d = {'timestamp': promotion.timestamp,
             'commit_hash': commit.commit_hash,
             'distro_hash': commit.distro_hash,
             'extended_hash': commit.extended_hash,
             'aggregate_hash': promotion.aggregate_hash,
             'repo_hash': repo_hash,
             'repo_url': repo_url,
             'promote_name': promotion.promotion_name,
             'component': promotion.component,
             'user': promotion.user}
        data.append(d)
    return jsonify(data)
Beispiel #18
0
def last_tested_repo_GET():
    # max_age: Maximum age in hours, used as base for the search
    # success(optional): find repos with a successful/unsuccessful vote
    # job_id(optional); name of the CI that sent the vote
    # sequential_mode(optional): if set to true, change the search algorithm
    #                            to only use previous_job_id as CI name to
    #                            search for. Defaults to false
    # previous_job_id(optional): CI name to search for, if sequential_mode is
    #                            True
    if request.headers['Content-Type'] != 'application/json':
        raise InvalidUsage('Unsupported Media Type, use JSON', status_code=415)

    max_age = request.json.get('max_age', None)
    job_id = request.json.get('job_id', None)
    success = request.json.get('success', None)
    sequential_mode = request.json.get('sequential_mode', None)
    previous_job_id = request.json.get('previous_job_id', None)

    if success is not None:
        success = bool(strtobool(success))

    if sequential_mode is not None:
        sequential_mode = bool(strtobool(sequential_mode))

    if sequential_mode and previous_job_id is None:
        raise InvalidUsage('Missing parameter previous_job_id',
                           status_code=400)

    if max_age is None:
        raise InvalidUsage('Missing parameters', status_code=400)

    # Calculate timestamp as now - max_age
    if int(max_age) == 0:
        timestamp = 0
    else:
        oldest_time = datetime.now() - timedelta(hours=int(max_age))
        timestamp = time.mktime(oldest_time.timetuple())

    session = getSession(app.config['DB_PATH'])
    try:
        if sequential_mode:
            # CI pipeline case
            vote = getVote(session,
                           timestamp,
                           success,
                           previous_job_id,
                           fallback=False)
        else:
            # Normal case
            vote = getVote(session, timestamp, success, job_id)
    except Exception as e:
        raise e

    commit = session.query(Commit).filter(Commit.status == 'SUCCESS',
                                          Commit.id == vote.commit_id).first()

    result = {
        'commit_hash': commit.commit_hash,
        'distro_hash': commit.distro_hash,
        'timestamp': vote.timestamp,
        'job_id': vote.ci_name,
        'success': vote.ci_vote,
        'in_progress': vote.ci_in_progress,
        'user': vote.user
    }
    closeSession(session)
    return jsonify(result), 200
Beispiel #19
0
 def graphql_missing_libraries():
     raise InvalidUsage('Missing libraries, /api/graphql endpoint is not'
                        'available', status_code=501)
Beispiel #20
0
def promote_batch():
    # hash_pairs: list of commit/distro hash pairs
    # promote_name: symlink name
    hash_list = []
    try:
        for pair in request.json:
            commit_hash = pair['commit_hash']
            distro_hash = pair['distro_hash']
            promote_name = pair['promote_name']
            extended_hash = pair.get('extended_hash', None)
            hash_item = [commit_hash, distro_hash, extended_hash, promote_name]
            hash_list.append(hash_item)
    except KeyError:
        raise InvalidUsage('Missing parameters', status_code=400)

    config_options = _get_config_options(app.config['CONFIG_FILE'])
    session = _get_db()
    # Now we will be running all checks for each combination
    # Check for invalid promote names
    for hash_item in hash_list:
        commit_hash = hash_item[0]
        distro_hash = hash_item[1]
        extended_hash = hash_item[2]
        promote_name = hash_item[3]
        if (promote_name == 'consistent' or promote_name == 'current'):
            raise InvalidUsage('Invalid promote_name %s for hash %s_%s' % (
                               promote_name, commit_hash, distro_hash),
                               status_code=403)
        commit = _get_commit(session, commit_hash, distro_hash, extended_hash)
        if commit is None:
            raise InvalidUsage('commit_hash+distro_hash+extended_hash '
                               'combination not found for %s_%s_%s' % (
                                commit_hash, distro_hash, extended_hash),
                               status_code=404)

        # If the commit has been purged, do not move on
        if commit.flags & FLAG_PURGED:
            raise InvalidUsage('commit_hash+distro_hash+extended_hash %s_%s_%s'
                               ' has been purged, cannot promote it' % (
                                commit_hash, distro_hash, extended_hash),
                               status_code=410)

        if config_options.use_components:
            base_directory = os.path.join(app.config['REPO_PATH'],
                                          "component/%s" % commit.component)
        else:
            base_directory = app.config['REPO_PATH']

        target_link = os.path.join(base_directory, promote_name)
        # Check for invalid target links, like ../promotename
        target_dir = os.path.dirname(os.path.abspath(target_link))
        if not os.path.samefile(target_dir, base_directory):
            raise InvalidUsage('Invalid promote_name %s' % promote_name,
                               status_code=403)

    # After all checks have been performed, do all promotions
    rollback_list = []
    for hash_item in hash_list:
        rollback_item = {}
        commit_hash = hash_item[0]
        distro_hash = hash_item[1]
        extended_hash = hash_item[2]
        promote_name = hash_item[3]
        commit = _get_commit(session, commit_hash, distro_hash, extended_hash)
        # We should create a relative symlink
        yumrepodir = commit.getshardedcommitdir()
        if config_options.use_components:
            base_directory = os.path.join(app.config['REPO_PATH'],
                                          "component/%s" %
                                          commit.component)
            # In this case, the relative path should not include
            # the component part
            yumrepodir = yumrepodir.replace("component/%s/" % commit.component,
                                            '')
        else:
            base_directory = app.config['REPO_PATH']

        target_link = os.path.join(base_directory, promote_name)
        rollback_item['target_link'] = target_link
        rollback_item['previous_link'] = None
        # Remove symlink if it exists, so we can create it again
        if os.path.lexists(os.path.abspath(target_link)):
            rollback_item['previous_link'] = os.readlink(
                os.path.abspath(target_link))
            os.remove(target_link)

        rollback_list.append(rollback_item)
        # This is the only destructive operation. If something fails here,
        # we will try to roll everything back
        try:
            os.symlink(yumrepodir, target_link)
        except Exception as e:
            _rollback_batch_promotion(rollback_list)
            raise InvalidUsage("Symlink creation failed with error: %s. "
                               "All previously created symlinks have been "
                               "rolled back." %
                               e, status_code=500)

        timestamp = time.mktime(datetime.now().timetuple())
        promotion = Promotion(commit_id=commit.id,
                              promotion_name=promote_name,
                              timestamp=timestamp, user=auth.username(),
                              component=commit.component,
                              aggregate_hash=None)
        session.add(promotion)

    # And finally, if we are using components, update the top-level
    # repo file
    repo_checksum = None
    if config_options.use_components:
        datadir = os.path.realpath(config_options.datadir)
        repo_checksum = aggregate_repo_files(promote_name, datadir, session,
                                             config_options.reponame,
                                             hashed_dir=True)
        promotion.aggregate_hash = repo_checksum
        session.add(promotion)

    # Close session and return the last promotion we did (which includes the
    # repo checksum)
    session.commit()
    repo_hash = _repo_hash(commit)
    repo_url = "%s/%s" % (config_options.baseurl, commit.getshardedcommitdir())
    result = {'commit_hash': commit_hash,
              'distro_hash': distro_hash,
              'extended_hash': commit.extended_hash,
              'repo_hash': repo_hash,
              'repo_url': repo_url,
              'promote_name': promote_name,
              'component': commit.component,
              'timestamp': timestamp,
              'user': auth.username(),
              'aggregate_hash': repo_checksum}
    return jsonify(result), 201
Beispiel #21
0
def last_tested_repo_GET():
    # max_age: Maximum age in hours, used as base for the search
    # success(optional): find repos with a successful/unsuccessful vote
    # job_id(optional); name of the CI that sent the vote
    # sequential_mode(optional): if set to true, change the search algorithm
    #                            to only use previous_job_id as CI name to
    #                            search for. Defaults to false
    # previous_job_id(optional): CI name to search for, if sequential_mode is
    #                            True
    # component(optional): only get votes for this component

    max_age = request.args.get('max_age', None)
    job_id = request.args.get('job_id', None)
    success = request.args.get('success', None)
    sequential_mode = request.args.get('sequential_mode', None)
    previous_job_id = request.args.get('previous_job_id', None)
    component = request.args.get('component', None)

    if request.headers.get('Content-Type') == 'application/json':
        # This is the old, deprecated method of in-body parameters
        # We will keep it for backwards compatibility
        if max_age is None:
            max_age = request.json.get('max_age', None)
        if job_id is None:
            job_id = request.json.get('job_id', None)
        if success is None:
            success = request.json.get('success', None)
        if sequential_mode is None:
            sequential_mode = request.json.get('sequential_mode', None)
        if previous_job_id is None:
            previous_job_id = request.json.get('previous_job_id', None)
        if component is None:
            component = request.json.get('component', None)

    if success is not None:
        success = bool(strtobool(success))

    if sequential_mode is not None:
        sequential_mode = bool(strtobool(sequential_mode))

    if sequential_mode and previous_job_id is None:
        raise InvalidUsage('Missing parameter previous_job_id',
                           status_code=400)

    if max_age is None:
        raise InvalidUsage('Missing parameters', status_code=400)

    # Calculate timestamp as now - max_age
    if int(max_age) == 0:
        timestamp = 0
    else:
        oldest_time = datetime.now() - timedelta(hours=int(max_age))
        timestamp = time.mktime(oldest_time.timetuple())

    session = _get_db()
    try:
        if sequential_mode:
            # CI pipeline case
            vote = getVote(session, timestamp, success, previous_job_id,
                           component=component, fallback=False)
        else:
            # Normal case
            vote = getVote(session, timestamp, success, job_id,
                           component=component)
    except Exception as e:
        raise e

    commit = session.query(Commit).filter(
        Commit.status == 'SUCCESS',
        Commit.id == vote.commit_id).first()

    result = {'commit_hash': commit.commit_hash,
              'distro_hash': commit.distro_hash,
              'extended_hash': commit.extended_hash,
              'timestamp': vote.timestamp,
              'job_id': vote.ci_name,
              'success': vote.ci_vote,
              'in_progress': vote.ci_in_progress,
              'user': vote.user,
              'component': vote.component}
    return jsonify(result), 200
Beispiel #22
0
def promote():
    # commit_hash: commit hash
    # distro_hash: distro hash
    # promote_name: symlink name

    if request.headers['Content-Type'] != 'application/json':
        raise InvalidUsage('Unsupported Media Type, use JSON', status_code=415)

    try:
        commit_hash = request.json['commit_hash']
        distro_hash = request.json['distro_hash']
        promote_name = request.json['promote_name']
    except KeyError:
        raise InvalidUsage('Missing parameters', status_code=400)

    # Check for invalid promote names
    if (promote_name == 'consistent' or promote_name == 'current'):
        raise InvalidUsage('Invalid promote_name %s' % promote_name,
                           status_code=403)

    config_options = _get_config_options(app.config['CONFIG_FILE'])

    session = getSession(app.config['DB_PATH'])
    commit = session.query(Commit).filter(
        Commit.status == 'SUCCESS', Commit.commit_hash == commit_hash,
        Commit.distro_hash == distro_hash).first()
    if commit is None:
        raise InvalidUsage('commit_hash+distro_hash combination not found',
                           status_code=404)

    target_link = os.path.join(app.config['REPO_PATH'], promote_name)
    # Check for invalid target links, like ../promotename
    target_dir = os.path.dirname(os.path.abspath(target_link))
    if not os.path.samefile(target_dir, app.config['REPO_PATH']):
        raise InvalidUsage('Invalid promote_name %s' % promote_name,
                           status_code=403)

    # We should create a relative symlink
    yumrepodir = commit.getshardedcommitdir()

    # Remove symlink if it exists, so we can create it again
    if os.path.lexists(os.path.abspath(target_link)):
        os.remove(target_link)
    try:
        os.symlink(yumrepodir, target_link)
    except Exception as e:
        raise InvalidUsage("Symlink creation failed with error: %s" % e,
                           status_code=500)

    timestamp = time.mktime(datetime.now().timetuple())
    promotion = Promotion(commit_id=commit.id,
                          promotion_name=promote_name,
                          timestamp=timestamp,
                          user=auth.username())

    session.add(promotion)
    session.commit()

    repo_hash = _repo_hash(commit)
    repo_url = "%s/%s" % (config_options.baseurl, yumrepodir)

    result = {
        'commit_hash': commit_hash,
        'distro_hash': distro_hash,
        'repo_hash': repo_hash,
        'repo_url': repo_url,
        'promote_name': promote_name,
        'timestamp': timestamp,
        'user': auth.username()
    }
    closeSession(session)
    return jsonify(result), 201