1 import base64
2 import datetime
3 from functools import wraps
4 import json
5 import os
6 import flask
7
8 from werkzeug import secure_filename
9
10 from coprs import db
11 from coprs import exceptions
12 from coprs import forms
13 from coprs import helpers
14 from coprs import models
15 from coprs.helpers import fix_protocol_for_backend
16 from coprs.logic.api_logic import MonitorWrapper
17 from coprs.logic.builds_logic import BuildsLogic
18 from coprs.logic.complex_logic import ComplexLogic
19 from coprs.logic.users_logic import UsersLogic
20 from coprs.logic.packages_logic import PackagesLogic
21
22 from coprs.views.misc import login_required, api_login_required
23
24 from coprs.views.api_ns import api_ns
25
26 from coprs.logic import builds_logic
27 from coprs.logic import coprs_logic
28 from coprs.logic.coprs_logic import CoprsLogic
29 from coprs.logic.actions_logic import ActionsLogic
30
31 from coprs.exceptions import (ActionInProgressException,
32 InsufficientRightsException,
33 DuplicateException,
34 LegacyApiError,
35 UnknownSourceTypeException)
48 return wrapper
49
53 """
54 Render the home page of the api.
55 This page provides information on how to call/use the API.
56 """
57
58 return flask.render_template("api.html")
59
60
61 @api_ns.route("/new/", methods=["GET", "POST"])
82
83
84 @api_ns.route("/coprs/<username>/new/", methods=["POST"])
87 """
88 Receive information from the user on how to create its new copr,
89 check their validity and create the corresponding copr.
90
91 :arg name: the name of the copr to add
92 :arg chroots: a comma separated list of chroots to use
93 :kwarg repos: a comma separated list of repository that this copr
94 can use.
95 :kwarg initial_pkgs: a comma separated list of initial packages to
96 build in this new copr
97
98 """
99
100 form = forms.CoprFormFactory.create_form_cls()(csrf_enabled=False)
101 infos = []
102
103
104
105 for post_key in flask.request.form.keys():
106 if post_key not in form.__dict__.keys():
107 infos.append("Unknown key '{key}' received.".format(key=post_key))
108
109 if form.validate_on_submit():
110 group = ComplexLogic.get_group_by_name_safe(username[1:]) if username[0] == "@" else None
111
112 try:
113 copr = CoprsLogic.add(
114 name=form.name.data.strip(),
115 repos=" ".join(form.repos.data.split()),
116 user=flask.g.user,
117 selected_chroots=form.selected_chroots,
118 description=form.description.data,
119 instructions=form.instructions.data,
120 check_for_duplicates=True,
121 disable_createrepo=form.disable_createrepo.data,
122 unlisted_on_hp=form.unlisted_on_hp.data,
123 build_enable_net=form.build_enable_net.data,
124 group=group,
125 persistent=form.persistent.data,
126 )
127 infos.append("New project was successfully created.")
128
129 if form.initial_pkgs.data:
130 pkgs = form.initial_pkgs.data.split()
131 for pkg in pkgs:
132 builds_logic.BuildsLogic.add(
133 user=flask.g.user,
134 pkgs=pkg,
135 copr=copr)
136
137 infos.append("Initial packages were successfully "
138 "submitted for building.")
139
140 output = {"output": "ok", "message": "\n".join(infos)}
141 db.session.commit()
142 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as err:
143 db.session.rollback()
144 raise LegacyApiError(str(err))
145
146 else:
147 errormsg = "Validation error\n"
148 if form.errors:
149 for field, emsgs in form.errors.items():
150 errormsg += "- {0}: {1}\n".format(field, "\n".join(emsgs))
151
152 errormsg = errormsg.replace('"', "'")
153 raise LegacyApiError(errormsg)
154
155 return flask.jsonify(output)
156
157
158 @api_ns.route("/coprs/<username>/<coprname>/delete/", methods=["POST"])
183
184
185 @api_ns.route("/coprs/<username>/<coprname>/fork/", methods=["POST"])
186 @api_login_required
187 @api_req_with_copr
188 -def api_copr_fork(copr):
189 """ Fork the project and builds in it
190 """
191 form = forms.CoprForkFormFactory\
192 .create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)(csrf_enabled=False)
193
194 if form.validate_on_submit() and copr:
195 try:
196 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0]
197 if flask.g.user.name != form.owner.data and not dstgroup:
198 return LegacyApiError("There is no such group: {}".format(form.owner.data))
199
200 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup)
201 if created:
202 msg = ("Forking project {} for you into {}.\nPlease be aware that it may take a few minutes "
203 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
204 elif not created and form.confirm.data == True:
205 msg = ("Updating packages in {} from {}.\nPlease be aware that it may take a few minutes "
206 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
207 else:
208 raise LegacyApiError("You are about to fork into existing project: {}\n"
209 "Please use --confirm if you really want to do this".format(fcopr.full_name))
210
211 output = {"output": "ok", "message": msg}
212 db.session.commit()
213
214 except (exceptions.ActionInProgressException,
215 exceptions.InsufficientRightsException) as err:
216 db.session.rollback()
217 raise LegacyApiError(str(err))
218 else:
219 raise LegacyApiError("Invalid request: {0}".format(form.errors))
220
221 return flask.jsonify(output)
222
223
224 @api_ns.route("/coprs/")
225 @api_ns.route("/coprs/<username>/")
226 -def api_coprs_by_owner(username=None):
227 """ Return the list of coprs owned by the given user.
228 username is taken either from GET params or from the URL itself
229 (in this order).
230
231 :arg username: the username of the person one would like to the
232 coprs of.
233
234 """
235 username = flask.request.args.get("username", None) or username
236 if username is None:
237 raise LegacyApiError("Invalid request: missing `username` ")
238
239 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
240
241 if username.startswith("@"):
242 group_name = username[1:]
243 query = CoprsLogic.get_multiple()
244 query = CoprsLogic.filter_by_group_name(query, group_name)
245 else:
246 query = CoprsLogic.get_multiple_owned_by_username(username)
247
248 query = CoprsLogic.join_builds(query)
249 query = CoprsLogic.set_query_order(query)
250
251 repos = query.all()
252 output = {"output": "ok", "repos": []}
253 for repo in repos:
254 yum_repos = {}
255 for build in repo.builds:
256 if build.results:
257 for chroot in repo.active_chroots:
258 release = release_tmpl.format(chroot=chroot)
259 yum_repos[release] = fix_protocol_for_backend(
260 os.path.join(build.results, release + '/'))
261 break
262
263 output["repos"].append({"name": repo.name,
264 "additional_repos": repo.repos,
265 "yum_repos": yum_repos,
266 "description": repo.description,
267 "instructions": repo.instructions,
268 "persistent": repo.persistent,
269 "unlisted_on_hp": repo.unlisted_on_hp
270 })
271
272 return flask.jsonify(output)
273
278 """ Return detail of one project.
279
280 :arg username: the username of the person one would like to the
281 coprs of.
282 :arg coprname: the name of project.
283
284 """
285 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
286 output = {"output": "ok", "detail": {}}
287 yum_repos = {}
288
289 build = models.Build.query.filter(models.Build.results != None).first()
290 if build:
291 for chroot in copr.active_chroots:
292 release = release_tmpl.format(chroot=chroot)
293 yum_repos[release] = fix_protocol_for_backend(
294 os.path.join(build.results, release + '/'))
295
296 output["detail"] = {
297 "name": copr.name,
298 "additional_repos": copr.repos,
299 "yum_repos": yum_repos,
300 "description": copr.description,
301 "instructions": copr.instructions,
302 "last_modified": builds_logic.BuildsLogic.last_modified(copr),
303 "auto_createrepo": copr.auto_createrepo,
304 "persistent": copr.persistent,
305 "unlisted_on_hp": copr.unlisted_on_hp
306 }
307 return flask.jsonify(output)
308
309
310 @api_ns.route("/coprs/<username>/<coprname>/new_build/", methods=["POST"])
311 @api_login_required
312 @api_req_with_copr
313 -def copr_new_build(copr):
325 return process_creating_new_build(copr, form, create_new_build)
326
327
328 @api_ns.route("/coprs/<username>/<coprname>/new_build_upload/", methods=["POST"])
342 return process_creating_new_build(copr, form, create_new_build)
343
344
345 @api_ns.route("/coprs/<username>/<coprname>/new_build_pypi/", methods=["POST"])
365 return process_creating_new_build(copr, form, create_new_build)
366
367
368 @api_ns.route("/coprs/<username>/<coprname>/new_build_tito/", methods=["POST"])
385 return process_creating_new_build(copr, form, create_new_build)
386
387
388 @api_ns.route("/coprs/<username>/<coprname>/new_build_mock/", methods=["POST"])
405 return process_creating_new_build(copr, form, create_new_build)
406
407
408 @api_ns.route("/coprs/<username>/<coprname>/new_build_rubygems/", methods=["POST"])
422 return process_creating_new_build(copr, form, create_new_build)
423
426 infos = []
427
428
429 for post_key in flask.request.form.keys():
430 if post_key not in form.__dict__.keys():
431 infos.append("Unknown key '{key}' received.".format(key=post_key))
432
433 if not form.validate_on_submit():
434 raise LegacyApiError("Invalid request: bad request parameters: {0}".format(form.errors))
435
436 if not flask.g.user.can_build_in(copr):
437 raise LegacyApiError("Invalid request: user {} is not allowed to build in the copr: {}"
438 .format(flask.g.user.username, copr.full_name))
439
440
441 try:
442
443
444 build = create_new_build()
445 db.session.commit()
446 ids = [build.id] if type(build) != list else [b.id for b in build]
447 infos.append("Build was added to {0}.".format(copr.name))
448
449 except (ActionInProgressException, InsufficientRightsException) as e:
450 raise LegacyApiError("Invalid request: {}".format(e))
451
452 output = {"output": "ok",
453 "ids": ids,
454 "message": "\n".join(infos)}
455
456 return flask.jsonify(output)
457
458
459 @api_ns.route("/coprs/build_status/<int:build_id>/", methods=["GET"])
466
467
468 @api_ns.route("/coprs/build_detail/<int:build_id>/", methods=["GET"])
469 @api_ns.route("/coprs/build/<int:build_id>/", methods=["GET"])
471 build = ComplexLogic.get_build_safe(build_id)
472
473 chroots = {}
474 results_by_chroot = {}
475 for chroot in build.build_chroots:
476 chroots[chroot.name] = chroot.state
477 results_by_chroot[chroot.name] = chroot.result_dir_url
478
479 built_packages = None
480 if build.built_packages:
481 built_packages = build.built_packages.split("\n")
482
483 output = {
484 "output": "ok",
485 "status": build.state,
486 "project": build.copr.name,
487 "owner": build.copr.owner_name,
488 "results": build.results,
489 "built_pkgs": built_packages,
490 "src_version": build.pkg_version,
491 "chroots": chroots,
492 "submitted_on": build.submitted_on,
493 "started_on": build.min_started_on,
494 "ended_on": build.max_ended_on,
495 "src_pkg": build.pkgs,
496 "submitted_by": build.user.name,
497 "results_by_chroot": results_by_chroot
498 }
499 return flask.jsonify(output)
500
501
502 @api_ns.route("/coprs/cancel_build/<int:build_id>/", methods=["POST"])
515
516
517 @api_ns.route('/coprs/<username>/<coprname>/modify/', methods=["POST"])
518 @api_login_required
519 @api_req_with_copr
520 -def copr_modify(copr):
559
560
561 @api_ns.route('/coprs/<username>/<coprname>/modify/<chrootname>/', methods=["POST"])
577
578
579 @api_ns.route('/coprs/<username>/<coprname>/detail/<chrootname>/', methods=["GET"])
585
590 """ Return the list of coprs found in search by the given text.
591 project is taken either from GET params or from the URL itself
592 (in this order).
593
594 :arg project: the text one would like find for coprs.
595
596 """
597 project = flask.request.args.get("project", None) or project
598 if not project:
599 raise LegacyApiError("No project found.")
600
601 try:
602 query = CoprsLogic.get_multiple_fulltext(project)
603
604 repos = query.all()
605 output = {"output": "ok", "repos": []}
606 for repo in repos:
607 output["repos"].append({"username": repo.user.name,
608 "coprname": repo.name,
609 "description": repo.description})
610 except ValueError as e:
611 raise LegacyApiError("Server error: {}".format(e))
612
613 return flask.jsonify(output)
614
618 """ Return list of coprs which are part of playground """
619 query = CoprsLogic.get_playground()
620 repos = query.all()
621 output = {"output": "ok", "repos": []}
622 for repo in repos:
623 output["repos"].append({"username": repo.owner_name,
624 "coprname": repo.name,
625 "chroots": [chroot.name for chroot in repo.active_chroots]})
626
627 jsonout = flask.jsonify(output)
628 jsonout.status_code = 200
629 return jsonout
630
631
632 @api_ns.route("/coprs/<username>/<coprname>/monitor/", methods=["GET"])
633 @api_req_with_copr
634 -def monitor(copr):
638
639
640
641 @api_ns.route("/coprs/<username>/<coprname>/package/add/<source_type_text>/", methods=["POST"])
642 @api_login_required
643 @api_req_with_copr
644 -def copr_add_package(copr, source_type_text):
646
647
648 @api_ns.route("/coprs/<username>/<coprname>/package/<package_name>/edit/<source_type_text>/", methods=["POST"])
649 @api_login_required
650 @api_req_with_copr
651 -def copr_edit_package(copr, package_name, source_type_text):
657
688
691 params = {}
692 if flask.request.args.get('with_latest_build'):
693 params['with_latest_build'] = True
694 if flask.request.args.get('with_latest_succeeded_build'):
695 params['with_latest_succeeded_build'] = True
696 if flask.request.args.get('with_all_builds'):
697 params['with_all_builds'] = True
698 return params
699
702 """
703 A lagging generator to stream JSON so we don't have to hold everything in memory
704 This is a little tricky, as we need to omit the last comma to make valid JSON,
705 thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/
706 """
707 packages = query.__iter__()
708 try:
709 prev_package = next(packages)
710 except StopIteration:
711
712 yield '{"packages": []}'
713 raise StopIteration
714
715 yield '{"packages": ['
716
717 for package in packages:
718 yield json.dumps(prev_package.to_dict(**params)) + ', '
719 prev_package = package
720
721 yield json.dumps(prev_package.to_dict(**params)) + ']}'
722
723
724 @api_ns.route("/coprs/<username>/<coprname>/package/list/", methods=["GET"])
730
731
732
733 @api_ns.route("/coprs/<username>/<coprname>/package/get/<package_name>/", methods=["GET"])
743
744
745 @api_ns.route("/coprs/<username>/<coprname>/package/delete/<package_name>/", methods=["POST"])
765
766
767 @api_ns.route("/coprs/<username>/<coprname>/package/reset/<package_name>/", methods=["POST"])
768 @api_login_required
769 @api_req_with_copr
770 -def copr_reset_package(copr, package_name):
787
788
789 @api_ns.route("/coprs/<username>/<coprname>/package/build/<package_name>/", methods=["POST"])
790 @api_login_required
791 @api_req_with_copr
792 -def copr_build_package(copr, package_name):
793 form = forms.BuildFormRebuildFactory.create_form_cls(copr.active_chroots)(csrf_enabled=False)
794
795 try:
796 package = PackagesLogic.get(copr.id, package_name)[0]
797 except IndexError:
798 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name))
799
800 if form.validate_on_submit():
801 try:
802 build = PackagesLogic.build_package(flask.g.user, copr, package, form.selected_chroots, **form.data)
803 db.session.commit()
804 except (InsufficientRightsException, ActionInProgressException, NoPackageSourceException) as e:
805 raise LegacyApiError(str(e))
806 else:
807 raise LegacyApiError(form.errors)
808
809 return flask.jsonify({
810 "output": "ok",
811 "ids": [build.id],
812 "message": "Build was added to {0}.".format(copr.name)
813 })
814
815
816 @api_ns.route("/coprs/<username>/<coprname>/module/build/", methods=["POST"])
834