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 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.scm_subdir.data, 427 form.spec.data, 428 form.selected_chroots, 429 background=form.background.data, 430 )
431 return process_creating_new_build(copr, form, create_new_build) 432 433 434 @api_ns.route("/coprs/<username>/<coprname>/new_build_rubygems/", methods=["POST"])
435 @api_login_required 436 @api_req_with_copr 437 -def copr_new_build_rubygems(copr):
438 form = forms.BuildFormRubyGemsFactory(copr.active_chroots)(csrf_enabled=False) 439 440 def create_new_build(): 441 return BuildsLogic.create_new_from_rubygems( 442 flask.g.user, 443 copr, 444 form.gem_name.data, 445 form.selected_chroots, 446 background=form.background.data, 447 )
448 return process_creating_new_build(copr, form, create_new_build) 449 450 451 @api_ns.route("/coprs/<username>/<coprname>/new_build_distgit/", methods=["POST"])
452 @api_login_required 453 @api_req_with_copr 454 -def copr_new_build_distgit(copr):
455 form = forms.BuildFormDistGitFactory(copr.active_chroots)(csrf_enabled=False) 456 457 def create_new_build(): 458 return BuildsLogic.create_new_from_distgit( 459 flask.g.user, 460 copr, 461 form.clone_url.data, 462 form.branch.data, 463 form.selected_chroots, 464 background=form.background.data, 465 )
466 return process_creating_new_build(copr, form, create_new_build) 467
468 469 -def process_creating_new_build(copr, form, create_new_build):
470 infos = [] 471 472 # are there any arguments in POST which our form doesn't know? 473 infos.extend(validate_post_keys(form)) 474 475 if not form.validate_on_submit(): 476 raise LegacyApiError("Invalid request: bad request parameters: {0}".format(form.errors)) 477 478 if not flask.g.user.can_build_in(copr): 479 raise LegacyApiError("Invalid request: user {} is not allowed to build in the copr: {}" 480 .format(flask.g.user.username, copr.full_name)) 481 482 # create a new build 483 try: 484 # From URLs it can be created multiple builds at once 485 # so it can return a list 486 build = create_new_build() 487 db.session.commit() 488 ids = [build.id] if type(build) != list else [b.id for b in build] 489 infos.append("Build was added to {0}:".format(copr.name)) 490 for build_id in ids: 491 infos.append(" " + flask.url_for("coprs_ns.copr_build_redirect", 492 build_id=build_id, 493 _external=True)) 494 495 except (ActionInProgressException, InsufficientRightsException) as e: 496 raise LegacyApiError("Invalid request: {}".format(e)) 497 498 output = {"output": "ok", 499 "ids": ids, 500 "message": "\n".join(infos)} 501 502 return flask.jsonify(output)
503 504 505 @api_ns.route("/coprs/build_status/<int:build_id>/", methods=["GET"])
506 @api_login_required 507 -def build_status(build_id):
508 build = ComplexLogic.get_build_safe(build_id) 509 output = {"output": "ok", 510 "status": build.state} 511 return flask.jsonify(output)
512 513 514 @api_ns.route("/coprs/build_detail/<int:build_id>/", methods=["GET"]) 515 @api_ns.route("/coprs/build/<int:build_id>/", methods=["GET"])
516 -def build_detail(build_id):
517 build = ComplexLogic.get_build_safe(build_id) 518 519 chroots = {} 520 results_by_chroot = {} 521 for chroot in build.build_chroots: 522 chroots[chroot.name] = chroot.state 523 results_by_chroot[chroot.name] = chroot.result_dir_url 524 525 built_packages = None 526 if build.built_packages: 527 built_packages = build.built_packages.split("\n") 528 529 output = { 530 "output": "ok", 531 "status": build.state, 532 "project": build.copr.name, 533 "owner": build.copr.owner_name, 534 "results": build.results, 535 "built_pkgs": built_packages, 536 "src_version": build.pkg_version, 537 "chroots": chroots, 538 "submitted_on": build.submitted_on, 539 "started_on": build.min_started_on, 540 "ended_on": build.max_ended_on, 541 "src_pkg": build.pkgs, 542 "submitted_by": build.user.name, 543 "results_by_chroot": results_by_chroot 544 } 545 return flask.jsonify(output)
546 547 548 @api_ns.route("/coprs/cancel_build/<int:build_id>/", methods=["POST"])
549 @api_login_required 550 -def cancel_build(build_id):
551 build = ComplexLogic.get_build_safe(build_id) 552 553 try: 554 builds_logic.BuildsLogic.cancel_build(flask.g.user, build) 555 db.session.commit() 556 except exceptions.InsufficientRightsException as e: 557 raise LegacyApiError("Invalid request: {}".format(e)) 558 559 output = {'output': 'ok', 'status': "Build canceled"} 560 return flask.jsonify(output)
561 562 563 @api_ns.route("/coprs/delete_build/<int:build_id>/", methods=["POST"])
564 @api_login_required 565 -def delete_build(build_id):
566 build = ComplexLogic.get_build_safe(build_id) 567 568 try: 569 builds_logic.BuildsLogic.delete_build(flask.g.user, build) 570 db.session.commit() 571 except (exceptions.InsufficientRightsException,exceptions.ActionInProgressException) as e: 572 raise LegacyApiError("Invalid request: {}".format(e)) 573 574 output = {'output': 'ok', 'status': "Build deleted"} 575 return flask.jsonify(output)
576 577 578 @api_ns.route('/coprs/<username>/<coprname>/modify/', methods=["POST"])
579 @api_login_required 580 @api_req_with_copr 581 -def copr_modify(copr):
582 form = forms.CoprModifyForm(csrf_enabled=False) 583 584 if not form.validate_on_submit(): 585 raise LegacyApiError("Invalid request: {0}".format(form.errors)) 586 587 # .raw_data needs to be inspected to figure out whether the field 588 # was not sent or was sent empty 589 if form.description.raw_data and len(form.description.raw_data): 590 copr.description = form.description.data 591 if form.instructions.raw_data and len(form.instructions.raw_data): 592 copr.instructions = form.instructions.data 593 if form.repos.raw_data and len(form.repos.raw_data): 594 copr.repos = form.repos.data 595 if form.disable_createrepo.raw_data and len(form.disable_createrepo.raw_data): 596 copr.disable_createrepo = form.disable_createrepo.data 597 598 if "unlisted_on_hp" in flask.request.form: 599 copr.unlisted_on_hp = form.unlisted_on_hp.data 600 if "build_enable_net" in flask.request.form: 601 copr.build_enable_net = form.build_enable_net.data 602 if "auto_prune" in flask.request.form: 603 copr.auto_prune = form.auto_prune.data 604 605 try: 606 CoprsLogic.update(flask.g.user, copr) 607 if copr.group: # load group.id 608 _ = copr.group.id 609 db.session.commit() 610 except (exceptions.ActionInProgressException, 611 exceptions.InsufficientRightsException, 612 exceptions.NonAdminCannotDisableAutoPrunning) as e: 613 db.session.rollback() 614 raise LegacyApiError("Invalid request: {}".format(e)) 615 616 output = { 617 'output': 'ok', 618 'description': copr.description, 619 'instructions': copr.instructions, 620 'repos': copr.repos, 621 } 622 623 return flask.jsonify(output)
624 625 626 @api_ns.route('/coprs/<username>/<coprname>/modify/<chrootname>/', methods=["POST"])
627 @api_login_required 628 @api_req_with_copr 629 -def copr_modify_chroot(copr, chrootname):
630 """Deprecated to copr_edit_chroot""" 631 form = forms.ModifyChrootForm(csrf_enabled=False) 632 # chroot = coprs_logic.MockChrootsLogic.get_from_name(chrootname, active_only=True).first() 633 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname) 634 635 if not form.validate_on_submit(): 636 raise LegacyApiError("Invalid request: {0}".format(form.errors)) 637 else: 638 coprs_logic.CoprChrootsLogic.update_chroot(flask.g.user, chroot, form.buildroot_pkgs.data) 639 db.session.commit() 640 641 output = {'output': 'ok', 'buildroot_pkgs': chroot.buildroot_pkgs} 642 return flask.jsonify(output)
643 644 645 @api_ns.route('/coprs/<username>/<coprname>/chroot/edit/<chrootname>/', methods=["POST"])
646 @api_login_required 647 @api_req_with_copr 648 -def copr_edit_chroot(copr, chrootname):
649 form = forms.ModifyChrootForm(csrf_enabled=False) 650 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname) 651 652 if not form.validate_on_submit(): 653 raise LegacyApiError("Invalid request: {0}".format(form.errors)) 654 else: 655 buildroot_pkgs = repos = comps_xml = comps_name = None 656 if "buildroot_pkgs" in flask.request.form: 657 buildroot_pkgs = form.buildroot_pkgs.data 658 if "repos" in flask.request.form: 659 repos = form.repos.data 660 if form.upload_comps.has_file(): 661 comps_xml = form.upload_comps.data.stream.read() 662 comps_name = form.upload_comps.data.filename 663 if form.delete_comps.data: 664 coprs_logic.CoprChrootsLogic.remove_comps(flask.g.user, chroot) 665 coprs_logic.CoprChrootsLogic.update_chroot( 666 flask.g.user, chroot, buildroot_pkgs, repos, comps=comps_xml, comps_name=comps_name) 667 db.session.commit() 668 669 output = { 670 "output": "ok", 671 "message": "Edit chroot operation was successful.", 672 "chroot": chroot.to_dict(), 673 } 674 return flask.jsonify(output)
675 676 677 @api_ns.route('/coprs/<username>/<coprname>/detail/<chrootname>/', methods=["GET"])
678 @api_req_with_copr 679 -def copr_chroot_details(copr, chrootname):
680 """Deprecated to copr_get_chroot""" 681 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname) 682 output = {'output': 'ok', 'buildroot_pkgs': chroot.buildroot_pkgs} 683 return flask.jsonify(output)
684 685 @api_ns.route('/coprs/<username>/<coprname>/chroot/get/<chrootname>/', methods=["GET"])
686 @api_req_with_copr 687 -def copr_get_chroot(copr, chrootname):
688 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname) 689 output = {'output': 'ok', 'chroot': chroot.to_dict()} 690 return flask.jsonify(output)
691
692 @api_ns.route("/coprs/search/") 693 @api_ns.route("/coprs/search/<project>/") 694 -def api_coprs_search_by_project(project=None):
695 """ Return the list of coprs found in search by the given text. 696 project is taken either from GET params or from the URL itself 697 (in this order). 698 699 :arg project: the text one would like find for coprs. 700 701 """ 702 project = flask.request.args.get("project", None) or project 703 if not project: 704 raise LegacyApiError("No project found.") 705 706 try: 707 query = CoprsLogic.get_multiple_fulltext(project) 708 709 repos = query.all() 710 output = {"output": "ok", "repos": []} 711 for repo in repos: 712 output["repos"].append({"username": repo.user.name, 713 "coprname": repo.name, 714 "description": repo.description}) 715 except ValueError as e: 716 raise LegacyApiError("Server error: {}".format(e)) 717 718 return flask.jsonify(output)
719
720 721 @api_ns.route("/playground/list/") 722 -def playground_list():
723 """ Return list of coprs which are part of playground """ 724 query = CoprsLogic.get_playground() 725 repos = query.all() 726 output = {"output": "ok", "repos": []} 727 for repo in repos: 728 output["repos"].append({"username": repo.owner_name, 729 "coprname": repo.name, 730 "chroots": [chroot.name for chroot in repo.active_chroots]}) 731 732 jsonout = flask.jsonify(output) 733 jsonout.status_code = 200 734 return jsonout
735 736 737 @api_ns.route("/coprs/<username>/<coprname>/monitor/", methods=["GET"])
738 @api_req_with_copr 739 -def monitor(copr):
740 monitor_data = builds_logic.BuildsMonitorLogic.get_monitor_data(copr) 741 output = MonitorWrapper(copr, monitor_data).to_dict() 742 return flask.jsonify(output)
743 744 ############################################################################### 745 746 @api_ns.route("/coprs/<username>/<coprname>/package/add/<source_type_text>/", methods=["POST"])
747 @api_login_required 748 @api_req_with_copr 749 -def copr_add_package(copr, source_type_text):
750 return process_package_add_or_edit(copr, source_type_text)
751 752 753 @api_ns.route("/coprs/<username>/<coprname>/package/<package_name>/edit/<source_type_text>/", methods=["POST"])
754 @api_login_required 755 @api_req_with_copr 756 -def copr_edit_package(copr, package_name, source_type_text):
757 try: 758 package = PackagesLogic.get(copr.id, package_name)[0] 759 except IndexError: 760 raise LegacyApiError("Package {name} does not exists in copr {copr}.".format(name=package_name, copr=copr.full_name)) 761 return process_package_add_or_edit(copr, source_type_text, package=package)
762
763 764 -def process_package_add_or_edit(copr, source_type_text, package=None):
765 try: 766 form = forms.get_package_form_cls_by_source_type_text(source_type_text)(csrf_enabled=False) 767 except UnknownSourceTypeException: 768 raise LegacyApiError("Unsupported package source type {source_type_text}".format(source_type_text=source_type_text)) 769 770 if form.validate_on_submit(): 771 if not package: 772 try: 773 package = PackagesLogic.add(flask.app.g.user, copr, form.package_name.data) 774 except InsufficientRightsException: 775 raise LegacyApiError("Insufficient permissions.") 776 except DuplicateException: 777 raise LegacyApiError("Package {0} already exists in copr {1}.".format(form.package_name.data, copr.full_name)) 778 779 package.source_type = helpers.BuildSourceEnum(source_type_text) 780 package.source_json = form.source_json 781 if "webhook_rebuild" in flask.request.form: 782 package.webhook_rebuild = form.webhook_rebuild.data 783 784 db.session.add(package) 785 db.session.commit() 786 else: 787 raise LegacyApiError(form.errors) 788 789 return flask.jsonify({ 790 "output": "ok", 791 "message": "Create or edit operation was successful.", 792 "package": package.to_dict(), 793 })
794
795 796 -def get_package_record_params():
797 params = {} 798 if flask.request.args.get('with_latest_build'): 799 params['with_latest_build'] = True 800 if flask.request.args.get('with_latest_succeeded_build'): 801 params['with_latest_succeeded_build'] = True 802 if flask.request.args.get('with_all_builds'): 803 params['with_all_builds'] = True 804 return params
805
806 807 -def generate_package_list(query, params):
808 """ 809 A lagging generator to stream JSON so we don't have to hold everything in memory 810 This is a little tricky, as we need to omit the last comma to make valid JSON, 811 thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/ 812 """ 813 packages = query.__iter__() 814 try: 815 prev_package = next(packages) # get first result 816 except StopIteration: 817 # StopIteration here means the length was zero, so yield a valid packages doc and stop 818 yield '{"packages": []}' 819 raise StopIteration 820 # We have some packages. First, yield the opening json 821 yield '{"packages": [' 822 # Iterate over the packages 823 for package in packages: 824 yield json.dumps(prev_package.to_dict(**params)) + ', ' 825 prev_package = package 826 # Now yield the last iteration without comma but with the closing brackets 827 yield json.dumps(prev_package.to_dict(**params)) + ']}'
828 829 830 @api_ns.route("/coprs/<username>/<coprname>/package/list/", methods=["GET"])
831 @api_req_with_copr 832 -def copr_list_packages(copr):
833 packages = PackagesLogic.get_all(copr.id) 834 params = get_package_record_params() 835 return flask.Response(generate_package_list(packages, params), content_type='application/json')
836 #return flask.jsonify({"packages": [package.to_dict(**params) for package in packages]}) 837 838 839 @api_ns.route("/coprs/<username>/<coprname>/package/get/<package_name>/", methods=["GET"])
840 @api_req_with_copr 841 -def copr_get_package(copr, package_name):
842 try: 843 package = PackagesLogic.get(copr.id, package_name)[0] 844 except IndexError: 845 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name)) 846 847 params = get_package_record_params() 848 return flask.jsonify({'package': package.to_dict(**params)})
849 850 851 @api_ns.route("/coprs/<username>/<coprname>/package/delete/<package_name>/", methods=["POST"])
852 @api_login_required 853 @api_req_with_copr 854 -def copr_delete_package(copr, package_name):
855 try: 856 package = PackagesLogic.get(copr.id, package_name)[0] 857 except IndexError: 858 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name)) 859 860 try: 861 PackagesLogic.delete_package(flask.g.user, package) 862 db.session.commit() 863 except (InsufficientRightsException, ActionInProgressException) as e: 864 raise LegacyApiError(str(e)) 865 866 return flask.jsonify({ 867 "output": "ok", 868 "message": "Package was successfully deleted.", 869 'package': package.to_dict(), 870 })
871 872 873 @api_ns.route("/coprs/<username>/<coprname>/package/reset/<package_name>/", methods=["POST"])
874 @api_login_required 875 @api_req_with_copr 876 -def copr_reset_package(copr, package_name):
877 try: 878 package = PackagesLogic.get(copr.id, package_name)[0] 879 except IndexError: 880 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name)) 881 882 try: 883 PackagesLogic.reset_package(flask.g.user, package) 884 db.session.commit() 885 except InsufficientRightsException as e: 886 raise LegacyApiError(str(e)) 887 888 return flask.jsonify({ 889 "output": "ok", 890 "message": "Package's default source was successfully reseted.", 891 'package': package.to_dict(), 892 })
893 894 895 @api_ns.route("/coprs/<username>/<coprname>/package/build/<package_name>/", methods=["POST"])
896 @api_login_required 897 @api_req_with_copr 898 -def copr_build_package(copr, package_name):
899 form = forms.BuildFormRebuildFactory.create_form_cls(copr.active_chroots)(csrf_enabled=False) 900 901 try: 902 package = PackagesLogic.get(copr.id, package_name)[0] 903 except IndexError: 904 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name)) 905 906 if form.validate_on_submit(): 907 try: 908 build = PackagesLogic.build_package(flask.g.user, copr, package, form.selected_chroots, **form.data) 909 db.session.commit() 910 except (InsufficientRightsException, ActionInProgressException, NoPackageSourceException) as e: 911 raise LegacyApiError(str(e)) 912 else: 913 raise LegacyApiError(form.errors) 914 915 return flask.jsonify({ 916 "output": "ok", 917 "ids": [build.id], 918 "message": "Build was added to {0}.".format(copr.name) 919 })
920 921 922 @api_ns.route("/module/build/", methods=["POST"])
923 @api_login_required 924 -def copr_build_module():
925 form = forms.ModuleBuildForm(csrf_enabled=False) 926 if not form.validate_on_submit(): 927 raise LegacyApiError(form.errors) 928 929 try: 930 common = {"owner": flask.g.user.name, 931 "copr_owner": form.copr_owner.data, 932 "copr_project": form.copr_project.data} 933 if form.scmurl.data: 934 kwargs = {"json": dict({"scmurl": form.scmurl.data, "branch": form.branch.data}, **common)} 935 else: 936 kwargs = {"data": common, "files": {"yaml": (form.modulemd.data.filename, form.modulemd.data)}} 937 938 response = requests.post(flask.current_app.config["MBS_URL"], verify=False, **kwargs) 939 if response.status_code == 500: 940 raise LegacyApiError("Error from MBS: {} - {}".format(response.status_code, response.reason)) 941 942 resp = json.loads(response.content) 943 if response.status_code != 201: 944 raise LegacyApiError("Error from MBS: {}".format(resp["message"])) 945 946 return flask.jsonify({ 947 "output": "ok", 948 "message": "Created module {}-{}-{}".format(resp["name"], resp["stream"], resp["version"]), 949 }) 950 951 except requests.ConnectionError: 952 raise LegacyApiError("Can't connect to MBS instance")
953 954 955 @api_ns.route("/coprs/<username>/<coprname>/module/make/", methods=["POST"])
956 @api_login_required 957 @api_req_with_copr 958 -def copr_make_module(copr):
959 form = forms.ModuleFormUploadFactory(csrf_enabled=False) 960 if not form.validate_on_submit(): 961 # @TODO Prettier error 962 raise LegacyApiError(form.errors) 963 964 modulemd = form.modulemd.data.read() 965 module = ModulesLogic.from_modulemd(modulemd) 966 try: 967 ModulesLogic.validate(modulemd) 968 msg = "Nothing happened" 969 if form.create.data: 970 module = ModulesLogic.add(flask.g.user, copr, module) 971 db.session.flush() 972 msg = "Module was created" 973 974 if form.build.data: 975 if not module.id: 976 module = ModulesLogic.get_by_nsv(copr, module.name, module.stream, module.version).one() 977 ActionsLogic.send_build_module(flask.g.user, copr, module) 978 msg = "Module build was submitted" 979 db.session.commit() 980 981 return flask.jsonify({ 982 "output": "ok", 983 "message": msg, 984 "modulemd": modulemd, 985 }) 986 987 except sqlalchemy.exc.IntegrityError: 988 raise LegacyApiError({"nsv": ["Module {} already exists".format(module.nsv)]}) 989 990 except sqlalchemy.orm.exc.NoResultFound: 991 raise LegacyApiError({"nsv": ["Module {} doesn't exist. You need to create it first".format(module.nsv)]}) 992 993 except ValidationError as ex: 994 raise LegacyApiError({"nsv": [ex.message]})
995 996 997 @api_ns.route("/coprs/<username>/<coprname>/build-config/<chroot>/", methods=["GET"]) 998 @api_ns.route("/g/<group_name>/<coprname>/build-config/<chroot>/", methods=["GET"])
999 @api_req_with_copr 1000 -def copr_build_config(copr, chroot):
1001 """ 1002 Generate build configuration. 1003 """ 1004 output = { 1005 "output": "ok", 1006 "build_config": generate_build_config(copr, chroot), 1007 } 1008 1009 if not output['build_config']: 1010 raise LegacyApiError('Chroot not found.') 1011 1012 return flask.jsonify(output)
1013 1014 1015 @api_ns.route("/module/repo/", methods=["POST"])
1016 -def copr_module_repo():
1017 """ 1018 :return: URL to a DNF repository for the module 1019 """ 1020 form = forms.ModuleRepo(csrf_enabled=False) 1021 if not form.validate_on_submit(): 1022 raise LegacyApiError(form.errors) 1023 1024 copr = ComplexLogic.get_copr_by_owner_safe(form.owner.data, form.copr.data) 1025 nvs = [form.name.data, form.stream.data, form.version.data] 1026 module = ModulesLogic.get_by_nsv(copr, *nvs).first() 1027 if not module: 1028 raise LegacyApiError("No module {}".format("-".join(nvs))) 1029 1030 return flask.jsonify({"output": "ok", "repo": module.repo_url(form.arch.data)})
1031