From 9e426816a58dcf5e328236877a0100899e8c5538 Mon Sep 17 00:00:00 2001 From: jakedt Date: Wed, 19 Feb 2014 16:08:33 -0500 Subject: [PATCH] Pass trigger information on build status. Set up a trigger for the sample building repository. Allow to list the builds started from a trigger. Protect the callback with the proper auth for creating a trigger on a repo. --- data/database.py | 1 + data/model.py | 51 +++++++++++++++++++++++++---------------- endpoints/api.py | 42 ++++++++++++++++++++++++--------- endpoints/callbacks.py | 24 +++++++++++++------ endpoints/trigger.py | 6 ++--- initdb.py | 13 +++++++++-- test/data/test.db | Bin 151552 -> 152576 bytes 7 files changed, 94 insertions(+), 43 deletions(-) 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 36e07e99a25591e58d468feb4fe8f28527ccef8c..45dd94d43ffaee5403429bf30a98bcc3bb0b9281 100644 GIT binary patch delta 4379 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 delta 4120 zcmbtWc~p~E7SDa(V+kYziUKOKSVY9^J5`YoAcQPHfCO9v1PP*m0-}yZtF0naH+p=Y z&Z!l3Y)==pVtLhW+NpLQr#e+@)u|q>4v4kVy4A|OPmi_^p8ipjlixe{{@#1{es{~a z_!Z&eO~N(9!Zr#7g6G-q{>Qa;tcocmXOsMxDvW!LCo12poxq{e{EnU6;_QhoORg@@ zq_?;=YOBqdX?L3q2AiR%$d+DWW;#=mGq*@#a2Ds=3^u(@RistvHHDURdydtvE3)Ui z+iGn^n(_svDtG#f*=l!%&L?ZK<`!5yN~zqdw5Tc_d5t-aMq^&1ugc?Stn_LM+?hpA zb-J?c7u#sfg4|M7LqTC0-&4TUC{$r@#LVTDy+TC0<18O-_f6sDpIoyI#ySC%`=?$o=SX=a%%tD&}WPQD_2 zc2-WgExX)2zwK>%qQ+~Pn{GE4tu?xt^If%OO?{Kq>~?sHD`)6t&d#w*OA6C!@-hmG z?9Ni7Twd>}ncGzAZ3}m3Cfby){A_t&SOaY2SbZ(`q>xmuTTuED2CZ}fJYa7aTjw;BD|A)4NW9zSi`vbx89oQ?}+ ztGVXKtspb_3jmgg5&Bc%4gOtxcU1~S+#as3BP`I%kxl!+zZpHQdnnV7IVb#c&RK(g zOa88=rqpJYszRqM(`zIegFzut%hhs;r>tBl(a5zGWfi66dWA~MAsGr3K%tO@B|xK8 zdObRoS|V3z^b)nFqFhqy(JLi#wO6MpSIN~1uNrYOxO)Hu5TL8jt944=g1)R=!aB$x zF?i()iAt|hdjhmtgI=vK@3=$#^&MCZxWn(KK5h#hE20>~ro>5hmC`AYTPBQ=zlOm}|>5D^zByQE$)EYUM@QS(*Myxq>;X zlqmGE3T=u)m7-KhwR)A_V4!`;us<%PCRbNdW8CPF^nei zVs`Gl8%95313NdA=!?Pl2#y_2BD;$r>d{7FjYV9t}m~bK=LEF zizQGHrtEiwc{iApP6F8@xXqIwMcwbn!uWcxXI6YlZ2TOrr`$VdZv3Kq@t!6SOhcVq z47ZBK+Y!60ATuB$IvQ3itoM{wd#h_^cxTtonSXyRq(2sb9zhlvH`(zkkdXGN!Ia-s45r+-8&9Q8VB(tBRWW&O1%uV444#|H;IAbNT8kL8DScBxSHLnL*QN2J;daG{!S%h+!~y7=t;H4C=xe%ns$1vqTK4 z0~yT3h#}&(G8C-g4su)Bh$0+b5Y45Me|1ARsWl@1z$Y0yU&GaqAO8zV+Sd#s46CSU z84!S#Wa~4K1J9Ey{PGML_bgkMkQMy$7xHg@sVA|^*-}fY`K5-uy&SA;c;^a8&v$(|Nalg1W^Cm|~#9rB2C zC9CN&GR=?G_qvsE1M*30E6AX{O?DxY9%%)`VAhD@b{Gv+MB5G~C?X5n;R*1OO}ykJ z=iAwpds`uz^tIpZz)L??iXr}A!#fzh z|J=cg!|9o?*^}a^ilZK0jw0w7oWm2k=1k?qA)UuzHsF-;?|saRqv;FZfQSWY+y1k> zoInqq;4KEP8Y^Z-<})X85Ir14%&`I)r1NS(HN(2ci#f%KD{i zXxBjG2iV{1^U>Zxs25|g)iZ^5Me^Qi6P{`(H>1!+Jmzqeg`OXZ#4KXw%B}R;FoY2f zyzxORy%UXwv8}-`?I6)H=xaQF;fsOfS`500!&^RzAV0*TAw(F5_T#8|lgHc@>A5(> zx-;DJ^g>>~g%#oAT-q}db+IIDPnXkg6L>>@bvt_KrBR4=-}q5uFVVh4WcR}n7eY^w zJPA6&DLyz(5~b)v9J2PC8?;BtdmMN6?7QT)3|+(eOFJ9sH92C{Fk)NtG=h}qJT`pz z{TkY>L@Y_+U+p@|-Y0oJWPN8f4c745nCDhFX^a-JS{&DLD3Qo?=nI_m+MW{Hr$f{I zuZT=SqD$GV~5-3t-f2SiXynXTf$O_;l+AD~z5yrsaz<_)yZfdKyxCWypZ