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