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