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