Package coprs :: Package views :: Package api_ns :: Module api_general
[hide private]
[frames] | no frames]

Source Code for Module coprs.views.api_ns.api_general

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