257 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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_random_func, UseThenDisconnect)
 | |
| from data.model import (InvalidBuildTriggerException, InvalidRepositoryBuildException,
 | |
|                         db_transaction, user as user_model, config)
 | |
| 
 | |
| 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)
 | |
| 
 | |
| 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(build_uuid):
 | |
|   return RepositoryBuild.select().where(RepositoryBuild.uuid == build_uuid).get()
 | |
| 
 | |
| 
 | |
| def update_phase_then_close(build_uuid, phase):
 | |
|   """ A function to change the phase of a build """
 | |
|   with UseThenDisconnect(config.app_config):
 | |
|     try:
 | |
|       build = _get_build_row(build_uuid)
 | |
|     except RepositoryBuild.DoesNotExist:
 | |
|       return False
 | |
| 
 | |
|     # Can't update a cancelled build
 | |
|     if build.phase == BUILD_PHASE.CANCELLED:
 | |
|       return False
 | |
| 
 | |
|     updated = (RepositoryBuild
 | |
|                .update(phase=phase)
 | |
|                .where(RepositoryBuild.id == build.id, RepositoryBuild.phase == build.phase)
 | |
|                .execute())
 | |
|     
 | |
|     return updated > 0
 | |
| 
 | |
| 
 | |
| def create_cancel_build_in_queue(build_phase, build_queue_id, 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_phase, build_uuid, 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
 | |
| 
 | |
|     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 """
 | |
|   from app import build_canceller
 | |
|   from buildman.jobutil.buildjob import BuildJobNotifier
 | |
| 
 | |
|   cancel_builds = [create_cancel_build_in_queue(build.phase, build.queue_id, build_queue),
 | |
|                    create_cancel_build_in_manager(build.phase, build.uuid, build_canceller), ]
 | |
|   for cancelled in cancel_builds:
 | |
|     if cancelled():
 | |
|       updated = update_phase_then_close(build.uuid, BUILD_PHASE.CANCELLED)
 | |
|       if updated:
 | |
|         BuildJobNotifier(build.uuid).send_notification("build_cancelled")
 | |
| 
 | |
|       return updated
 | |
| 
 | |
|   return False
 | |
| 
 | |
| 
 | |
| def get_archivable_build():
 | |
|   presumed_dead_date = datetime.utcnow() - PRESUMED_DEAD_BUILD_AGE
 | |
| 
 | |
|   candidates = (RepositoryBuild
 | |
|                 .select(RepositoryBuild.id)
 | |
|                 .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
 |