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