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