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