diff --git a/data/database.py b/data/database.py index c72fa86a5..6020f9abc 100644 --- a/data/database.py +++ b/data/database.py @@ -230,6 +230,7 @@ class RepositoryBuild(BaseModel): phase = CharField(default='waiting') started = DateTimeField(default=datetime.now) display_name = CharField() + trigger = ForeignKeyField(RepositoryBuildTrigger, null=True, index=True) class QueueItem(BaseModel): diff --git a/data/model.py b/data/model.py index 98d1e28b2..b07d453e8 100644 --- a/data/model.py +++ b/data/model.py @@ -1299,35 +1299,38 @@ def load_token_data(code): def get_repository_build(namespace_name, repository_name, build_uuid): - joined = RepositoryBuild.select().join(Repository) - fetched = list(joined.where(Repository.name == repository_name, - Repository.namespace == namespace_name, - RepositoryBuild.uuid == build_uuid)) + try: + query = list_repository_builds(namespace_name, repository_name) + return query.where(RepositoryBuild.uuid == build_uuid).get() - if not fetched: + except RepositoryBuild.DoesNotExist: msg = 'Unable to locate a build by id: %s' % build_uuid raise InvalidRepositoryBuildException(msg) - return fetched[0] - def list_repository_builds(namespace_name, repository_name, include_inactive=True): - joined = RepositoryBuild.select().join(Repository) - filtered = joined + query = (RepositoryBuild + .select(RepositoryBuild, RepositoryBuildTrigger, BuildTriggerService) + .join(Repository) + .switch(RepositoryBuild) + .join(RepositoryBuildTrigger, JOIN_LEFT_OUTER) + .join(BuildTriggerService, JOIN_LEFT_OUTER) + .where(Repository.name == repository_name, + Repository.namespace == namespace_name)) + if not include_inactive: - filtered = filtered.where(RepositoryBuild.phase != 'error', - RepositoryBuild.phase != 'complete') - fetched = list(filtered.where(Repository.name == repository_name, - Repository.namespace == namespace_name)) - return fetched + query = query.where(RepositoryBuild.phase != 'error', + RepositoryBuild.phase != 'complete') + + return query def create_repository_build(repo, access_token, resource_key, tag, - display_name): + display_name, trigger=None): return RepositoryBuild.create(repository=repo, access_token=access_token, resource_key=resource_key, tag=tag, - display_name=display_name) + display_name=display_name, trigger=trigger) def create_webhook(repo, params_obj): @@ -1386,11 +1389,8 @@ def log_action(kind_name, user_or_organization_name, performer=None, metadata_json=json.dumps(metadata), datetime=timestamp) -def create_build_trigger(namespace_name, repository_name, service_name, - auth_token, user): +def create_build_trigger(repo, service_name, auth_token, user): service = BuildTriggerService.get(name=service_name) - repo = Repository.get(namespace=namespace_name, name=repository_name) - trigger = RepositoryBuildTrigger.create(repository=repo, service=service, auth_token=auth_token, connected_user=user) @@ -1428,3 +1428,14 @@ def list_build_triggers(namespace_name, repository_name): def delete_build_trigger(namespace_name, repository_name, trigger_uuid): trigger = get_build_trigger(namespace_name, repository_name, trigger_uuid) trigger.delete_instance() + + +def list_trigger_builds(namespace_name, repository_name, trigger_uuid, + limit=None): + query = (list_repository_builds(namespace_name, repository_name) + .where(RepositoryBuildTrigger.uuid == trigger_uuid)) + + if limit: + return query.limit(limit) + else: + return query diff --git a/endpoints/api.py b/endpoints/api.py index 4b307337d..df2260edf 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -1110,7 +1110,7 @@ def get_repo(namespace, repository): 'can_write': can_write, 'can_admin': can_admin, 'is_public': is_public, - 'is_building': len(active_builds) > 0, + 'is_building': len(list(active_builds)) > 0, 'is_organization': bool(organization) }) @@ -1118,6 +1118,18 @@ def get_repo(namespace, repository): abort(403) # Permission denied +def trigger_view(trigger): + if trigger and trigger.uuid: + return { + 'service': trigger.service.name, + 'config': json.loads(trigger.config), + 'id': trigger.uuid, + 'connected_user': trigger.connected_user.username, + } + + return None + + def build_status_view(build_obj, can_write=False): status = build_logs.get_status(build_obj.uuid) return { @@ -1127,7 +1139,8 @@ def build_status_view(build_obj, can_write=False): 'display_name': build_obj.display_name, 'status': status, 'resource_key': build_obj.resource_key if can_write else None, - 'is_writer': can_write + 'is_writer': can_write, + 'trigger': trigger_view(build_obj.trigger), } @@ -1328,15 +1341,6 @@ def delete_webhook(namespace, repository, public_id): abort(403) # Permission denied -def trigger_view(trigger): - return { - 'service': trigger.service.name, - 'config': json.loads(trigger.config), - 'id': trigger.uuid, - 'connected_user': trigger.connected_user.username, - } - - @api.route('/repository//trigger/', methods=['GET']) @api_login_required @@ -1354,6 +1358,22 @@ def get_build_trigger(namespace, repository, trigger_uuid): abort(403) # Permission denied +@api.route('/repository//trigger//builds', + methods=['GET']) +@api_login_required +@parse_repository_name +def list_trigger_recent_builds(namespace, repository, trigger_uuid): + permission = AdministerRepositoryPermission(namespace, repository) + if permission.can(): + limit = request.args.get('limit', 5) + builds = model.list_trigger_builds(namespace, repository, trigger_uuid, + limit) + return jsonify({ + 'builds': [build_status_view(build, True) for build in builds] + }) + + abort(403) # Permission denied + @api.route('/repository//trigger/', methods=['GET']) @api_login_required @parse_repository_name diff --git a/endpoints/callbacks.py b/endpoints/callbacks.py index e78bda55e..791c1a613 100644 --- a/endpoints/callbacks.py +++ b/endpoints/callbacks.py @@ -7,6 +7,8 @@ from endpoints.common import render_page_template, common_login from app import app, mixpanel from data import model from util.names import parse_repository_name +from util.http import abort +from auth.permissions import AdministerRepositoryPermission logger = logging.getLogger(__name__) @@ -113,10 +115,18 @@ def github_oauth_attach(): @login_required @parse_repository_name def attach_github_build_trigger(namespace, repository): - token = exchange_github_code_for_token(request.args.get('code')) - model.create_build_trigger(namespace, repository, 'github', token, - current_user.db_user()) - admin_path = '%s/%s/%s' % (namespace, repository, 'admin') - full_url = url_for('web.repository', path=admin_path) + '?tab=trigger' - logger.debug('Redirecting to full url: %s' % full_url) - return redirect(full_url) + permission = AdministerRepositoryPermission(namespace, repository) + if permission.can(): + token = exchange_github_code_for_token(request.args.get('code')) + repo = model.get_repository(namespace, repository) + if not repo: + msg = 'Invalid repository: %s/%s' % (namespace, repository) + abort(404, message=msg) + + model.create_build_trigger(repo, 'github', token, current_user.db_user()) + admin_path = '%s/%s/%s' % (namespace, repository, 'admin') + full_url = url_for('web.repository', path=admin_path) + '?tab=trigger' + logger.debug('Redirecting to full url: %s' % full_url) + return redirect(full_url) + + abort(403) \ No newline at end of file diff --git a/endpoints/trigger.py b/endpoints/trigger.py index 8242e7ee6..dfcb21b83 100644 --- a/endpoints/trigger.py +++ b/endpoints/trigger.py @@ -30,10 +30,10 @@ class BuildTrigger(object): def __init__(self): pass - def list_repositories(self, auth_token): + def list_build_sources(self, auth_token): """ Take the auth information for the specific trigger type and load the - list of repositories. + list of build sources(repositories). """ raise NotImplementedError @@ -73,7 +73,7 @@ class GithubBuildTrigger(BuildTrigger): def service_name(cls): return 'github' - def list_repositories(self, auth_token): + def list_build_sources(self, auth_token): gh_client = self._get_client(auth_token) usr = gh_client.get_user() diff --git a/initdb.py b/initdb.py index 075798e5a..ddef21e51 100644 --- a/initdb.py +++ b/initdb.py @@ -281,8 +281,17 @@ def populate_database(): token = model.create_access_token(building, 'write') tag = 'ci.devtable.com:5000/%s/%s' % (building.namespace, building.name) - build = model.create_repository_build(building, token, '701dcc3724fb4f2ea6c31400528343cd', - tag, 'build-name') + + trigger = model.create_build_trigger(building, 'github', '123authtoken', + new_user_1) + trigger.config = json.dumps({ + 'build_source': 'jakedt/testconnect', + }) + trigger.save() + + build = model.create_repository_build(building, token, + '701dcc3724fb4f2ea6c31400528343cd', + tag, 'build-name', trigger) build.uuid = 'deadbeef-dead-beef-dead-beefdeadbeef' build.save() diff --git a/test/data/test.db b/test/data/test.db index 36e07e99a..45dd94d43 100644 Binary files a/test/data/test.db and b/test/data/test.db differ