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