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