This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/data/model/build.py

261 lines
8.6 KiB
Python
Raw Normal View History

import json
import os
from datetime import timedelta, datetime
from peewee import JOIN_LEFT_OUTER
import features
from data.database import (BuildTriggerService, RepositoryBuildTrigger, Repository, Namespace, User,
RepositoryBuild, BUILD_PHASE, db_for_update, db_random_func)
from data.model import (InvalidBuildTriggerException, InvalidRepositoryBuildException,
db_transaction, user as user_model)
PRESUMED_DEAD_BUILD_AGE = timedelta(days=15)
PHASES_NOT_ALLOWED_TO_CANCEL_FROM = (BUILD_PHASE.PUSHING, BUILD_PHASE.COMPLETE,
BUILD_PHASE.ERROR, BUILD_PHASE.INTERNAL_ERROR)
2016-11-16 18:51:07 +00:00
ARCHIVABLE_BUILD_PHASES = [BUILD_PHASE.COMPLETE, BUILD_PHASE.ERROR, BUILD_PHASE.CANCELLED]
def update_build_trigger(trigger, config, auth_token=None, write_token=None):
trigger.config = json.dumps(config or {})
if auth_token is not None:
trigger.auth_token = auth_token
if write_token is not None:
trigger.write_token = write_token
trigger.save()
def create_build_trigger(repo, service_name, auth_token, user, pull_robot=None, config=None):
service = BuildTriggerService.get(name=service_name)
trigger = RepositoryBuildTrigger.create(repository=repo, service=service,
auth_token=auth_token,
connected_user=user,
pull_robot=pull_robot,
config=json.dumps(config or {}))
return trigger
def get_build_trigger(trigger_uuid):
try:
return (RepositoryBuildTrigger
.select(RepositoryBuildTrigger, BuildTriggerService, Repository, Namespace)
.join(BuildTriggerService)
.switch(RepositoryBuildTrigger)
.join(Repository)
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
.switch(RepositoryBuildTrigger)
.join(User)
.where(RepositoryBuildTrigger.uuid == trigger_uuid)
.get())
except RepositoryBuildTrigger.DoesNotExist:
msg = 'No build trigger with uuid: %s' % trigger_uuid
raise InvalidBuildTriggerException(msg)
def list_build_triggers(namespace_name, repository_name):
return (RepositoryBuildTrigger
.select(RepositoryBuildTrigger, BuildTriggerService, Repository)
.join(BuildTriggerService)
.switch(RepositoryBuildTrigger)
.join(Repository)
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
.where(Namespace.username == namespace_name, Repository.name == repository_name))
def list_trigger_builds(namespace_name, repository_name, trigger_uuid,
limit):
return (list_repository_builds(namespace_name, repository_name, limit)
.where(RepositoryBuildTrigger.uuid == trigger_uuid))
def get_repository_for_resource(resource_key):
try:
return (Repository
.select(Repository, Namespace)
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
.switch(Repository)
.join(RepositoryBuild)
.where(RepositoryBuild.resource_key == resource_key)
.get())
except Repository.DoesNotExist:
return None
def _get_build_base_query():
return (RepositoryBuild
.select(RepositoryBuild, RepositoryBuildTrigger, BuildTriggerService, Repository,
Namespace, User)
.join(Repository)
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
.switch(RepositoryBuild)
.join(User, JOIN_LEFT_OUTER)
.switch(RepositoryBuild)
.join(RepositoryBuildTrigger, JOIN_LEFT_OUTER)
.join(BuildTriggerService, JOIN_LEFT_OUTER)
.order_by(RepositoryBuild.started.desc()))
def get_repository_build(build_uuid):
try:
return _get_build_base_query().where(RepositoryBuild.uuid == build_uuid).get()
except RepositoryBuild.DoesNotExist:
msg = 'Unable to locate a build by id: %s' % build_uuid
raise InvalidRepositoryBuildException(msg)
def list_repository_builds(namespace_name, repository_name, limit,
include_inactive=True, since=None):
query = (_get_build_base_query()
.where(Repository.name == repository_name, Namespace.username == namespace_name)
.limit(limit))
if since is not None:
query = query.where(RepositoryBuild.started >= since)
if not include_inactive:
query = query.where(RepositoryBuild.phase != BUILD_PHASE.ERROR,
RepositoryBuild.phase != BUILD_PHASE.COMPLETE)
return query
def get_recent_repository_build(namespace_name, repository_name):
query = list_repository_builds(namespace_name, repository_name, 1)
try:
return query.get()
except RepositoryBuild.DoesNotExist:
return None
def create_repository_build(repo, access_token, job_config_obj, dockerfile_id,
display_name, trigger=None, pull_robot_name=None):
pull_robot = None
if pull_robot_name:
pull_robot = user_model.lookup_robot(pull_robot_name)
return RepositoryBuild.create(repository=repo, access_token=access_token,
job_config=json.dumps(job_config_obj),
display_name=display_name, trigger=trigger,
resource_key=dockerfile_id,
pull_robot=pull_robot)
def get_pull_robot_name(trigger):
if not trigger.pull_robot:
return None
return trigger.pull_robot.username
def _get_build_row_for_update(build_uuid):
return db_for_update(RepositoryBuild.select().where(RepositoryBuild.uuid == build_uuid)).get()
def update_phase(build_uuid, phase):
""" A function to change the phase of a build """
try:
build = _get_build_row_for_update(build_uuid)
except RepositoryBuild.DoesNotExist:
return False
# Can't update a cancelled build
if build.phase == BUILD_PHASE.CANCELLED:
return False
build.phase = phase
build.save()
return True
def create_cancel_build_in_queue(build, build_queue):
""" A function to cancel a build before it leaves the queue """
def cancel_build():
cancelled = False
if build.queue_id is not None:
cancelled = build_queue.cancel(build.queue_id)
if build.phase != BUILD_PHASE.WAITING:
return False
return cancelled
return cancel_build
def create_cancel_build_in_manager(build, build_canceller):
""" A function to cancel the build before it starts to push """
def cancel_build():
if build.phase in PHASES_NOT_ALLOWED_TO_CANCEL_FROM:
return False
2016-11-16 18:51:07 +00:00
return build_canceller.try_cancel_build(build.uuid)
return cancel_build
def cancel_repository_build(build, build_queue):
""" This tries to cancel the build returns true if request is successful false if it can't be cancelled """
with db_transaction():
from app import build_canceller
2016-11-29 16:48:08 +00:00
from buildman.jobutil.buildjob import BuildJobNotifier
# Reload the build for update.
# We are loading the build for update so checks should be as quick as possible.
try:
build = _get_build_row_for_update(build.uuid)
except RepositoryBuild.DoesNotExist:
return False
cancel_builds = [create_cancel_build_in_queue(build, build_queue),
create_cancel_build_in_manager(build, build_canceller), ]
2016-11-16 18:51:07 +00:00
original_phase = build.phase
for cancelled in cancel_builds:
if cancelled():
2016-11-16 18:51:07 +00:00
build.phase = BUILD_PHASE.CANCELLED
2016-11-29 16:48:08 +00:00
BuildJobNotifier(build.uuid).send_notification("build_cancelled")
2016-11-16 18:51:07 +00:00
build.save()
return True
2016-11-16 18:51:07 +00:00
build.phase = original_phase
build.save()
return False
def get_archivable_build():
presumed_dead_date = datetime.utcnow() - PRESUMED_DEAD_BUILD_AGE
2016-11-16 18:51:07 +00:00
candidates = (RepositoryBuild
.select(RepositoryBuild.id)
2016-11-16 18:51:07 +00:00
.where((RepositoryBuild.phase << ARCHIVABLE_BUILD_PHASES) |
(RepositoryBuild.started < presumed_dead_date),
RepositoryBuild.logs_archived == False)
.limit(50)
.alias('candidates'))
try:
found_id = (RepositoryBuild
.select(candidates.c.id)
.from_(candidates)
.order_by(db_random_func())
.get())
return RepositoryBuild.get(id=found_id)
except RepositoryBuild.DoesNotExist:
return None
def mark_build_archived(build_uuid):
""" Mark a build as archived, and return True if we were the ones who actually
updated the row. """
return (RepositoryBuild
.update(logs_archived=True)
.where(RepositoryBuild.uuid == build_uuid,
RepositoryBuild.logs_archived == False)
.execute()) > 0