Package coprs :: Package views :: Package coprs_ns :: Module coprs_general
[hide private]
[frames] | no frames]

Source Code for Module coprs.views.coprs_ns.coprs_general

   1  # coding: utf-8 
   2   
   3  import os 
   4  import time 
   5  import fnmatch 
   6  import subprocess 
   7  import json 
   8  import datetime 
   9   
  10  from six.moves.urllib.parse import urljoin 
  11   
  12  import flask 
  13  from flask import render_template, url_for, stream_with_context 
  14  import sqlalchemy 
  15  from itertools import groupby 
  16  from wtforms import ValidationError 
  17   
  18  from pygments import highlight 
  19  from pygments.lexers import get_lexer_by_name 
  20  from pygments.formatters import HtmlFormatter 
  21   
  22  from coprs import app 
  23  from coprs import cache 
  24  from coprs import db 
  25  from coprs import rcp 
  26  from coprs import exceptions 
  27  from coprs import forms 
  28  from coprs import helpers 
  29  from coprs import models 
  30  from coprs.exceptions import ObjectNotFound 
  31  from coprs.logic.coprs_logic import CoprsLogic, PinnedCoprsLogic, MockChrootsLogic 
  32  from coprs.logic.stat_logic import CounterStatLogic 
  33  from coprs.logic.modules_logic import ModulesLogic, ModulemdGenerator, ModuleBuildFacade 
  34  from coprs.rmodels import TimedStatEvents 
  35  from coprs.mail import send_mail, LegalFlagMessage, PermissionRequestMessage, PermissionChangeMessage 
  36   
  37  from coprs.logic.complex_logic import ComplexLogic 
  38  from coprs.logic.outdated_chroots_logic import OutdatedChrootsLogic 
  39   
  40  from coprs.views.misc import (login_required, page_not_found, req_with_copr, 
  41                                generic_error, req_with_copr_dir) 
  42   
  43  from coprs.views.coprs_ns import coprs_ns 
  44   
  45  from coprs.logic import builds_logic, coprs_logic, actions_logic, users_logic 
  46  from coprs.helpers import generate_repo_url, CHROOT_RPMS_DL_STAT_FMT, \ 
  47      url_for_copr_view, REPO_DL_STAT_FMT, CounterStatType, generate_repo_name, \ 
  48      WorkList 
49 50 51 -def url_for_copr_details(copr):
52 return url_for_copr_view( 53 "coprs_ns.copr_detail", 54 "coprs_ns.copr_detail", 55 copr)
56
57 58 -def url_for_copr_edit(copr):
59 return url_for_copr_view( 60 "coprs_ns.copr_edit", 61 "coprs_ns.copr_edit", 62 copr)
63
64 65 @coprs_ns.route("/", defaults={"page": 1}) 66 @coprs_ns.route("/<int:page>/") 67 -def coprs_show(page=1):
68 query = CoprsLogic.get_multiple(include_unlisted_on_hp=False) 69 query = CoprsLogic.set_query_order(query, desc=True) 70 71 paginator = helpers.Paginator(query, query.count(), page) 72 73 coprs = paginator.sliced_query 74 75 # flask.g.user is none when no user is logged - showing builds from everyone 76 # TODO: builds_logic.BuildsLogic.get_recent_tasks(flask.g.user, 5) takes too much time, optimize sql 77 # users_builds = builds_logic.BuildsLogic.get_recent_tasks(flask.g.user, 5) 78 users_builds = builds_logic.BuildsLogic.get_recent_tasks(None, 4) 79 80 data = builds_logic.BuildsLogic.get_small_graph_data('30min') 81 82 return flask.render_template("coprs/show/all.html", 83 coprs=coprs, 84 pinned=[], 85 paginator=paginator, 86 tasks_info=ComplexLogic.get_queue_sizes(), 87 users_builds=users_builds, 88 graph=data)
89
90 91 @coprs_ns.route("/<username>/", defaults={"page": 1}) 92 @coprs_ns.route("/<username>/<int:page>/") 93 -def coprs_by_user(username=None, page=1):
94 user = users_logic.UsersLogic.get(username).first() 95 if not user: 96 return page_not_found( 97 "User {0} does not exist.".format(username)) 98 99 pinned = [pin.copr for pin in PinnedCoprsLogic.get_by_user_id(user.id)] if page == 1 else [] 100 query = CoprsLogic.get_multiple_owned_by_username(username) 101 query = CoprsLogic.filter_without_ids(query, [copr.id for copr in pinned]) 102 query = CoprsLogic.filter_without_group_projects(query) 103 query = CoprsLogic.set_query_order(query, desc=True) 104 105 paginator = helpers.Paginator(query, query.count(), page) 106 coprs = paginator.sliced_query 107 108 # flask.g.user is none when no user is logged - showing builds from everyone 109 users_builds = builds_logic.BuildsLogic.get_recent_tasks(flask.g.user, 4) 110 111 data = builds_logic.BuildsLogic.get_small_graph_data('30min') 112 113 return flask.render_template("coprs/show/user.html", 114 user=user, 115 coprs=coprs, 116 pinned=pinned, 117 paginator=paginator, 118 tasks_info=ComplexLogic.get_queue_sizes(), 119 users_builds=users_builds, 120 graph=data)
121
122 123 @coprs_ns.route("/fulltext/", defaults={"page": 1}) 124 @coprs_ns.route("/fulltext/<int:page>/") 125 -def coprs_fulltext_search(page=1):
126 fulltext = flask.request.args.get("fulltext", "") 127 try: 128 query = coprs_logic.CoprsLogic.get_multiple_fulltext(fulltext) 129 except ValueError as e: 130 flask.flash(str(e), "error") 131 return flask.redirect(flask.request.referrer or 132 flask.url_for("coprs_ns.coprs_show")) 133 134 paginator = helpers.Paginator(query, query.count(), page, 135 additional_params={"fulltext": fulltext}) 136 137 data = builds_logic.BuildsLogic.get_small_graph_data('30min') 138 139 coprs = paginator.sliced_query 140 return render_template("coprs/show/fulltext.html", 141 coprs=coprs, 142 pinned=[], 143 paginator=paginator, 144 fulltext=fulltext, 145 tasks_info=ComplexLogic.get_queue_sizes(), 146 graph=data)
147
148 149 @coprs_ns.route("/<username>/add/") 150 @coprs_ns.route("/g/<group_name>/add/") 151 @login_required 152 -def copr_add(username=None, group_name=None):
153 form = forms.CoprFormFactory.create_form_cls()() 154 comments = {} 155 for chroot in MockChrootsLogic.get_multiple(active_only=True): 156 comments[chroot.name] = chroot.comment 157 if group_name: 158 group = ComplexLogic.get_group_by_name_safe(group_name) 159 return flask.render_template("coprs/group_add.html", form=form, group=group, comments=comments) 160 return flask.render_template("coprs/add.html", form=form, comments=comments)
161
162 163 @coprs_ns.route("/<username>/new/", methods=["POST"]) 164 @coprs_ns.route("/g/<group_name>/new/", methods=["POST"]) 165 @login_required 166 -def copr_new(username=None, group_name=None):
167 """ 168 Receive information from the user (and group) on how to create its new copr 169 and create it accordingly. 170 """ 171 group = None 172 redirect = "coprs/add.html" 173 if group_name: 174 group = ComplexLogic.get_group_by_name_safe(group_name) 175 redirect = "coprs/group_add.html" 176 177 form = forms.CoprFormFactory.create_form_cls(group=group)() 178 if form.validate_on_submit(): 179 try: 180 copr = coprs_logic.CoprsLogic.add( 181 flask.g.user, 182 name=form.name.data, 183 homepage=form.homepage.data, 184 contact=form.contact.data, 185 repos=form.repos.data.replace("\n", " "), 186 selected_chroots=form.selected_chroots, 187 description=form.description.data, 188 instructions=form.instructions.data, 189 disable_createrepo=form.disable_createrepo.data, 190 build_enable_net=form.build_enable_net.data, 191 unlisted_on_hp=form.unlisted_on_hp.data, 192 group=group, 193 persistent=form.persistent.data, 194 auto_prune=(form.auto_prune.data if flask.g.user.admin else True), 195 follow_fedora_branching=form.follow_fedora_branching.data, 196 delete_after_days=form.delete_after_days.data, 197 multilib=form.multilib.data, 198 runtime_dependencies=form.runtime_dependencies.data.replace("\n", " "), 199 bootstrap=form.bootstrap.data, 200 ) 201 202 db.session.commit() 203 after_the_project_creation(copr, form) 204 return flask.redirect(url_for_copr_details(copr)) 205 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e: 206 flask.flash(str(e), "error") 207 208 return flask.render_template(redirect, form=form, group=group)
209
210 211 -def after_the_project_creation(copr, form):
212 flask.flash("New project has been created successfully.", "success") 213 _check_rpmfusion(copr.repos) 214 if form.initial_pkgs.data: 215 pkgs = form.initial_pkgs.data.replace("\n", " ").split(" ") 216 217 # validate (and skip bad) urls 218 bad_urls = [] 219 for pkg in pkgs: 220 if not pkg.endswith(".src.rpm"): 221 bad_urls.append(pkg) 222 flask.flash("Bad url: {0} (skipped)".format(pkg)) 223 for bad_url in bad_urls: 224 pkgs.remove(bad_url) 225 226 if not pkgs: 227 flask.flash("No initial packages submitted") 228 else: 229 # build each package as a separate build 230 for pkg in pkgs: 231 builds_logic.BuildsLogic.add( 232 flask.g.user, 233 pkgs=pkg, 234 srpm_url=pkg, 235 copr=copr, 236 enable_net=form.build_enable_net.data 237 ) 238 239 db.session.commit() 240 flask.flash("Initial packages were successfully submitted " 241 "for building.")
242
243 244 @coprs_ns.route("/<username>/<coprname>/report-abuse") 245 @coprs_ns.route("/g/<group_name>/<coprname>/report-abuse") 246 @req_with_copr 247 @login_required 248 -def copr_report_abuse(copr):
249 return render_copr_report_abuse(copr)
250
251 252 -def render_copr_report_abuse(copr):
253 form = forms.CoprLegalFlagForm() 254 return render_template("coprs/report_abuse.html", copr=copr, form=form)
255
256 257 @coprs_ns.route("/<username>/<coprname>/") 258 @coprs_ns.route("/g/<group_name>/<coprname>/") 259 @req_with_copr 260 -def copr_detail(copr):
261 return render_copr_detail(copr)
262
263 264 -def render_copr_detail(copr):
265 repo_dl_stat = CounterStatLogic.get_copr_repo_dl_stat(copr) 266 form = forms.CoprLegalFlagForm() 267 repos_info = {} 268 for chroot in copr.active_chroots: 269 chroot_rpms_dl_stat_key = CHROOT_RPMS_DL_STAT_FMT.format( 270 copr_user=copr.owner_name, 271 copr_project_name=copr.name, 272 copr_chroot=chroot.name, 273 ) 274 chroot_rpms_dl_stat = TimedStatEvents.get_count( 275 rconnect=rcp.get_connection(), 276 name=chroot_rpms_dl_stat_key, 277 ) 278 279 logoset = set() 280 logodir = app.static_folder + "/chroot_logodir" 281 for logo in os.listdir(logodir): 282 # glob.glob() uses listdir() and fnmatch anyways 283 if fnmatch.fnmatch(logo, "*.png"): 284 logoset.add(logo[:-4]) 285 286 if chroot.name_release not in repos_info: 287 logo = None 288 if chroot.name_release in logoset: 289 logo = chroot.name_release + ".png" 290 elif chroot.os_release in logoset: 291 logo = chroot.os_release + ".png" 292 293 repos_info[chroot.name_release] = { 294 "name_release": chroot.name_release, 295 "os_release": chroot.os_release, 296 "os_version": chroot.os_version, 297 "logo": logo, 298 "arch_list": [chroot.arch], 299 "repo_file": "{}-{}.repo".format(copr.repo_id, chroot.name_release), 300 "dl_stat": repo_dl_stat[chroot.name_release], 301 "rpm_dl_stat": { 302 chroot.arch: chroot_rpms_dl_stat 303 } 304 } 305 else: 306 repos_info[chroot.name_release]["arch_list"].append(chroot.arch) 307 repos_info[chroot.name_release]["rpm_dl_stat"][chroot.arch] = chroot_rpms_dl_stat 308 309 if copr.multilib: 310 for name_release in repos_info: 311 arches = repos_info[name_release]['arch_list'] 312 arch_repos = {} 313 for ch64, ch32 in models.MockChroot.multilib_pairs.items(): 314 if set([ch64, ch32]).issubset(set(arches)): 315 arch_repos[ch64] = ch32 316 317 repos_info[name_release]['arch_repos'] = arch_repos 318 319 320 repos_info_list = sorted(repos_info.values(), key=lambda rec: rec["name_release"]) 321 builds = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr).limit(1).all() 322 323 return flask.render_template( 324 "coprs/detail/overview.html", 325 copr=copr, 326 user=flask.g.user, 327 form=form, 328 repo_dl_stat=repo_dl_stat, 329 repos_info_list=repos_info_list, 330 latest_build=builds[0] if len(builds) == 1 else None, 331 )
332
333 334 @coprs_ns.route("/<username>/<coprname>/", methods=["POST"]) 335 @coprs_ns.route("/g/<group_name>/<coprname>/", methods=["POST"]) 336 @req_with_copr 337 @login_required 338 -def copr_detail_post(copr):
339 form = forms.VoteForCopr(meta={'csrf': False}) 340 if not form.validate_on_submit(): 341 flask.flash(form.errors, "error") 342 return render_copr_detail(copr) 343 344 # Always reset the current vote 345 coprs_logic.CoprScoreLogic.reset(copr) 346 347 if form.upvote.data: 348 coprs_logic.CoprScoreLogic.upvote(copr) 349 if form.downvote.data: 350 coprs_logic.CoprScoreLogic.downvote(copr) 351 db.session.commit() 352 353 # Return to the previous site. The vote could be sent from 354 # packages/builds/settings/etc page, so we don't want to just 355 # `render_copr_detail` but return to the previous page instead 356 if flask.request.referrer: 357 return flask.redirect(flask.request.referrer) 358 359 # HTTP referrer is unreliable so as a fallback option, 360 # we just render the project overview page 361 return flask.redirect(helpers.copr_url("coprs_ns.copr_detail", copr))
362
363 364 @coprs_ns.route("/<username>/<coprname>/permissions/") 365 @coprs_ns.route("/g/<group_name>/<coprname>/permissions/") 366 @req_with_copr 367 -def copr_permissions(copr):
368 permissions = coprs_logic.CoprPermissionsLogic.get_for_copr(copr).all() 369 if flask.g.user: 370 user_perm = flask.g.user.permissions_for_copr(copr) 371 else: 372 user_perm = None 373 374 permissions_applier_form = None 375 permissions_form = None 376 377 # generate a proper form for displaying 378 if flask.g.user: 379 # https://github.com/ajford/flask-wtf/issues/58 380 permissions_applier_form = \ 381 forms.PermissionsApplierFormFactory.create_form_cls( 382 user_perm)(formdata=None) 383 384 if flask.g.user.can_edit(copr): 385 permissions_form = forms.PermissionsFormFactory.create_form_cls( 386 permissions)() 387 388 return flask.render_template( 389 "coprs/detail/settings/permissions.html", 390 copr=copr, 391 permissions_form=permissions_form, 392 permissions_applier_form=permissions_applier_form, 393 permissions=permissions, 394 current_user_permissions=user_perm)
395
396 397 -def render_copr_integrations(copr, pagure_form):
398 if not copr.webhook_secret: 399 copr.new_webhook_secret() 400 db.session.add(copr) 401 db.session.commit() 402 403 bitbucket_url = "https://{}/webhooks/bitbucket/{}/{}/".format( 404 app.config["PUBLIC_COPR_HOSTNAME"], 405 copr.id, 406 copr.webhook_secret) 407 408 github_url = "https://{}/webhooks/github/{}/{}/".format( 409 app.config["PUBLIC_COPR_HOSTNAME"], 410 copr.id, 411 copr.webhook_secret) 412 413 gitlab_url = "https://{}/webhooks/gitlab/{}/{}/".format( 414 app.config["PUBLIC_COPR_HOSTNAME"], 415 copr.id, 416 copr.webhook_secret) 417 418 custom_url = "https://{}/webhooks/custom/{}/{}/".format( 419 app.config["PUBLIC_COPR_HOSTNAME"], 420 copr.id, 421 copr.webhook_secret) + "<PACKAGE_NAME>/" 422 423 return flask.render_template( 424 "coprs/detail/settings/integrations.html", 425 copr=copr, bitbucket_url=bitbucket_url, github_url=github_url, 426 gitlab_url=gitlab_url, custom_url=custom_url, pagure_form=pagure_form)
427
428 429 @coprs_ns.route("/<username>/<coprname>/integrations/") 430 @coprs_ns.route("/g/<group_name>/<coprname>/integrations/") 431 @login_required 432 @req_with_copr 433 -def copr_integrations(copr):
434 if not flask.g.user.can_edit(copr): 435 flask.flash("You don't have access to this page.", "error") 436 return flask.redirect(url_for_copr_details(copr)) 437 438 if copr.scm_api_type == 'pagure': 439 pagure_api_key = copr.scm_api_auth.get('api_key', '') 440 else: 441 pagure_api_key = '' 442 443 pagure_form = forms.PagureIntegrationForm( 444 api_key=pagure_api_key, repo_url=copr.scm_repo_url) 445 return render_copr_integrations(copr, pagure_form)
446
447 448 @coprs_ns.route("/<username>/<coprname>/integrations/update", methods=["POST"]) 449 @coprs_ns.route("/g/<group_name>/<coprname>/integrations/update", methods=["POST"]) 450 @login_required 451 @req_with_copr 452 -def copr_integrations_update(copr):
453 if not flask.g.user.can_edit(copr): 454 flask.flash("Access denied.", "error") 455 return flask.redirect(url_for_copr_details(copr)) 456 457 pagure_form = forms.PagureIntegrationForm() 458 459 if pagure_form.validate_on_submit(): 460 copr.scm_repo_url = pagure_form.repo_url.data 461 copr.scm_api_type = 'pagure' 462 copr.scm_api_auth_json = json.dumps({'api_key': pagure_form.api_key.data}) 463 db.session.add(copr) 464 db.session.commit() 465 flask.flash("Integrations have been updated.", 'success') 466 return flask.redirect(helpers.copr_url("coprs_ns.copr_integrations", copr)) 467 else: 468 return render_copr_integrations(copr, pagure_form)
469
470 471 -def render_copr_edit(copr, form, view):
472 if not form: 473 form = forms.CoprFormFactory.create_form_cls( 474 copr.mock_chroots, copr=copr)(obj=copr) 475 comments = {} 476 for chroot in MockChrootsLogic.get_multiple(active_only=True): 477 comments[chroot.name] = chroot.comment 478 return flask.render_template( 479 "coprs/detail/settings/edit.html", 480 copr=copr, form=form, view=view, comments=comments)
481
482 483 @coprs_ns.route("/<username>/<coprname>/edit/") 484 @coprs_ns.route("/g/<group_name>/<coprname>/edit/") 485 @login_required 486 @req_with_copr 487 -def copr_edit(copr, form=None):
488 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
489
490 491 -def _check_rpmfusion(repos):
492 if "rpmfusion" in repos: 493 message = flask.Markup('Using rpmfusion as dependency is nearly always wrong. Please see <a href="https://docs.pagure.org/copr.copr/user_documentation.html#what-i-can-build-in-copr">What I can build in Copr</a>.') 494 flask.flash(message, "error")
495
496 497 -def process_copr_update(copr, form):
498 copr.name = form.name.data 499 copr.homepage = form.homepage.data 500 copr.contact = form.contact.data 501 copr.repos = form.repos.data.replace("\n", " ") 502 copr.description = form.description.data 503 copr.instructions = form.instructions.data 504 copr.disable_createrepo = form.disable_createrepo.data 505 copr.build_enable_net = form.build_enable_net.data 506 copr.unlisted_on_hp = form.unlisted_on_hp.data 507 copr.follow_fedora_branching = form.follow_fedora_branching.data 508 copr.delete_after_days = form.delete_after_days.data 509 copr.multilib = form.multilib.data 510 copr.module_hotfixes = form.module_hotfixes.data 511 copr.runtime_dependencies = form.runtime_dependencies.data.replace("\n", " ") 512 copr.bootstrap = form.bootstrap.data 513 if flask.g.user.admin: 514 copr.auto_prune = form.auto_prune.data 515 else: 516 copr.auto_prune = True 517 518 try: 519 coprs_logic.CoprChrootsLogic.update_from_names( 520 flask.g.user, copr, form.selected_chroots) 521 # form validation checks for duplicates 522 coprs_logic.CoprsLogic.update(flask.g.user, copr) 523 except (exceptions.ActionInProgressException, 524 exceptions.InsufficientRightsException, 525 exceptions.ConflictingRequest) as e: 526 527 flask.flash(str(e), "error") 528 db.session.rollback() 529 else: 530 flask.flash("Project has been updated successfully.", "success") 531 db.session.commit() 532 533 copr_deps, _, non_existing = get_transitive_runtime_dependencies(copr) 534 deps_without_chroots = {} 535 for copr_dep in copr_deps: 536 for chroot in copr.active_chroots: 537 if chroot not in copr_dep.active_chroots: 538 if copr_dep in deps_without_chroots: 539 deps_without_chroots[copr_dep].append(chroot.name) 540 else: 541 deps_without_chroots[copr_dep] = [chroot.name] 542 543 if non_existing: 544 flask.flash("Non-existing projects set as runtime dependencies: " 545 "{0}.".format(", ".join(non_existing)), "warning") 546 for dep in deps_without_chroots: 547 flask.flash("Project {0}/{1} that is set as a dependency doesn't " 548 "provide all the chroots enabled in this project: {2}." 549 .format( 550 dep.owner.name if isinstance(dep.owner, models.User) 551 else "@" + dep.owner.name, 552 dep.name, ", ".join(deps_without_chroots[dep])), 553 "warning") 554 555 _check_rpmfusion(copr.repos)
556
557 558 @coprs_ns.route("/<username>/<coprname>/update/", methods=["POST"]) 559 @coprs_ns.route("/g/<group_name>/<coprname>/update/", methods=["POST"]) 560 @login_required 561 @req_with_copr 562 -def copr_update(copr):
563 form = forms.CoprFormFactory.create_form_cls(user=copr.user, group=copr.group)() 564 565 if form.validate_on_submit(): 566 process_copr_update(copr, form) 567 return flask.redirect(url_for_copr_details(copr)) 568 else: 569 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
570 571 572 @coprs_ns.route("/<username>/<coprname>/permissions_applier_change/", 573 methods=["POST"])
574 @coprs_ns.route("/g/<group_name>/<coprname>/permissions_applier_change/", methods=["POST"]) 575 @login_required 576 @req_with_copr 577 -def copr_permissions_applier_change(copr):
578 permission = coprs_logic.CoprPermissionsLogic.get(copr, flask.g.user).first() 579 applier_permissions_form = \ 580 forms.PermissionsApplierFormFactory.create_form_cls(permission)() 581 582 if copr.user == flask.g.user: 583 flask.flash("Owner cannot request permissions for his own project.", "error") 584 elif applier_permissions_form.validate_on_submit(): 585 # we rely on these to be 0 or 1 from form. TODO: abstract from that 586 if permission is not None: 587 old_builder = permission.copr_builder 588 old_admin = permission.copr_admin 589 else: 590 old_builder = 0 591 old_admin = 0 592 new_builder = applier_permissions_form.copr_builder.data 593 new_admin = applier_permissions_form.copr_admin.data 594 coprs_logic.CoprPermissionsLogic.update_permissions_by_applier( 595 flask.g.user, copr, permission, new_builder, new_admin) 596 db.session.commit() 597 flask.flash( 598 "Successfully updated permissions for project '{0}'." 599 .format(copr.name)) 600 601 # sending emails 602 if flask.current_app.config.get("SEND_EMAILS", False): 603 for mail in copr.admin_mails: 604 permission_dict = {"old_builder": old_builder, "old_admin": old_admin, 605 "new_builder": new_builder, "new_admin": new_admin} 606 msg = PermissionRequestMessage(copr, flask.g.user, permission_dict) 607 send_mail([mail], msg) 608 609 return flask.redirect(helpers.copr_url("coprs_ns.copr_detail", copr))
610
611 612 @coprs_ns.route("/<username>/<coprname>/update_permissions/", methods=["POST"]) 613 @coprs_ns.route("/g/<group_name>/<coprname>/update_permissions/", methods=["POST"]) 614 @login_required 615 @req_with_copr 616 -def copr_update_permissions(copr):
617 permissions = copr.copr_permissions 618 permissions_form = forms.PermissionsFormFactory.create_form_cls( 619 permissions)() 620 621 if permissions_form.validate_on_submit(): 622 # we don't change owner (yet) 623 try: 624 # if admin is changing his permissions, his must be changed last 625 # so that we don't get InsufficientRightsException 626 permissions.sort( 627 key=lambda x: -1 if x.user_id == flask.g.user.id else 1) 628 for perm in permissions: 629 old_builder = perm.copr_builder 630 old_admin = perm.copr_admin 631 new_builder = permissions_form[ 632 "copr_builder_{0}".format(perm.user_id)].data 633 new_admin = permissions_form[ 634 "copr_admin_{0}".format(perm.user_id)].data 635 coprs_logic.CoprPermissionsLogic.update_permissions( 636 flask.g.user, copr, perm, new_builder, new_admin) 637 if flask.current_app.config.get("SEND_EMAILS", False) and \ 638 (old_builder is not new_builder or old_admin is not new_admin): 639 permission_dict = {"old_builder": old_builder, "old_admin": old_admin, 640 "new_builder": new_builder, "new_admin": new_admin} 641 msg = PermissionChangeMessage(copr, permission_dict) 642 send_mail(perm.user.mail, msg) 643 # for now, we don't check for actions here, as permissions operation 644 # don't collide with any actions 645 except exceptions.InsufficientRightsException as e: 646 db.session.rollback() 647 flask.flash(str(e), "error") 648 else: 649 db.session.commit() 650 flask.flash("Project permissions were updated successfully.", "success") 651 652 return flask.redirect(url_for_copr_details(copr))
653
654 655 @coprs_ns.route("/<username>/<coprname>/repositories/") 656 @coprs_ns.route("/g/<group_name>/<coprname>/repositories/") 657 @login_required 658 @req_with_copr 659 -def copr_repositories(copr):
660 if not flask.g.user.can_edit(copr): 661 flask.flash("You don't have access to this page.", "error") 662 return flask.redirect(url_for_copr_details(copr)) 663 664 return render_copr_repositories(copr)
665
666 667 -def render_copr_repositories(copr):
668 outdated_chroots = copr.outdated_chroots 669 return flask.render_template("coprs/detail/settings/repositories.html", copr=copr, 670 outdated_chroots=outdated_chroots)
671
672 673 @coprs_ns.route("/<username>/<coprname>/repositories/", methods=["POST"]) 674 @coprs_ns.route("/g/<group_name>/<coprname>/repositories/", methods=["POST"]) 675 @login_required 676 @req_with_copr 677 -def copr_repositories_post(copr):
678 return process_copr_repositories(copr, render_copr_repositories)
679
680 681 -def process_copr_repositories(copr, on_success):
682 form = forms.CoprChrootExtend() 683 if not copr and not (form.ownername.data or form.projectname.data): 684 raise ValidationError("Ambiguous to what project the chroot belongs") 685 686 if not copr: 687 copr = ComplexLogic.get_copr_by_owner_safe(form.ownername.data, 688 form.projectname.data) 689 if not flask.g.user.can_edit(copr): 690 flask.flash("You don't have access to this page.", "error") 691 return flask.redirect(url_for_copr_details(copr)) 692 693 if form.extend.data: 694 update_fun = OutdatedChrootsLogic.extend 695 chroot_name = form.extend.data 696 flask.flash("Repository for {} will be preserved for another {} days from now" 697 .format(chroot_name, app.config["DELETE_EOL_CHROOTS_AFTER"])) 698 elif form.expire.data: 699 update_fun = OutdatedChrootsLogic.expire 700 chroot_name = form.expire.data 701 flask.flash("Repository for {} is scheduled to be removed." 702 "If you changed your mind, click 'Extend` to revert your decision." 703 .format(chroot_name)) 704 else: 705 raise ValidationError("Copr chroot needs to be either extended or expired") 706 707 copr_chroot = coprs_logic.CoprChrootsLogic.get_by_name(copr, chroot_name, active_only=False).one() 708 update_fun(copr_chroot) 709 db.session.commit() 710 return on_success(copr)
711
712 713 @coprs_ns.route("/id/<copr_id>/createrepo/", methods=["POST"]) 714 @login_required 715 -def copr_createrepo(copr_id):
716 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 717 if not flask.g.user.can_edit(copr): 718 flask.flash( 719 "You are not allowed to recreate repository metadata of copr with id {}.".format(copr_id), "error") 720 return flask.redirect(url_for_copr_details(copr)) 721 722 actions_logic.ActionsLogic.send_createrepo(copr) 723 db.session.commit() 724 725 flask.flash("Repository metadata in all directories will be regenerated...", "success") 726 return flask.redirect(url_for_copr_details(copr))
727
728 729 -def process_delete(copr, url_on_error, url_on_success):
730 form = forms.CoprDeleteForm() 731 if form.validate_on_submit(): 732 733 try: 734 ComplexLogic.delete_copr(copr) 735 except (exceptions.ActionInProgressException, 736 exceptions.InsufficientRightsException) as e: 737 738 db.session.rollback() 739 flask.flash(str(e), "error") 740 return flask.redirect(url_on_error) 741 else: 742 db.session.commit() 743 flask.flash("Project has been deleted successfully.") 744 return flask.redirect(url_on_success) 745 else: 746 return render_template("coprs/detail/settings/delete.html", form=form, copr=copr)
747
748 749 @coprs_ns.route("/<username>/<coprname>/delete/", methods=["GET", "POST"]) 750 @coprs_ns.route("/g/<group_name>/<coprname>/delete/", methods=["GET", "POST"]) 751 @login_required 752 @req_with_copr 753 -def copr_delete(copr):
754 if copr.group: 755 url_on_success = url_for("groups_ns.list_projects_by_group", group_name=copr.group.name) 756 else: 757 url_on_success = url_for("coprs_ns.coprs_by_user", username=copr.user.username) 758 url_on_error = helpers.copr_url("coprs_ns.copr_detail", copr) 759 return process_delete(copr, url_on_error, url_on_success)
760 768 786
787 788 -def get_transitive_runtime_dependencies(copr):
789 """Get a list of runtime dependencies (build transitively from 790 dependencies' dependencies). Returns three lists, one with Copr 791 dependencies, one with list of non-existing Copr dependencies 792 and one with URLs to external dependencies. 793 794 :type copr: models.Copr 795 :rtype: List[models.Copr], List[str], List[str] 796 """ 797 798 if not copr: 799 return [], [], [] 800 801 wlist = WorkList([copr]) 802 internal_deps = set() 803 non_existing = set() 804 external_deps = set() 805 806 while not wlist.empty: 807 analyzed_copr = wlist.pop() 808 809 for dep in analyzed_copr.runtime_deps: 810 try: 811 copr_dep = ComplexLogic.get_copr_by_repo_safe(dep) 812 except ObjectNotFound: 813 non_existing.add(dep) 814 continue 815 816 if not copr_dep: 817 external_deps.add(dep) 818 continue 819 if copr == copr_dep: 820 continue 821 # check transitive dependencies 822 internal_deps.add(copr_dep) 823 wlist.schedule(copr_dep) 824 825 return list(internal_deps), list(external_deps), list(non_existing)
826
827 828 @coprs_ns.route("/<username>/<copr_dirname>/repo/<name_release>/", defaults={"repofile": None}) 829 @coprs_ns.route("/<username>/<copr_dirname>/repo/<name_release>/<repofile>") 830 @coprs_ns.route("/g/<group_name>/<copr_dirname>/repo/<name_release>/", defaults={"repofile": None}) 831 @coprs_ns.route("/g/<group_name>/<copr_dirname>/repo/<name_release>/<repofile>") 832 @req_with_copr_dir 833 -def generate_repo_file(copr_dir, name_release, repofile):
834 """ Generate repo file for a given repo name. 835 Reponame = username-coprname """ 836 837 arch = flask.request.args.get('arch') 838 return render_generate_repo_file(copr_dir, name_release, arch)
839
840 841 -def render_repo_template(copr_dir, mock_chroot, arch=None, cost=None, runtime_dep=None, dependent=None):
842 repo_id = "{0}:{1}:{2}:{3}{4}".format( 843 "coprdep" if runtime_dep else "copr", 844 app.config["PUBLIC_COPR_HOSTNAME"].split(":")[0], 845 copr_dir.copr.owner_name.replace("@", "group_"), 846 copr_dir.name, 847 ":ml" if arch else "" 848 ) 849 850 if runtime_dep and dependent: 851 name = "Copr {0}/{1}/{2} runtime dependency #{3} - {4}/{5}".format( 852 app.config["PUBLIC_COPR_HOSTNAME"].split(":")[0], 853 dependent.copr.owner_name, dependent.name, runtime_dep, 854 copr_dir.copr.owner_name, copr_dir.name 855 ) 856 else: 857 name = "Copr repo for {0} owned by {1}".format(copr_dir.name, copr_dir.copr.owner_name) 858 859 url = os.path.join(copr_dir.repo_url, '') # adds trailing slash 860 repo_url = generate_repo_url(mock_chroot, url, arch) 861 pubkey_url = urljoin(url, "pubkey.gpg") 862 863 return flask.render_template("coprs/copr_dir.repo", copr_dir=copr_dir, 864 url=repo_url, pubkey_url=pubkey_url, 865 repo_id=repo_id, arch=arch, cost=cost, 866 name=name)
867
868 869 -def _render_external_repo_template(dep, copr_dir, mock_chroot, dep_idx):
870 repo_name = "coprdep:{0}".format(generate_repo_name(dep)) 871 baseurl = helpers.pre_process_repo_url(mock_chroot.name, dep) 872 873 name = "Copr {0}/{1}/{2} external runtime dependency #{3} - {4}".format( 874 app.config["PUBLIC_COPR_HOSTNAME"].split(":")[0], 875 copr_dir.copr.owner_name, copr_dir.name, dep_idx, 876 generate_repo_name(dep) 877 ) 878 879 return flask.render_template("coprs/external_dependency.repo", repo_id=repo_name, 880 name=name, url=baseurl) + "\n"
881
882 883 @cache.memoize(timeout=5*60) 884 -def render_generate_repo_file(copr_dir, name_release, arch=None):
885 copr = copr_dir.copr 886 887 # redirect the aliased chroot only if it is not enabled yet 888 if not any([ch.name.startswith(name_release) for ch in copr.active_chroots]): 889 name_release = app.config["CHROOT_NAME_RELEASE_ALIAS"].get(name_release, name_release) 890 891 # if the arch isn't specified, find the fist one starting with name_release 892 searched_chroot = name_release if not arch else name_release + "-" + arch 893 894 mock_chroot = None 895 for mc in copr.active_chroots: 896 if not mc.name.startswith(searched_chroot): 897 continue 898 mock_chroot = mc 899 900 if not mock_chroot: 901 raise ObjectNotFound("Chroot {} does not exist in {}".format( 902 searched_chroot, copr.full_name)) 903 904 # append multilib counterpart repo only upon explicit request (ach != None), 905 # and only if the chroot actually is multilib capable 906 multilib_on = (arch and 907 copr.multilib and 908 mock_chroot in copr.active_multilib_chroots) 909 910 # normal, arch agnostic repofile 911 response_content = render_repo_template(copr_dir, mock_chroot) 912 913 if multilib_on: 914 # slightly lower cost than the default dnf cost=1000 915 response_content += "\n" + render_repo_template( 916 copr_dir, mock_chroot, 917 models.MockChroot.multilib_pairs[mock_chroot.arch], 918 cost=1100) 919 920 internal_deps, external_deps, non_existing = get_transitive_runtime_dependencies(copr) 921 dep_idx = 1 922 923 for runtime_dep in internal_deps: 924 owner_name = runtime_dep.owner.name 925 if isinstance(runtime_dep.owner, models.Group): 926 owner_name = "@{0}".format(owner_name) 927 copr_dep_dir = ComplexLogic.get_copr_dir_safe(owner_name, runtime_dep.name) 928 response_content += "\n" + render_repo_template(copr_dep_dir, mock_chroot, 929 runtime_dep=dep_idx, 930 dependent=copr_dir) 931 dep_idx += 1 932 933 dep_idx = 1 934 for runtime_dep in external_deps: 935 response_content += "\n" + _render_external_repo_template(runtime_dep, copr_dir, 936 mock_chroot, dep_idx) 937 dep_idx += 1 938 939 for dep in non_existing: 940 response_content += ( 941 "\n\n# This repository is configured to have a runtime dependency " 942 "on a Copr project {0} but that doesn't exist.".format(dep[7:]) 943 ) 944 945 response = flask.make_response(response_content) 946 947 response.mimetype = "text/plain" 948 response.headers["Content-Disposition"] = \ 949 "filename={0}.repo".format(copr_dir.repo_name) 950 951 name = REPO_DL_STAT_FMT.format(**{ 952 'copr_user': copr_dir.copr.user.name, 953 'copr_project_name': copr_dir.copr.name, 954 'copr_name_release': name_release, 955 }) 956 CounterStatLogic.incr(name=name, counter_type=CounterStatType.REPO_DL) 957 db.session.commit() 958 959 return response
960
961 962 ######################################################### 963 ### Module repo files ### 964 ######################################################### 965 966 @coprs_ns.route("/<username>/<coprname>/module_repo/<name_release>/<module_nsv>.repo") 967 @coprs_ns.route("/g/<group_name>/<coprname>/module_repo/<name_release>/<module_nsv>.repo") 968 @req_with_copr 969 -def generate_module_repo_file(copr, name_release, module_nsv):
970 """ Generate module repo file for a given project. """ 971 return render_generate_module_repo_file(copr, name_release, module_nsv)
972
973 -def render_generate_module_repo_file(copr, name_release, module_nsv):
974 module = ModulesLogic.get_by_nsv_str(copr, module_nsv).one() 975 mock_chroot = coprs_logic.MockChrootsLogic.get_from_name(name_release, noarch=True).first() 976 url = os.path.join(copr.main_dir.repo_url, '') # adds trailing slash 977 repo_url = generate_repo_url(mock_chroot, copr.modules_url) 978 baseurl = "{}+{}/latest/$basearch".format(repo_url.rstrip("/"), module_nsv) 979 pubkey_url = urljoin(url, "pubkey.gpg") 980 response = flask.make_response( 981 flask.render_template("coprs/copr-modules.cfg", copr=copr, module=module, 982 baseurl=baseurl, pubkey_url=pubkey_url)) 983 response.mimetype = "text/plain" 984 response.headers["Content-Disposition"] = \ 985 "filename={0}.cfg".format(copr.repo_name) 986 return response
987
988 ######################################################### 989 990 @coprs_ns.route("/<username>/<coprname>/rpm/<name_release>/<rpmfile>") 991 -def copr_repo_rpm_file(username, coprname, name_release, rpmfile):
992 try: 993 packages_dir = os.path.join(app.config["DATA_DIR"], "repo-rpm-packages") 994 with open(os.path.join(packages_dir, rpmfile), "rb") as rpm: 995 response = flask.make_response(rpm.read()) 996 response.mimetype = "application/x-rpm" 997 response.headers["Content-Disposition"] = \ 998 "filename={0}".format(rpmfile) 999 return response 1000 except IOError: 1001 return flask.render_template("404.html")
1002
1003 1004 -def render_monitor(copr, detailed=False):
1005 monitor = builds_logic.BuildsMonitorLogic.get_monitor_data(copr) 1006 oses = [chroot.os for chroot in copr.active_chroots_sorted] 1007 oses_grouped = [(len(list(group)), key) for key, group in groupby(oses)] 1008 archs = [chroot.arch for chroot in copr.active_chroots_sorted] 1009 if detailed: 1010 template = "coprs/detail/monitor/detailed.html" 1011 else: 1012 template = "coprs/detail/monitor/simple.html" 1013 return flask.Response(stream_with_context(helpers.stream_template(template, 1014 copr=copr, 1015 monitor=monitor, 1016 oses=oses_grouped, 1017 archs=archs,)))
1018
1019 1020 @coprs_ns.route("/<username>/<coprname>/monitor/") 1021 @coprs_ns.route("/<username>/<coprname>/monitor/<detailed>") 1022 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/") 1023 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/<detailed>") 1024 @req_with_copr 1025 -def copr_build_monitor(copr, detailed=False):
1026 return render_monitor(copr, detailed == "detailed")
1027
1028 1029 @coprs_ns.route("/<username>/<coprname>/fork/") 1030 @coprs_ns.route("/g/<group_name>/<coprname>/fork/") 1031 @login_required 1032 @req_with_copr 1033 -def copr_fork(copr):
1034 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)() 1035 return render_copr_fork(copr, form)
1036
1037 1038 -def render_copr_fork(copr, form, confirm=False):
1039 return flask.render_template("coprs/fork.html", copr=copr, form=form, confirm=confirm)
1040
1041 1042 @coprs_ns.route("/<username>/<coprname>/fork/", methods=["POST"]) 1043 @coprs_ns.route("/g/<group_name>/<coprname>/fork/", methods=["POST"]) 1044 @login_required 1045 @req_with_copr 1046 -def copr_fork_post(copr):
1047 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)() 1048 if form.validate_on_submit(): 1049 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0] 1050 if flask.g.user.name != form.owner.data and not dstgroup: 1051 return generic_error("There is no such group: {}".format(form.owner.data)) 1052 1053 dst_copr = CoprsLogic.get(flask.g.user.name, form.name.data).all() 1054 if dst_copr and not form.confirm.data: 1055 return render_copr_fork(copr, form, confirm=True) 1056 1057 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, 1058 dstgroup=dstgroup) 1059 1060 if created: 1061 msg = ("Forking project {} for you into {}. Please be aware that it may take a few minutes " 1062 "to duplicate backend data.".format(copr.full_name, fcopr.full_name)) 1063 else: 1064 msg = ("Updating packages in {} from {}. Please be aware that it may take a few minutes " 1065 "to duplicate backend data.".format(copr.full_name, fcopr.full_name)) 1066 1067 db.session.commit() 1068 flask.flash(msg) 1069 1070 return flask.redirect(url_for_copr_details(fcopr)) 1071 return render_copr_fork(copr, form)
1072
1073 1074 @coprs_ns.route("/<username>/<coprname>/forks/") 1075 @coprs_ns.route("/g/<group_name>/<coprname>/forks/") 1076 @req_with_copr 1077 -def copr_forks(copr):
1078 return flask.render_template("coprs/detail/forks.html", copr=copr)
1079
1080 1081 @coprs_ns.route("/update_search_index/", methods=["POST"]) 1082 -def copr_update_search_index():
1083 subprocess.call(['/usr/share/copr/coprs_frontend/manage.py', 'update-indexes-quick', '1']) 1084 return "OK"
1085
1086 1087 @coprs_ns.route("/<username>/<coprname>/modules/") 1088 @coprs_ns.route("/g/<group_name>/<coprname>/modules/") 1089 @req_with_copr 1090 -def copr_modules(copr):
1091 return render_copr_modules(copr)
1092
1093 1094 -def render_copr_modules(copr):
1095 modules = ModulesLogic.get_multiple_by_copr(copr=copr).all() 1096 return flask.render_template("coprs/detail/modules.html", copr=copr, modules=modules)
1097
1098 1099 @coprs_ns.route("/<username>/<coprname>/create_module/") 1100 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/") 1101 @login_required 1102 @req_with_copr 1103 -def copr_create_module(copr):
1104 form = forms.CreateModuleForm() 1105 return render_create_module(copr, form)
1106
1107 1108 -def render_create_module(copr, form, profiles=2):
1109 built_packages = [] 1110 for build in filter(None, [p.last_build(successful=True) for p in copr.packages]): 1111 for package in build.built_packages.split("\n"): 1112 built_packages.append((package.split()[0], build)) 1113 1114 return flask.render_template("coprs/create_module.html", copr=copr, form=form, built_packages=built_packages, profiles=profiles)
1115
1116 1117 @coprs_ns.route("/<username>/<coprname>/create_module/", methods=["POST"]) 1118 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/", methods=["POST"]) 1119 @login_required 1120 @req_with_copr 1121 -def copr_create_module_post(copr):
1122 form = forms.CreateModuleForm(copr=copr, meta={'csrf': False}) 1123 args = [copr, form] 1124 if "add_profile" in flask.request.values: 1125 return add_profile(*args) 1126 if "build_module" in flask.request.values: 1127 return build_module(*args)
1128 # @TODO Error
1129 1130 1131 -def add_profile(copr, form):
1132 n = len(form.profile_names) + 1 1133 form.profile_names.append_entry() 1134 for i in range(2, n): 1135 form.profile_pkgs.append_entry() 1136 return render_create_module(copr, form, profiles=n)
1137
1138 1139 -def build_module(copr, form):
1140 if not form.validate_on_submit(): 1141 # WORKAROUND append those which are not in min_entries 1142 for i in range(2, len(form.profile_names)): 1143 form.profile_pkgs.append_entry() 1144 return render_create_module(copr, form, profiles=len(form.profile_names)) 1145 1146 summary = "Module from Copr repository: {}".format(copr.full_name) 1147 generator = ModulemdGenerator(str(copr.name), summary=summary, config=app.config) 1148 generator.add_filter(form.filter.data) 1149 generator.add_api(form.api.data) 1150 generator.add_profiles(enumerate(zip(form.profile_names.data, form.profile_pkgs.data))) 1151 generator.add_components(form.packages.data, form.filter.data, form.builds.data) 1152 yaml = generator.generate() 1153 1154 facade = None 1155 try: 1156 facade = ModuleBuildFacade(flask.g.user, copr, yaml) 1157 module = facade.submit_build() 1158 db.session.commit() 1159 1160 flask.flash("Modulemd yaml file successfully generated and submitted to be build as {}" 1161 .format(module.nsv), "success") 1162 return flask.redirect(url_for_copr_details(copr)) 1163 1164 except ValidationError as ex: 1165 flask.flash(ex.message, "error") 1166 return render_create_module(copr, form, len(form.profile_names)) 1167 1168 except sqlalchemy.exc.IntegrityError: 1169 flask.flash("Module {}-{}-{} already exists".format( 1170 facade.modulemd.name, facade.modulemd.stream, facade.modulemd.version), "error") 1171 db.session.rollback() 1172 return render_create_module(copr, form, len(form.profile_names))
1173
1174 1175 @coprs_ns.route("/<username>/<coprname>/module/<id>") 1176 @coprs_ns.route("/g/<group_name>/<coprname>/module/<id>") 1177 @req_with_copr 1178 -def copr_module(copr, id):
1179 module = ModulesLogic.get(id).first() 1180 formatter = HtmlFormatter(style="autumn", linenos=False, noclasses=True) 1181 pretty_yaml = highlight(module.yaml, get_lexer_by_name("YAML"), formatter) 1182 1183 # Get the list of chroots with unique name_release attribute 1184 # Once we use jinja in 2.10 version, we can simply use 1185 # {{ copr.active_chroots |unique(attribute='name_release') }} 1186 unique_chroots = [] 1187 unique_name_releases = set() 1188 for chroot in copr.active_chroots_sorted: 1189 if chroot.name_release in unique_name_releases: 1190 continue 1191 unique_chroots.append(chroot) 1192 unique_name_releases.add(chroot.name_release) 1193 1194 return flask.render_template("coprs/detail/module.html", copr=copr, module=module, 1195 yaml=pretty_yaml, unique_chroots=unique_chroots)
1196
1197 1198 @coprs_ns.route("/<username>/<coprname>/module/<id>/raw") 1199 @coprs_ns.route("/g/<group_name>/<coprname>/module/<id>/raw") 1200 @req_with_copr 1201 -def copr_module_raw(copr, id):
1202 module = ModulesLogic.get(id).first() 1203 response = flask.make_response(module.yaml) 1204 response.mimetype = "text/plain" 1205 response.headers["Content-Disposition"] = \ 1206 "filename={}.yaml".format("-".join([str(module.id), module.name, module.stream, str(module.version)])) 1207 return response
1208