Package coprs :: Package views :: Package api_ns :: Module api_general
[hide private]
[frames] | no frames]

Source Code for Module coprs.views.api_ns.api_general

   1  import base64 
   2  import datetime 
   3  from functools import wraps 
   4  import json 
   5  import os 
   6  import flask 
   7  import sqlalchemy 
   8  import json 
   9  import requests 
  10  from wtforms import ValidationError 
  11   
  12  from werkzeug import secure_filename 
  13   
  14  from coprs import db 
  15  from coprs import exceptions 
  16  from coprs import forms 
  17  from coprs import helpers 
  18  from coprs import models 
  19  from coprs.helpers import fix_protocol_for_backend, generate_build_config 
  20  from coprs.logic.api_logic import MonitorWrapper 
  21  from coprs.logic.builds_logic import BuildsLogic 
  22  from coprs.logic.complex_logic import ComplexLogic 
  23  from coprs.logic.users_logic import UsersLogic 
  24  from coprs.logic.packages_logic import PackagesLogic 
  25  from coprs.logic.modules_logic import ModulesLogic 
  26   
  27  from coprs.views.misc import login_required, api_login_required 
  28   
  29  from coprs.views.api_ns import api_ns 
  30   
  31  from coprs.logic import builds_logic 
  32  from coprs.logic import coprs_logic 
  33  from coprs.logic.coprs_logic import CoprsLogic 
  34  from coprs.logic.actions_logic import ActionsLogic 
  35   
  36  from coprs.exceptions import (ActionInProgressException, 
  37                                InsufficientRightsException, 
  38                                DuplicateException, 
  39                                LegacyApiError, 
  40                                UnknownSourceTypeException) 
41 42 43 -def api_req_with_copr(f):
44 @wraps(f) 45 def wrapper(username, coprname, **kwargs): 46 if username.startswith("@"): 47 group_name = username[1:] 48 copr = ComplexLogic.get_group_copr_safe(group_name, coprname) 49 else: 50 copr = ComplexLogic.get_copr_safe(username, coprname) 51 52 return f(copr, **kwargs)
53 return wrapper 54
55 56 @api_ns.route("/") 57 -def api_home():
58 """ 59 Render the home page of the api. 60 This page provides information on how to call/use the API. 61 """ 62 63 return flask.render_template("api.html")
64 65 66 @api_ns.route("/new/", methods=["GET", "POST"])
67 @login_required 68 -def api_new_token():
69 """ 70 Generate a new API token for the current user. 71 """ 72 73 user = flask.g.user 74 copr64 = base64.b64encode(b"copr") + b"##" 75 api_login = helpers.generate_api_token( 76 flask.current_app.config["API_TOKEN_LENGTH"] - len(copr64)) 77 user.api_login = api_login 78 user.api_token = helpers.generate_api_token( 79 flask.current_app.config["API_TOKEN_LENGTH"]) 80 user.api_token_expiration = datetime.date.today() + \ 81 datetime.timedelta( 82 days=flask.current_app.config["API_TOKEN_EXPIRATION"]) 83 84 db.session.add(user) 85 db.session.commit() 86 return flask.redirect(flask.url_for("api_ns.api_home"))
87
88 89 -def validate_post_keys(form):
90 infos = [] 91 # TODO: don't use WTFform for parsing and validation here 92 # are there any arguments in POST which our form doesn't know? 93 proxyuser_keys = ["username"] # When user is proxyuser, he can specify username of delegated author 94 allowed = form.__dict__.keys() + proxyuser_keys 95 for post_key in flask.request.form.keys(): 96 if post_key not in allowed: 97 infos.append("Unknown key '{key}' received.".format(key=post_key)) 98 return infos
99 100 101 @api_ns.route("/coprs/<username>/new/", methods=["POST"])
102 @api_login_required 103 -def api_new_copr(username):
104 """ 105 Receive information from the user on how to create its new copr, 106 check their validity and create the corresponding copr. 107 108 :arg name: the name of the copr to add 109 :arg chroots: a comma separated list of chroots to use 110 :kwarg repos: a comma separated list of repository that this copr 111 can use. 112 :kwarg initial_pkgs: a comma separated list of initial packages to 113 build in this new copr 114 115 """ 116 117 form = forms.CoprFormFactory.create_form_cls()(csrf_enabled=False) 118 infos = [] 119 120 # are there any arguments in POST which our form doesn't know? 121 infos.extend(validate_post_keys(form)) 122 123 if form.validate_on_submit(): 124 group = ComplexLogic.get_group_by_name_safe(username[1:]) if username[0] == "@" else None 125 126 auto_prune = True 127 if "auto_prune" in flask.request.form: 128 auto_prune = form.auto_prune.data 129 130 try: 131 copr = CoprsLogic.add( 132 name=form.name.data.strip(), 133 repos=" ".join(form.repos.data.split()), 134 user=flask.g.user, 135 selected_chroots=form.selected_chroots, 136 description=form.description.data, 137 instructions=form.instructions.data, 138 check_for_duplicates=True, 139 disable_createrepo=form.disable_createrepo.data, 140 unlisted_on_hp=form.unlisted_on_hp.data, 141 build_enable_net=form.build_enable_net.data, 142 group=group, 143 persistent=form.persistent.data, 144 auto_prune=auto_prune, 145 ) 146 infos.append("New project was successfully created.") 147 148 if form.initial_pkgs.data: 149 pkgs = form.initial_pkgs.data.split() 150 for pkg in pkgs: 151 builds_logic.BuildsLogic.add( 152 user=flask.g.user, 153 pkgs=pkg, 154 copr=copr) 155 156 infos.append("Initial packages were successfully " 157 "submitted for building.") 158 159 output = {"output": "ok", "message": "\n".join(infos)} 160 db.session.commit() 161 except (exceptions.DuplicateException, 162 exceptions.NonAdminCannotCreatePersistentProject, 163 exceptions.NonAdminCannotDisableAutoPrunning) as err: 164 db.session.rollback() 165 raise LegacyApiError(str(err)) 166 167 else: 168 errormsg = "Validation error\n" 169 if form.errors: 170 for field, emsgs in form.errors.items(): 171 errormsg += "- {0}: {1}\n".format(field, "\n".join(emsgs)) 172 173 errormsg = errormsg.replace('"', "'") 174 raise LegacyApiError(errormsg) 175 176 return flask.jsonify(output)
177 178 179 @api_ns.route("/coprs/<username>/<coprname>/delete/", methods=["POST"])
180 @api_login_required 181 @api_req_with_copr 182 -def api_copr_delete(copr):
183 """ Deletes selected user's project 184 """ 185 form = forms.CoprDeleteForm(csrf_enabled=False) 186 httpcode = 200 187 188 if form.validate_on_submit() and copr: 189 try: 190 ComplexLogic.delete_copr(copr) 191 except (exceptions.ActionInProgressException, 192 exceptions.InsufficientRightsException) as err: 193 194 db.session.rollback() 195 raise LegacyApiError(str(err)) 196 else: 197 message = "Project {} has been deleted.".format(copr.name) 198 output = {"output": "ok", "message": message} 199 db.session.commit() 200 else: 201 raise LegacyApiError("Invalid request: {0}".format(form.errors)) 202 203 return flask.jsonify(output)
204 205 206 @api_ns.route("/coprs/<username>/<coprname>/fork/", methods=["POST"])
207 @api_login_required 208 @api_req_with_copr 209 -def api_copr_fork(copr):
210 """ Fork the project and builds in it 211 """ 212 form = forms.CoprForkFormFactory\ 213 .create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)(csrf_enabled=False) 214 215 if form.validate_on_submit() and copr: 216 try: 217 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0] 218 if flask.g.user.name != form.owner.data and not dstgroup: 219 return LegacyApiError("There is no such group: {}".format(form.owner.data)) 220 221 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup) 222 if created: 223 msg = ("Forking project {} for you into {}.\nPlease be aware that it may take a few minutes " 224 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name)) 225 elif not created and form.confirm.data == True: 226 msg = ("Updating packages in {} from {}.\nPlease be aware that it may take a few minutes " 227 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name)) 228 else: 229 raise LegacyApiError("You are about to fork into existing project: {}\n" 230 "Please use --confirm if you really want to do this".format(fcopr.full_name)) 231 232 output = {"output": "ok", "message": msg} 233 db.session.commit() 234 235 except (exceptions.ActionInProgressException, 236 exceptions.InsufficientRightsException) as err: 237 db.session.rollback() 238 raise LegacyApiError(str(err)) 239 else: 240 raise LegacyApiError("Invalid request: {0}".format(form.errors)) 241 242 return flask.jsonify(output)
243
244 245 @api_ns.route("/coprs/") 246 @api_ns.route("/coprs/<username>/") 247 -def api_coprs_by_owner(username=None):
248 """ Return the list of coprs owned by the given user. 249 username is taken either from GET params or from the URL itself 250 (in this order). 251 252 :arg username: the username of the person one would like to the 253 coprs of. 254 255 """ 256 username = flask.request.args.get("username", None) or username 257 if username is None: 258 raise LegacyApiError("Invalid request: missing `username` ") 259 260 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}" 261 262 if username.startswith("@"): 263 group_name = username[1:] 264 query = CoprsLogic.get_multiple() 265 query = CoprsLogic.filter_by_group_name(query, group_name) 266 else: 267 query = CoprsLogic.get_multiple_owned_by_username(username) 268 269 query = CoprsLogic.join_builds(query) 270 query = CoprsLogic.set_query_order(query) 271 272 repos = query.all() 273 output = {"output": "ok", "repos": []} 274 for repo in repos: 275 yum_repos = {} 276 for build in repo.builds: 277 if build.results: 278 for chroot in repo.active_chroots: 279 release = release_tmpl.format(chroot=chroot) 280 yum_repos[release] = fix_protocol_for_backend( 281 os.path.join(build.results, release + '/')) 282 break 283 284 output["repos"].append({"name": repo.name, 285 "additional_repos": repo.repos, 286 "yum_repos": yum_repos, 287 "description": repo.description, 288 "instructions": repo.instructions, 289 "persistent": repo.persistent, 290 "unlisted_on_hp": repo.unlisted_on_hp, 291 "auto_prune": repo.auto_prune, 292 }) 293 294 return flask.jsonify(output)
295
296 297 @api_ns.route("/coprs/<username>/<coprname>/detail/") 298 @api_req_with_copr 299 -def api_coprs_by_owner_detail(copr):
300 """ Return detail of one project. 301 302 :arg username: the username of the person one would like to the 303 coprs of. 304 :arg coprname: the name of project. 305 306 """ 307 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}" 308 output = {"output": "ok", "detail": {}} 309 yum_repos = {} 310 311 build = models.Build.query.filter( 312 models.Build.copr_id == copr.id, models.Build.results != None).first() 313 314 if build: 315 for chroot in copr.active_chroots: 316 release = release_tmpl.format(chroot=chroot) 317 yum_repos[release] = fix_protocol_for_backend( 318 os.path.join(build.results, release + '/')) 319 320 output["detail"] = { 321 "name": copr.name, 322 "additional_repos": copr.repos, 323 "yum_repos": yum_repos, 324 "description": copr.description, 325 "instructions": copr.instructions, 326 "last_modified": builds_logic.BuildsLogic.last_modified(copr), 327 "auto_createrepo": copr.auto_createrepo, 328 "persistent": copr.persistent, 329 "unlisted_on_hp": copr.unlisted_on_hp, 330 "auto_prune": copr.auto_prune, 331 } 332 return flask.jsonify(output)
333 334 335 @api_ns.route("/coprs/<username>/<coprname>/new_build/", methods=["POST"])
336 @api_login_required 337 @api_req_with_copr 338 -def copr_new_build(copr):
339 form = forms.BuildFormUrlFactory(copr.active_chroots)(csrf_enabled=False) 340 341 def create_new_build(): 342 # create separate build for each package 343 pkgs = form.pkgs.data.split("\n") 344 return [BuildsLogic.create_new_from_url( 345 flask.g.user, copr, 346 srpm_url=pkg, 347 chroot_names=form.selected_chroots, 348 background=form.background.data, 349 ) for pkg in pkgs]
350 return process_creating_new_build(copr, form, create_new_build) 351 352 353 @api_ns.route("/coprs/<username>/<coprname>/new_build_upload/", methods=["POST"])
354 @api_login_required 355 @api_req_with_copr 356 -def copr_new_build_upload(copr):
357 form = forms.BuildFormUploadFactory(copr.active_chroots)(csrf_enabled=False) 358 359 def create_new_build(): 360 return BuildsLogic.create_new_from_upload( 361 flask.g.user, copr, 362 f_uploader=lambda path: form.pkgs.data.save(path), 363 orig_filename=secure_filename(form.pkgs.data.filename), 364 chroot_names=form.selected_chroots, 365 background=form.background.data, 366 )
367 return process_creating_new_build(copr, form, create_new_build) 368 369 370 @api_ns.route("/coprs/<username>/<coprname>/new_build_pypi/", methods=["POST"])
371 @api_login_required 372 @api_req_with_copr 373 -def copr_new_build_pypi(copr):
374 form = forms.BuildFormPyPIFactory(copr.active_chroots)(csrf_enabled=False) 375 376 # TODO: automatically prepopulate all form fields with their defaults 377 if not form.python_versions.data: 378 form.python_versions.data = form.python_versions.default 379 380 def create_new_build(): 381 return BuildsLogic.create_new_from_pypi( 382 flask.g.user, 383 copr, 384 form.pypi_package_name.data, 385 form.pypi_package_version.data, 386 form.python_versions.data, 387 form.selected_chroots, 388 background=form.background.data, 389 )
390 return process_creating_new_build(copr, form, create_new_build) 391 392 393 @api_ns.route("/coprs/<username>/<coprname>/new_build_tito/", methods=["POST"])
394 @api_login_required 395 @api_req_with_copr 396 -def copr_new_build_tito(copr):
397 form = forms.BuildFormTitoFactory(copr.active_chroots)(csrf_enabled=False) 398 399 def create_new_build(): 400 return BuildsLogic.create_new_from_tito( 401 flask.g.user, 402 copr, 403 form.git_url.data, 404 form.git_directory.data, 405 form.git_branch.data, 406 form.tito_test.data, 407 form.selected_chroots, 408 background=form.background.data, 409 )
410 return process_creating_new_build(copr, form, create_new_build) 411 412 413 @api_ns.route("/coprs/<username>/<coprname>/new_build_mock/", methods=["POST"])
414 @api_login_required 415 @api_req_with_copr 416 -def copr_new_build_mock(copr):
417 form = forms.BuildFormMockFactory(copr.active_chroots)(csrf_enabled=False) 418 419 def create_new_build(): 420 return BuildsLogic.create_new_from_mock( 421 flask.g.user, 422 copr, 423 form.scm_type.data, 424 form.scm_url.data, 425 form.scm_branch.data, 426 form.spec.data, 427 form.selected_chroots, 428 background=form.background.data, 429 )
430 return process_creating_new_build(copr, form, create_new_build) 431 432 433 @api_ns.route("/coprs/<username>/<coprname>/new_build_rubygems/", methods=["POST"])
434 @api_login_required 435 @api_req_with_copr 436 -def copr_new_build_rubygems(copr):
437 form = forms.BuildFormRubyGemsFactory(copr.active_chroots)(csrf_enabled=False) 438 439 def create_new_build(): 440 return BuildsLogic.create_new_from_rubygems( 441 flask.g.user, 442 copr, 443 form.gem_name.data, 444 form.selected_chroots, 445 background=form.background.data, 446 )
447 return process_creating_new_build(copr, form, create_new_build) 448 449 450 @api_ns.route("/coprs/<username>/<coprname>/new_build_distgit/", methods=["POST"])
451 @api_login_required 452 @api_req_with_copr 453 -def copr_new_build_distgit(copr):
454 form = forms.BuildFormDistGitFactory(copr.active_chroots)(csrf_enabled=False) 455 456 def create_new_build(): 457 return BuildsLogic.create_new_from_distgit( 458 flask.g.user, 459 copr, 460 form.clone_url.data, 461 form.branch.data, 462 form.selected_chroots, 463 background=form.background.data, 464 )
465 return process_creating_new_build(copr, form, create_new_build) 466
467 468 -def process_creating_new_build(copr, form, create_new_build):
469 infos = [] 470 471 # are there any arguments in POST which our form doesn't know? 472 infos.extend(validate_post_keys(form)) 473 474 if not form.validate_on_submit(): 475 raise LegacyApiError("Invalid request: bad request parameters: {0}".format(form.errors)) 476 477 if not flask.g.user.can_build_in(copr): 478 raise LegacyApiError("Invalid request: user {} is not allowed to build in the copr: {}" 479 .format(flask.g.user.username, copr.full_name)) 480 481 # create a new build 482 try: 483 # From URLs it can be created multiple builds at once 484 # so it can return a list 485 build = create_new_build() 486 db.session.commit() 487 ids = [build.id] if type(build) != list else [b.id for b in build] 488 infos.append("Build was added to {0}:".format(copr.name)) 489 for build_id in ids: 490 infos.append(" " + flask.url_for("coprs_ns.copr_build_redirect", 491 build_id=build_id, 492 _external=True)) 493 494 except (ActionInProgressException, InsufficientRightsException) as e: 495 raise LegacyApiError("Invalid request: {}".format(e)) 496 497 output = {"output": "ok", 498 "ids": ids, 499 "message": "\n".join(infos)} 500 501 return flask.jsonify(output)
502 503 504 @api_ns.route("/coprs/build_status/<int:build_id>/", methods=["GET"])
505 @api_login_required 506 -def build_status(build_id):
507 build = ComplexLogic.get_build_safe(build_id) 508 output = {"output": "ok", 509 "status": build.state} 510 return flask.jsonify(output)
511 512 513 @api_ns.route("/coprs/build_detail/<int:build_id>/", methods=["GET"]) 514 @api_ns.route("/coprs/build/<int:build_id>/", methods=["GET"])
515 -def build_detail(build_id):
516 build = ComplexLogic.get_build_safe(build_id) 517 518 chroots = {} 519 results_by_chroot = {} 520 for chroot in build.build_chroots: 521 chroots[chroot.name] = chroot.state 522 results_by_chroot[chroot.name] = chroot.result_dir_url 523 524 built_packages = None 525 if build.built_packages: 526 built_packages = build.built_packages.split("\n") 527 528 output = { 529 "output": "ok", 530 "status": build.state, 531 "project": build.copr.name, 532 "owner": build.copr.owner_name, 533 "results": build.results, 534 "built_pkgs": built_packages, 535 "src_version": build.pkg_version, 536 "chroots": chroots, 537 "submitted_on": build.submitted_on, 538 "started_on": build.min_started_on, 539 "ended_on": build.max_ended_on, 540 "src_pkg": build.pkgs, 541 "submitted_by": build.user.name, 542 "results_by_chroot": results_by_chroot 543 } 544 return flask.jsonify(output)
545 546 547 @api_ns.route("/coprs/cancel_build/<int:build_id>/", methods=["POST"])
548 @api_login_required 549 -def cancel_build(build_id):
550 build = ComplexLogic.get_build_safe(build_id) 551 552 try: 553 builds_logic.BuildsLogic.cancel_build(flask.g.user, build) 554 db.session.commit() 555 except exceptions.InsufficientRightsException as e: 556 raise LegacyApiError("Invalid request: {}".format(e)) 557 558 output = {'output': 'ok', 'status': "Build canceled"} 559 return flask.jsonify(output)
560 561 562 @api_ns.route("/coprs/delete_build/<int:build_id>/", methods=["POST"])
563 @api_login_required 564 -def delete_build(build_id):
565 build = ComplexLogic.get_build_safe(build_id) 566 567 try: 568 builds_logic.BuildsLogic.delete_build(flask.g.user, build) 569 db.session.commit() 570 except (exceptions.InsufficientRightsException,exceptions.ActionInProgressException) as e: 571 raise LegacyApiError("Invalid request: {}".format(e)) 572 573 output = {'output': 'ok', 'status': "Build deleted"} 574 return flask.jsonify(output)
575 576 577 @api_ns.route('/coprs/<username>/<coprname>/modify/', methods=["POST"])
578 @api_login_required 579 @api_req_with_copr 580 -def copr_modify(copr):
581 form = forms.CoprModifyForm(csrf_enabled=False) 582 583 if not form.validate_on_submit(): 584 raise LegacyApiError("Invalid request: {0}".format(form.errors)) 585 586 # .raw_data needs to be inspected to figure out whether the field 587 # was not sent or was sent empty 588 if form.description.raw_data and len(form.description.raw_data): 589 copr.description = form.description.data 590 if form.instructions.raw_data and len(form.instructions.raw_data): 591 copr.instructions = form.instructions.data 592 if form.repos.raw_data and len(form.repos.raw_data): 593 copr.repos = form.repos.data 594 if form.disable_createrepo.raw_data and len(form.disable_createrepo.raw_data): 595 copr.disable_createrepo = form.disable_createrepo.data 596 597 if "unlisted_on_hp" in flask.request.form: 598 copr.unlisted_on_hp = form.unlisted_on_hp.data 599 if "build_enable_net" in flask.request.form: 600 copr.build_enable_net = form.build_enable_net.data 601 if "auto_prune" in flask.request.form: 602 copr.auto_prune = form.auto_prune.data 603 604 try: 605 CoprsLogic.update(flask.g.user, copr) 606 if copr.group: # load group.id 607 _ = copr.group.id 608 db.session.commit() 609 except (exceptions.ActionInProgressException, 610 exceptions.InsufficientRightsException, 611 exceptions.NonAdminCannotDisableAutoPrunning) as e: 612 db.session.rollback() 613 raise LegacyApiError("Invalid request: {}".format(e)) 614 615 output = { 616 'output': 'ok', 617 'description': copr.description, 618 'instructions': copr.instructions, 619 'repos': copr.repos, 620 } 621 622 return flask.jsonify(output)
623 624 625 @api_ns.route('/coprs/<username>/<coprname>/modify/<chrootname>/', methods=["POST"])
626 @api_login_required 627 @api_req_with_copr 628 -def copr_modify_chroot(copr, chrootname):
629 """Deprecated to copr_edit_chroot""" 630 form = forms.ModifyChrootForm(csrf_enabled=False) 631 # chroot = coprs_logic.MockChrootsLogic.get_from_name(chrootname, active_only=True).first() 632 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname) 633 634 if not form.validate_on_submit(): 635 raise LegacyApiError("Invalid request: {0}".format(form.errors)) 636 else: 637 coprs_logic.CoprChrootsLogic.update_chroot(flask.g.user, chroot, form.buildroot_pkgs.data) 638 db.session.commit() 639 640 output = {'output': 'ok', 'buildroot_pkgs': chroot.buildroot_pkgs} 641 return flask.jsonify(output)
642 643 644 @api_ns.route('/coprs/<username>/<coprname>/chroot/edit/<chrootname>/', methods=["POST"])
645 @api_login_required 646 @api_req_with_copr 647 -def copr_edit_chroot(copr, chrootname):
648 form = forms.ModifyChrootForm(csrf_enabled=False) 649 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname) 650 651 if not form.validate_on_submit(): 652 raise LegacyApiError("Invalid request: {0}".format(form.errors)) 653 else: 654 buildroot_pkgs = repos = comps_xml = comps_name = None 655 if "buildroot_pkgs" in flask.request.form: 656 buildroot_pkgs = form.buildroot_pkgs.data 657 if "repos" in flask.request.form: 658 repos = form.repos.data 659 if form.upload_comps.has_file(): 660 comps_xml = form.upload_comps.data.stream.read() 661 comps_name = form.upload_comps.data.filename 662 if form.delete_comps.data: 663 coprs_logic.CoprChrootsLogic.remove_comps(flask.g.user, chroot) 664 coprs_logic.CoprChrootsLogic.update_chroot( 665 flask.g.user, chroot, buildroot_pkgs, repos, comps=comps_xml, comps_name=comps_name) 666 db.session.commit() 667 668 output = { 669 "output": "ok", 670 "message": "Edit chroot operation was successful.", 671 "chroot": chroot.to_dict(), 672 } 673 return flask.jsonify(output)
674 675 676 @api_ns.route('/coprs/<username>/<coprname>/detail/<chrootname>/', methods=["GET"])
677 @api_req_with_copr 678 -def copr_chroot_details(copr, chrootname):
679 """Deprecated to copr_get_chroot""" 680 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname) 681 output = {'output': 'ok', 'buildroot_pkgs': chroot.buildroot_pkgs} 682 return flask.jsonify(output)
683 684 @api_ns.route('/coprs/<username>/<coprname>/chroot/get/<chrootname>/', methods=["GET"])
685 @api_req_with_copr 686 -def copr_get_chroot(copr, chrootname):
687 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname) 688 output = {'output': 'ok', 'chroot': chroot.to_dict()} 689 return flask.jsonify(output)
690
691 @api_ns.route("/coprs/search/") 692 @api_ns.route("/coprs/search/<project>/") 693 -def api_coprs_search_by_project(project=None):
694 """ Return the list of coprs found in search by the given text. 695 project is taken either from GET params or from the URL itself 696 (in this order). 697 698 :arg project: the text one would like find for coprs. 699 700 """ 701 project = flask.request.args.get("project", None) or project 702 if not project: 703 raise LegacyApiError("No project found.") 704 705 try: 706 query = CoprsLogic.get_multiple_fulltext(project) 707 708 repos = query.all() 709 output = {"output": "ok", "repos": []} 710 for repo in repos: 711 output["repos"].append({"username": repo.user.name, 712 "coprname": repo.name, 713 "description": repo.description}) 714 except ValueError as e: 715 raise LegacyApiError("Server error: {}".format(e)) 716 717 return flask.jsonify(output)
718
719 720 @api_ns.route("/playground/list/") 721 -def playground_list():
722 """ Return list of coprs which are part of playground """ 723 query = CoprsLogic.get_playground() 724 repos = query.all() 725 output = {"output": "ok", "repos": []} 726 for repo in repos: 727 output["repos"].append({"username": repo.owner_name, 728 "coprname": repo.name, 729 "chroots": [chroot.name for chroot in repo.active_chroots]}) 730 731 jsonout = flask.jsonify(output) 732 jsonout.status_code = 200 733 return jsonout
734 735 736 @api_ns.route("/coprs/<username>/<coprname>/monitor/", methods=["GET"])
737 @api_req_with_copr 738 -def monitor(copr):
739 monitor_data = builds_logic.BuildsMonitorLogic.get_monitor_data(copr) 740 output = MonitorWrapper(copr, monitor_data).to_dict() 741 return flask.jsonify(output)
742 743 ############################################################################### 744 745 @api_ns.route("/coprs/<username>/<coprname>/package/add/<source_type_text>/", methods=["POST"])
746 @api_login_required 747 @api_req_with_copr 748 -def copr_add_package(copr, source_type_text):
749 return process_package_add_or_edit(copr, source_type_text)
750 751 752 @api_ns.route("/coprs/<username>/<coprname>/package/<package_name>/edit/<source_type_text>/", methods=["POST"])
753 @api_login_required 754 @api_req_with_copr 755 -def copr_edit_package(copr, package_name, source_type_text):
756 try: 757 package = PackagesLogic.get(copr.id, package_name)[0] 758 except IndexError: 759 raise LegacyApiError("Package {name} does not exists in copr {copr}.".format(name=package_name, copr=copr.full_name)) 760 return process_package_add_or_edit(copr, source_type_text, package=package)
761
762 763 -def process_package_add_or_edit(copr, source_type_text, package=None):
764 try: 765 form = forms.get_package_form_cls_by_source_type_text(source_type_text)(csrf_enabled=False) 766 except UnknownSourceTypeException: 767 raise LegacyApiError("Unsupported package source type {source_type_text}".format(source_type_text=source_type_text)) 768 769 if form.validate_on_submit(): 770 if not package: 771 try: 772 package = PackagesLogic.add(flask.app.g.user, copr, form.package_name.data) 773 except InsufficientRightsException: 774 raise LegacyApiError("Insufficient permissions.") 775 except DuplicateException: 776 raise LegacyApiError("Package {0} already exists in copr {1}.".format(form.package_name.data, copr.full_name)) 777 778 package.source_type = helpers.BuildSourceEnum(source_type_text) 779 package.source_json = form.source_json 780 if "webhook_rebuild" in flask.request.form: 781 package.webhook_rebuild = form.webhook_rebuild.data 782 783 db.session.add(package) 784 db.session.commit() 785 else: 786 raise LegacyApiError(form.errors) 787 788 return flask.jsonify({ 789 "output": "ok", 790 "message": "Create or edit operation was successful.", 791 "package": package.to_dict(), 792 })
793
794 795 -def get_package_record_params():
796 params = {} 797 if flask.request.args.get('with_latest_build'): 798 params['with_latest_build'] = True 799 if flask.request.args.get('with_latest_succeeded_build'): 800 params['with_latest_succeeded_build'] = True 801 if flask.request.args.get('with_all_builds'): 802 params['with_all_builds'] = True 803 return params
804
805 806 -def generate_package_list(query, params):
807 """ 808 A lagging generator to stream JSON so we don't have to hold everything in memory 809 This is a little tricky, as we need to omit the last comma to make valid JSON, 810 thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/ 811 """ 812 packages = query.__iter__() 813 try: 814 prev_package = next(packages) # get first result 815 except StopIteration: 816 # StopIteration here means the length was zero, so yield a valid packages doc and stop 817 yield '{"packages": []}' 818 raise StopIteration 819 # We have some packages. First, yield the opening json 820 yield '{"packages": [' 821 # Iterate over the packages 822 for package in packages: 823 yield json.dumps(prev_package.to_dict(**params)) + ', ' 824 prev_package = package 825 # Now yield the last iteration without comma but with the closing brackets 826 yield json.dumps(prev_package.to_dict(**params)) + ']}'
827 828 829 @api_ns.route("/coprs/<username>/<coprname>/package/list/", methods=["GET"])
830 @api_req_with_copr 831 -def copr_list_packages(copr):
832 packages = PackagesLogic.get_all(copr.id) 833 params = get_package_record_params() 834 return flask.Response(generate_package_list(packages, params), content_type='application/json')
835 #return flask.jsonify({"packages": [package.to_dict(**params) for package in packages]}) 836 837 838 @api_ns.route("/coprs/<username>/<coprname>/package/get/<package_name>/", methods=["GET"])
839 @api_req_with_copr 840 -def copr_get_package(copr, package_name):
841 try: 842 package = PackagesLogic.get(copr.id, package_name)[0] 843 except IndexError: 844 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name)) 845 846 params = get_package_record_params() 847 return flask.jsonify({'package': package.to_dict(**params)})
848 849 850 @api_ns.route("/coprs/<username>/<coprname>/package/delete/<package_name>/", methods=["POST"])
851 @api_login_required 852 @api_req_with_copr 853 -def copr_delete_package(copr, package_name):
854 try: 855 package = PackagesLogic.get(copr.id, package_name)[0] 856 except IndexError: 857 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name)) 858 859 try: 860 PackagesLogic.delete_package(flask.g.user, package) 861 db.session.commit() 862 except (InsufficientRightsException, ActionInProgressException) as e: 863 raise LegacyApiError(str(e)) 864 865 return flask.jsonify({ 866 "output": "ok", 867 "message": "Package was successfully deleted.", 868 'package': package.to_dict(), 869 })
870 871 872 @api_ns.route("/coprs/<username>/<coprname>/package/reset/<package_name>/", methods=["POST"])
873 @api_login_required 874 @api_req_with_copr 875 -def copr_reset_package(copr, package_name):
876 try: 877 package = PackagesLogic.get(copr.id, package_name)[0] 878 except IndexError: 879 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name)) 880 881 try: 882 PackagesLogic.reset_package(flask.g.user, package) 883 db.session.commit() 884 except InsufficientRightsException as e: 885 raise LegacyApiError(str(e)) 886 887 return flask.jsonify({ 888 "output": "ok", 889 "message": "Package's default source was successfully reseted.", 890 'package': package.to_dict(), 891 })
892 893 894 @api_ns.route("/coprs/<username>/<coprname>/package/build/<package_name>/", methods=["POST"])
895 @api_login_required 896 @api_req_with_copr 897 -def copr_build_package(copr, package_name):
898 form = forms.BuildFormRebuildFactory.create_form_cls(copr.active_chroots)(csrf_enabled=False) 899 900 try: 901 package = PackagesLogic.get(copr.id, package_name)[0] 902 except IndexError: 903 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name)) 904 905 if form.validate_on_submit(): 906 try: 907 build = PackagesLogic.build_package(flask.g.user, copr, package, form.selected_chroots, **form.data) 908 db.session.commit() 909 except (InsufficientRightsException, ActionInProgressException, NoPackageSourceException) as e: 910 raise LegacyApiError(str(e)) 911 else: 912 raise LegacyApiError(form.errors) 913 914 return flask.jsonify({ 915 "output": "ok", 916 "ids": [build.id], 917 "message": "Build was added to {0}.".format(copr.name) 918 })
919 920 921 @api_ns.route("/module/build/", methods=["POST"])
922 @api_login_required 923 -def copr_build_module():
924 form = forms.ModuleBuildForm(csrf_enabled=False) 925 if not form.validate_on_submit(): 926 raise LegacyApiError(form.errors) 927 928 try: 929 common = {"owner": flask.g.user.name, 930 "copr_owner": form.copr_owner.data, 931 "copr_project": form.copr_project.data} 932 if form.scmurl.data: 933 kwargs = {"json": dict({"scmurl": form.scmurl.data, "branch": form.branch.data}, **common)} 934 else: 935 kwargs = {"data": common, "files": {"yaml": (form.modulemd.data.filename, form.modulemd.data)}} 936 937 response = requests.post(flask.current_app.config["MBS_URL"], verify=False, **kwargs) 938 if response.status_code == 500: 939 raise LegacyApiError("Error from MBS: {} - {}".format(response.status_code, response.reason)) 940 941 resp = json.loads(response.content) 942 if response.status_code != 201: 943 raise LegacyApiError("Error from MBS: {}".format(resp["message"])) 944 945 return flask.jsonify({ 946 "output": "ok", 947 "message": "Created module {}-{}-{}".format(resp["name"], resp["stream"], resp["version"]), 948 }) 949 950 except requests.ConnectionError: 951 raise LegacyApiError("Can't connect to MBS instance")
952 953 954 @api_ns.route("/coprs/<username>/<coprname>/module/make/", methods=["POST"])
955 @api_login_required 956 @api_req_with_copr 957 -def copr_make_module(copr):
958 form = forms.ModuleFormUploadFactory(csrf_enabled=False) 959 if not form.validate_on_submit(): 960 # @TODO Prettier error 961 raise LegacyApiError(form.errors) 962 963 modulemd = form.modulemd.data.read() 964 module = ModulesLogic.from_modulemd(modulemd) 965 try: 966 ModulesLogic.validate(modulemd) 967 msg = "Nothing happened" 968 if form.create.data: 969 module = ModulesLogic.add(flask.g.user, copr, module) 970 db.session.flush() 971 msg = "Module was created" 972 973 if form.build.data: 974 if not module.id: 975 module = ModulesLogic.get_by_nsv(copr, module.name, module.stream, module.version).one() 976 ActionsLogic.send_build_module(flask.g.user, copr, module) 977 msg = "Module build was submitted" 978 db.session.commit() 979 980 return flask.jsonify({ 981 "output": "ok", 982 "message": msg, 983 "modulemd": modulemd, 984 }) 985 986 except sqlalchemy.exc.IntegrityError: 987 raise LegacyApiError({"nsv": ["Module {} already exists".format(module.nsv)]}) 988 989 except sqlalchemy.orm.exc.NoResultFound: 990 raise LegacyApiError({"nsv": ["Module {} doesn't exist. You need to create it first".format(module.nsv)]}) 991 992 except ValidationError as ex: 993 raise LegacyApiError({"nsv": [ex.message]})
994 995 996 @api_ns.route("/coprs/<username>/<coprname>/build-config/<chroot>/", methods=["GET"]) 997 @api_ns.route("/g/<group_name>/<coprname>/build-config/<chroot>/", methods=["GET"])
998 @api_req_with_copr 999 -def copr_build_config(copr, chroot):
1000 """ 1001 Generate build configuration. 1002 """ 1003 output = { 1004 "output": "ok", 1005 "build_config": generate_build_config(copr, chroot), 1006 } 1007 1008 if not output['build_config']: 1009 raise LegacyApiError('Chroot not found.') 1010 1011 return flask.jsonify(output)
1012 1013 1014 @api_ns.route("/module/repo/", methods=["POST"])
1015 -def copr_module_repo():
1016 """ 1017 :return: URL to a DNF repository for the module 1018 """ 1019 form = forms.ModuleRepo(csrf_enabled=False) 1020 if not form.validate_on_submit(): 1021 raise LegacyApiError(form.errors) 1022 1023 copr = ComplexLogic.get_copr_by_owner_safe(form.owner.data, form.copr.data) 1024 nvs = [form.name.data, form.stream.data, form.version.data] 1025 module = ModulesLogic.get_by_nsv(copr, *nvs).first() 1026 if not module: 1027 raise LegacyApiError("No module {}".format("-".join(nvs))) 1028 1029 return flask.jsonify({"output": "ok", "repo": module.repo_url(form.arch.data)})
1030