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 uuid 
   7  import subprocess 
   8  from six.moves.urllib.parse import urljoin 
   9   
  10  import flask 
  11  from flask import render_template, url_for, stream_with_context 
  12  import platform 
  13  import smtplib 
  14  import tempfile 
  15  import sqlalchemy 
  16  import modulemd 
  17  from email.mime.text import MIMEText 
  18  from itertools import groupby 
  19  from wtforms import ValidationError 
  20   
  21  from pygments import highlight 
  22  from pygments.lexers import get_lexer_by_name 
  23  from pygments.formatters import HtmlFormatter 
  24   
  25  from coprs import app 
  26  from coprs import db 
  27  from coprs import rcp 
  28  from coprs import exceptions 
  29  from coprs import forms 
  30  from coprs import helpers 
  31  from coprs import models 
  32  from coprs.exceptions import ObjectNotFound 
  33  from coprs.logic.coprs_logic import CoprsLogic 
  34  from coprs.logic.packages_logic import PackagesLogic 
  35  from coprs.logic.stat_logic import CounterStatLogic 
  36  from coprs.logic.users_logic import UsersLogic 
  37  from coprs.logic.modules_logic import ModulesLogic, ModulemdGenerator, ModuleBuildFacade 
  38  from coprs.rmodels import TimedStatEvents 
  39   
  40  from coprs.logic.complex_logic import ComplexLogic 
  41   
  42  from coprs.views.misc import login_required, page_not_found, req_with_copr, req_with_copr, generic_error 
  43   
  44  from coprs.views.coprs_ns import coprs_ns 
  45  from coprs.views.groups_ns import groups_ns 
  46   
  47  from coprs.logic import builds_logic, coprs_logic, actions_logic, users_logic 
  48  from coprs.helpers import parse_package_name, generate_repo_url, CHROOT_RPMS_DL_STAT_FMT, CHROOT_REPO_MD_DL_STAT_FMT, \ 
  49      str2bool, url_for_copr_view, REPO_DL_STAT_FMT, CounterStatType 
50 51 -def url_for_copr_details(copr):
52 return url_for_copr_view( 53 "coprs_ns.copr_detail", 54 "coprs_ns.group_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.group_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_tasks_from_last_day() 81 82 return flask.render_template("coprs/show/all.html", 83 coprs=coprs, 84 paginator=paginator, 85 tasks_info=ComplexLogic.get_queue_sizes(), 86 users_builds=users_builds, 87 graph=data)
88
89 90 @coprs_ns.route("/<username>/", defaults={"page": 1}) 91 @coprs_ns.route("/<username>/<int:page>/") 92 -def coprs_by_user(username=None, page=1):
93 user = users_logic.UsersLogic.get(username).first() 94 if not user: 95 return page_not_found( 96 "User {0} does not exist.".format(username)) 97 98 query = CoprsLogic.get_multiple_owned_by_username(username) 99 query = CoprsLogic.filter_without_group_projects(query) 100 query = CoprsLogic.set_query_order(query, desc=True) 101 102 paginator = helpers.Paginator(query, query.count(), page) 103 104 coprs = paginator.sliced_query 105 106 # flask.g.user is none when no user is logged - showing builds from everyone 107 users_builds = builds_logic.BuildsLogic.get_recent_tasks(flask.g.user, 4) 108 109 data = builds_logic.BuildsLogic.get_tasks_from_last_day() 110 111 return flask.render_template("coprs/show/user.html", 112 user=user, 113 coprs=coprs, 114 paginator=paginator, 115 tasks_info=ComplexLogic.get_queue_sizes(), 116 users_builds=users_builds, 117 graph=data)
118
119 120 @coprs_ns.route("/fulltext/", defaults={"page": 1}) 121 @coprs_ns.route("/fulltext/<int:page>/") 122 -def coprs_fulltext_search(page=1):
123 fulltext = flask.request.args.get("fulltext", "") 124 try: 125 query = coprs_logic.CoprsLogic.get_multiple_fulltext(fulltext) 126 except ValueError as e: 127 flask.flash(str(e), "error") 128 return flask.redirect(flask.request.referrer or 129 flask.url_for("coprs_ns.coprs_show")) 130 131 paginator = helpers.Paginator(query, query.count(), page, 132 additional_params={"fulltext": fulltext}) 133 134 data = builds_logic.BuildsLogic.get_tasks_from_last_day() 135 136 coprs = paginator.sliced_query 137 return render_template("coprs/show/fulltext.html", 138 coprs=coprs, 139 paginator=paginator, 140 fulltext=fulltext, 141 tasks_info=ComplexLogic.get_queue_sizes(), 142 graph=data)
143
144 145 @coprs_ns.route("/<username>/add/") 146 @login_required 147 -def copr_add(username):
148 form = forms.CoprFormFactory.create_form_cls()() 149 150 return flask.render_template("coprs/add.html", form=form)
151
152 153 @coprs_ns.route("/g/<group_name>/add/") 154 @login_required 155 -def group_copr_add(group_name):
156 group = ComplexLogic.get_group_by_name_safe(group_name) 157 form = forms.CoprFormFactory.create_form_cls()() 158 159 return flask.render_template( 160 "coprs/group_add.html", form=form, group=group)
161
162 163 @coprs_ns.route("/g/<group_name>/new/", methods=["POST"]) 164 @login_required 165 -def group_copr_new(group_name):
166 group = ComplexLogic.get_group_by_name_safe(group_name) 167 form = forms.CoprFormFactory.create_form_cls(group=group)() 168 169 if form.validate_on_submit(): 170 try: 171 copr = coprs_logic.CoprsLogic.add( 172 flask.g.user, 173 name=form.name.data, 174 homepage=form.homepage.data, 175 contact=form.contact.data, 176 repos=form.repos.data.replace("\n", " "), 177 selected_chroots=form.selected_chroots, 178 description=form.description.data, 179 instructions=form.instructions.data, 180 disable_createrepo=form.disable_createrepo.data, 181 build_enable_net=form.build_enable_net.data, 182 unlisted_on_hp=form.unlisted_on_hp.data, 183 group=group, 184 persistent=form.persistent.data, 185 auto_prune=(form.auto_prune.data if flask.g.user.admin else True), 186 use_bootstrap_container=form.use_bootstrap_container.data, 187 follow_fedora_branching=form.follow_fedora_branching.data, 188 ) 189 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e: 190 flask.flash(str(e), "error") 191 return flask.render_template("coprs/group_add.html", form=form, group=group) 192 193 db.session.add(copr) 194 db.session.commit() 195 after_the_project_creation(copr, form) 196 197 return flask.redirect(url_for_copr_details(copr)) 198 else: 199 return flask.render_template("coprs/group_add.html", form=form, group=group)
200
201 202 @coprs_ns.route("/<username>/new/", methods=["POST"]) 203 @login_required 204 -def copr_new(username):
205 """ 206 Receive information from the user on how to create its new copr 207 and create it accordingly. 208 """ 209 210 form = forms.CoprFormFactory.create_form_cls()() 211 if form.validate_on_submit(): 212 try: 213 copr = coprs_logic.CoprsLogic.add( 214 flask.g.user, 215 name=form.name.data, 216 homepage=form.homepage.data, 217 contact=form.contact.data, 218 repos=form.repos.data.replace("\n", " "), 219 selected_chroots=form.selected_chroots, 220 description=form.description.data, 221 instructions=form.instructions.data, 222 disable_createrepo=form.disable_createrepo.data, 223 build_enable_net=form.build_enable_net.data, 224 unlisted_on_hp=form.unlisted_on_hp.data, 225 persistent=form.persistent.data, 226 auto_prune=(form.auto_prune.data if flask.g.user.admin else True), 227 use_bootstrap_container=form.use_bootstrap_container.data, 228 follow_fedora_branching=form.follow_fedora_branching.data, 229 ) 230 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e: 231 flask.flash(str(e), "error") 232 return flask.render_template("coprs/add.html", form=form) 233 234 db.session.commit() 235 after_the_project_creation(copr, form) 236 237 return flask.redirect(url_for_copr_details(copr)) 238 else: 239 return flask.render_template("coprs/add.html", form=form)
240
241 242 -def after_the_project_creation(copr, form):
243 flask.flash("New project has been created successfully.", "success") 244 _check_rpmfusion(copr.repos) 245 if form.initial_pkgs.data: 246 pkgs = form.initial_pkgs.data.replace("\n", " ").split(" ") 247 248 # validate (and skip bad) urls 249 bad_urls = [] 250 for pkg in pkgs: 251 if not pkg.endswith(".src.rpm"): 252 bad_urls.append(pkg) 253 flask.flash("Bad url: {0} (skipped)".format(pkg)) 254 for bad_url in bad_urls: 255 pkgs.remove(bad_url) 256 257 if not pkgs: 258 flask.flash("No initial packages submitted") 259 else: 260 # build each package as a separate build 261 for pkg in pkgs: 262 builds_logic.BuildsLogic.add( 263 flask.g.user, 264 pkgs=pkg, 265 srpm_url=pkg, 266 copr=copr, 267 enable_net=form.build_enable_net.data 268 ) 269 270 db.session.commit() 271 flask.flash("Initial packages were successfully submitted " 272 "for building.")
273
274 275 @coprs_ns.route("/<username>/<coprname>/report-abuse") 276 @req_with_copr 277 @login_required 278 -def copr_report_abuse(copr):
279 return render_copr_report_abuse(copr)
280
281 282 @coprs_ns.route("/g/<group_name>/<coprname>/report-abuse") 283 @req_with_copr 284 @login_required 285 -def group_copr_report_abuse(copr):
286 return render_copr_report_abuse(copr)
287
288 289 -def render_copr_report_abuse(copr):
290 form = forms.CoprLegalFlagForm() 291 return render_template("coprs/report_abuse.html", copr=copr, form=form)
292
293 294 @coprs_ns.route("/g/<group_name>/<coprname>/") 295 @req_with_copr 296 -def group_copr_detail(copr):
297 return render_copr_detail(copr)
298
299 300 @coprs_ns.route("/<username>/<coprname>/") 301 @req_with_copr 302 -def copr_detail(copr):
303 if copr.is_a_group_project: 304 return flask.redirect(url_for_copr_details(copr)) 305 return render_copr_detail(copr)
306
307 308 -def render_copr_detail(copr):
309 repo_dl_stat = CounterStatLogic.get_copr_repo_dl_stat(copr) 310 form = forms.CoprLegalFlagForm() 311 repos_info = {} 312 for chroot in copr.active_chroots: 313 # chroot_rpms_dl_stat_key = CHROOT_REPO_MD_DL_STAT_FMT.format( 314 # copr_user=copr.user.name, 315 # copr_project_name=copr.name, 316 # copr_chroot=chroot.name, 317 # ) 318 chroot_rpms_dl_stat_key = CHROOT_RPMS_DL_STAT_FMT.format( 319 copr_user=copr.user.name, 320 copr_project_name=copr.name, 321 copr_chroot=chroot.name, 322 ) 323 chroot_rpms_dl_stat = TimedStatEvents.get_count( 324 rconnect=rcp.get_connection(), 325 name=chroot_rpms_dl_stat_key, 326 ) 327 328 logoset = set() 329 logodir = app.static_folder + "/chroot_logodir" 330 for logo in os.listdir(logodir): 331 # glob.glob() uses listdir() and fnmatch anyways 332 if fnmatch.fnmatch(logo, "*.png"): 333 logoset.add(logo.strip(".png")) 334 335 if chroot.name_release not in repos_info: 336 logo = None 337 if chroot.name_release in logoset: 338 logo = chroot.name_release + ".png" 339 elif chroot.os_release in logoset: 340 logo = chroot.os_release + ".png" 341 342 repos_info[chroot.name_release] = { 343 "name_release": chroot.name_release, 344 "name_release_human": chroot.name_release_human, 345 "os_release": chroot.os_release, 346 "os_version": chroot.os_version, 347 "logo": logo, 348 "arch_list": [chroot.arch], 349 "repo_file": "{}-{}.repo".format(copr.repo_id, chroot.name_release), 350 "dl_stat": repo_dl_stat[chroot.name_release], 351 "rpm_dl_stat": { 352 chroot.arch: chroot_rpms_dl_stat 353 } 354 } 355 else: 356 repos_info[chroot.name_release]["arch_list"].append(chroot.arch) 357 repos_info[chroot.name_release]["rpm_dl_stat"][chroot.arch] = chroot_rpms_dl_stat 358 repos_info_list = sorted(repos_info.values(), key=lambda rec: rec["name_release"]) 359 builds = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr).limit(1).all() 360 361 return flask.render_template( 362 "coprs/detail/overview.html", 363 copr=copr, 364 user=flask.g.user, 365 form=form, 366 repo_dl_stat=repo_dl_stat, 367 repos_info_list=repos_info_list, 368 latest_build=builds[0] if len(builds) == 1 else None, 369 )
370
371 372 @coprs_ns.route("/<username>/<coprname>/permissions/") 373 @req_with_copr 374 -def copr_permissions(copr):
375 permissions = coprs_logic.CoprPermissionsLogic.get_for_copr(copr).all() 376 if flask.g.user: 377 user_perm = flask.g.user.permissions_for_copr(copr) 378 else: 379 user_perm = None 380 381 permissions_applier_form = None 382 permissions_form = None 383 384 # generate a proper form for displaying 385 if flask.g.user: 386 # https://github.com/ajford/flask-wtf/issues/58 387 permissions_applier_form = \ 388 forms.PermissionsApplierFormFactory.create_form_cls( 389 user_perm)(formdata=None) 390 391 if flask.g.user.can_edit(copr): 392 permissions_form = forms.PermissionsFormFactory.create_form_cls( 393 permissions)() 394 395 return flask.render_template( 396 "coprs/detail/settings/permissions.html", 397 copr=copr, 398 permissions_form=permissions_form, 399 permissions_applier_form=permissions_applier_form, 400 permissions=permissions, 401 current_user_permissions=user_perm)
402
403 404 -def render_copr_webhooks(copr):
405 if not copr.webhook_secret: 406 copr.webhook_secret = str(uuid.uuid4()) 407 db.session.add(copr) 408 db.session.commit() 409 410 bitbucket_url = "https://{}/webhooks/bitbucket/{}/{}/".format( 411 app.config["PUBLIC_COPR_HOSTNAME"], 412 copr.id, 413 copr.webhook_secret) 414 415 github_url = "https://{}/webhooks/github/{}/{}/".format( 416 app.config["PUBLIC_COPR_HOSTNAME"], 417 copr.id, 418 copr.webhook_secret) 419 420 gitlab_url = "https://{}/webhooks/gitlab/{}/{}/".format( 421 app.config["PUBLIC_COPR_HOSTNAME"], 422 copr.id, 423 copr.webhook_secret) 424 425 custom_url = "https://{}/webhooks/custom/{}/{}/".format( 426 app.config["PUBLIC_COPR_HOSTNAME"], 427 copr.id, 428 copr.webhook_secret) + "<PACKAGE_NAME>/" 429 430 return flask.render_template( 431 "coprs/detail/settings/webhooks.html", 432 copr=copr, bitbucket_url=bitbucket_url, github_url=github_url, 433 gitlab_url=gitlab_url, custom_url=custom_url)
434
435 436 @coprs_ns.route("/g/<group_name>/<coprname>/webhooks/") 437 @login_required 438 @req_with_copr 439 -def group_copr_webhooks(copr):
440 return render_copr_webhooks(copr)
441
442 443 @coprs_ns.route("/<username>/<coprname>/webhooks/") 444 @login_required 445 @req_with_copr 446 -def copr_webhooks(copr):
447 return render_copr_webhooks(copr)
448
449 450 -def render_copr_edit(copr, form, view):
451 if not form: 452 form = forms.CoprFormFactory.create_form_cls( 453 copr.mock_chroots)(obj=copr) 454 return flask.render_template( 455 "coprs/detail/settings/edit.html", 456 copr=copr, form=form, view=view)
457
458 459 @coprs_ns.route("/g/<group_name>/<coprname>/edit/") 460 @login_required 461 @req_with_copr 462 -def group_copr_edit(copr, form=None):
463 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
464
465 466 @coprs_ns.route("/<username>/<coprname>/edit/") 467 @login_required 468 @req_with_copr 469 -def copr_edit(copr, form=None):
470 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
471
472 473 -def _check_rpmfusion(repos):
474 if "rpmfusion" in repos: 475 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>.') 476 flask.flash(message, "error")
477
478 479 -def process_copr_update(copr, form):
480 copr.name = form.name.data 481 copr.homepage = form.homepage.data 482 copr.contact = form.contact.data 483 copr.repos = form.repos.data.replace("\n", " ") 484 copr.description = form.description.data 485 copr.instructions = form.instructions.data 486 copr.disable_createrepo = form.disable_createrepo.data 487 copr.build_enable_net = form.build_enable_net.data 488 copr.unlisted_on_hp = form.unlisted_on_hp.data 489 copr.use_bootstrap_container = form.use_bootstrap_container.data 490 copr.follow_fedora_branching = form.follow_fedora_branching.data 491 if flask.g.user.admin: 492 copr.auto_prune = form.auto_prune.data 493 else: 494 copr.auto_prune = True 495 coprs_logic.CoprChrootsLogic.update_from_names( 496 flask.g.user, copr, form.selected_chroots) 497 try: 498 # form validation checks for duplicates 499 coprs_logic.CoprsLogic.update(flask.g.user, copr) 500 except (exceptions.ActionInProgressException, 501 exceptions.InsufficientRightsException) as e: 502 503 flask.flash(str(e), "error") 504 db.session.rollback() 505 else: 506 flask.flash("Project has been updated successfully.", "success") 507 db.session.commit() 508 _check_rpmfusion(copr.repos)
509
510 511 @coprs_ns.route("/g/<group_name>/<coprname>/update/", methods=["POST"]) 512 @login_required 513 @req_with_copr 514 -def group_copr_update(copr):
515 form = forms.CoprFormFactory.create_form_cls(group=copr.group)() 516 517 if form.validate_on_submit(): 518 process_copr_update(copr, form) 519 return flask.redirect(url_for( 520 "coprs_ns.group_copr_detail", 521 group_name=copr.group.name, coprname=copr.name 522 )) 523 524 else: 525 return group_copr_edit(group_name=copr.group.name, coprname=copr.name, form=form)
526
527 528 @coprs_ns.route("/<username>/<coprname>/update/", methods=["POST"]) 529 @login_required 530 @req_with_copr 531 -def copr_update(copr):
532 form = forms.CoprFormFactory.create_form_cls(user=copr.user)() 533 534 if form.validate_on_submit(): 535 process_copr_update(copr, form) 536 return flask.redirect(url_for_copr_details(copr)) 537 else: 538 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
539 540 541 @coprs_ns.route("/<username>/<coprname>/permissions_applier_change/", 542 methods=["POST"])
543 @login_required 544 @req_with_copr 545 -def copr_permissions_applier_change(copr):
546 permission = coprs_logic.CoprPermissionsLogic.get(copr, flask.g.user).first() 547 applier_permissions_form = \ 548 forms.PermissionsApplierFormFactory.create_form_cls(permission)() 549 550 if copr.user == flask.g.user: 551 flask.flash("Owner cannot request permissions for his own project.", "error") 552 elif applier_permissions_form.validate_on_submit(): 553 # we rely on these to be 0 or 1 from form. TODO: abstract from that 554 if permission is not None: 555 old_builder = permission.copr_builder 556 old_admin = permission.copr_admin 557 else: 558 old_builder = 0 559 old_admin = 0 560 new_builder = applier_permissions_form.copr_builder.data 561 new_admin = applier_permissions_form.copr_admin.data 562 coprs_logic.CoprPermissionsLogic.update_permissions_by_applier( 563 flask.g.user, copr, permission, new_builder, new_admin) 564 db.session.commit() 565 flask.flash( 566 "Successfully updated permissions for project '{0}'." 567 .format(copr.name)) 568 admin_mails = [copr.user.mail] 569 for perm in copr.copr_permissions: 570 # this 2 means that his status (admin) is approved 571 if perm.copr_admin == 2: 572 admin_mails.append(perm.user.mail) 573 574 # sending emails 575 if flask.current_app.config.get("SEND_EMAILS", False): 576 for mail in admin_mails: 577 msg = MIMEText( 578 "{6} is asking for these permissions:\n\n" 579 "Builder: {0} -> {1}\nAdmin: {2} -> {3}\n\n" 580 "Project: {4}\nOwner: {5}".format( 581 helpers.PermissionEnum(old_builder), 582 helpers.PermissionEnum(new_builder), 583 helpers.PermissionEnum(old_admin), 584 helpers.PermissionEnum(new_admin), 585 copr.name, copr.user.name, flask.g.user.name)) 586 587 msg["Subject"] = "[Copr] {0}: {1} is asking permissions".format(copr.name, flask.g.user.name) 588 msg["From"] = "root@{0}".format(platform.node()) 589 msg["To"] = mail 590 s = smtplib.SMTP("localhost") 591 s.sendmail("root@{0}".format(platform.node()), mail, msg.as_string()) 592 s.quit() 593 594 return flask.redirect(flask.url_for("coprs_ns.copr_detail", 595 username=copr.user.name, 596 coprname=copr.name))
597
598 599 @coprs_ns.route("/<username>/<coprname>/update_permissions/", methods=["POST"]) 600 @login_required 601 @req_with_copr 602 -def copr_update_permissions(copr):
603 permissions = copr.copr_permissions 604 permissions_form = forms.PermissionsFormFactory.create_form_cls( 605 permissions)() 606 607 if permissions_form.validate_on_submit(): 608 # we don't change owner (yet) 609 try: 610 # if admin is changing his permissions, his must be changed last 611 # so that we don't get InsufficientRightsException 612 permissions.sort( 613 key=lambda x: -1 if x.user_id == flask.g.user.id else 1) 614 for perm in permissions: 615 old_builder = perm.copr_builder 616 old_admin = perm.copr_admin 617 new_builder = permissions_form[ 618 "copr_builder_{0}".format(perm.user_id)].data 619 new_admin = permissions_form[ 620 "copr_admin_{0}".format(perm.user_id)].data 621 coprs_logic.CoprPermissionsLogic.update_permissions( 622 flask.g.user, copr, perm, new_builder, new_admin) 623 if flask.current_app.config.get("SEND_EMAILS", False) and \ 624 (old_builder is not new_builder or old_admin is not new_admin): 625 626 msg = MIMEText( 627 "Your permissions have changed:\n\n" 628 "Builder: {0} -> {1}\nAdmin: {2} -> {3}\n\n" 629 "Project: {4}\nOwner: {5}".format( 630 helpers.PermissionEnum(old_builder), 631 helpers.PermissionEnum(new_builder), 632 helpers.PermissionEnum(old_admin), 633 helpers.PermissionEnum(new_admin), 634 copr.name, copr.user.name)) 635 636 msg["Subject"] = "[Copr] {0}: Your permissions have changed".format(copr.name) 637 msg["From"] = "root@{0}".format(platform.node()) 638 msg["To"] = perm.user.mail 639 s = smtplib.SMTP("localhost") 640 s.sendmail("root@{0}".format(platform.node()), perm.user.mail, msg.as_string()) 641 s.quit() 642 # for now, we don't check for actions here, as permissions operation 643 # don't collide with any actions 644 except exceptions.InsufficientRightsException as e: 645 db.session.rollback() 646 flask.flash(str(e), "error") 647 else: 648 db.session.commit() 649 flask.flash("Project permissions were updated successfully.", "success") 650 651 return flask.redirect(url_for_copr_details(copr))
652
653 654 @coprs_ns.route("/id/<copr_id>/createrepo/", methods=["POST"]) 655 @login_required 656 -def copr_createrepo(copr_id):
657 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 658 if not flask.g.user.can_edit(copr): 659 flask.flash( 660 "You are not allowed to recreate repository metadata of copr with id {}.".format(copr_id), "error") 661 return flask.redirect(url_for_copr_details(copr)) 662 663 chroots = [c.name for c in copr.active_chroots] 664 actions_logic.ActionsLogic.send_createrepo( 665 username=copr.owner_name, coprname=copr.name, 666 chroots=chroots) 667 668 db.session.commit() 669 flask.flash("Repository metadata will be regenerated in a few minutes ...") 670 return flask.redirect(url_for_copr_details(copr))
671
672 673 -def process_delete(copr, url_on_error, url_on_success):
674 form = forms.CoprDeleteForm() 675 if form.validate_on_submit(): 676 677 try: 678 ComplexLogic.delete_copr(copr) 679 except (exceptions.ActionInProgressException, 680 exceptions.InsufficientRightsException) as e: 681 682 db.session.rollback() 683 flask.flash(str(e), "error") 684 return flask.redirect(url_on_error) 685 else: 686 db.session.commit() 687 flask.flash("Project has been deleted successfully.") 688 return flask.redirect(url_on_success) 689 else: 690 return render_template("coprs/detail/settings/delete.html", form=form, copr=copr)
691
692 693 @coprs_ns.route("/<username>/<coprname>/delete/", methods=["GET", "POST"]) 694 @login_required 695 @req_with_copr 696 -def copr_delete(copr):
697 return process_delete( 698 copr, 699 url_on_error=url_for("coprs_ns.copr_detail", 700 username=copr.user.name, coprname=copr.name), 701 url_on_success=url_for("coprs_ns.coprs_by_user", username=copr.user.username) 702 )
703
704 705 @coprs_ns.route("/g/<group_name>/<coprname>/delete/", methods=["GET", "POST"]) 706 @login_required 707 @req_with_copr 708 -def group_copr_delete(copr):
709 710 return process_delete( 711 copr, 712 url_on_error=url_for('coprs_ns.group_copr_detail', 713 group_name=copr.group.name, coprname=copr.name), 714 url_on_success=url_for('groups_ns.list_projects_by_group', 715 group_name=copr.group.name) 716 )
717 725 733 765
766 767 @coprs_ns.route("/<username>/<coprname>/repo/<name_release>/", defaults={"repofile": None}) 768 @coprs_ns.route("/<username>/<coprname>/repo/<name_release>/<repofile>") 769 -def generate_repo_file(username, coprname, name_release, repofile):
770 """ Generate repo file for a given repo name. 771 Reponame = username-coprname """ 772 # This solution is used because flask splits off the last part after a 773 # dash, therefore user-re-po resolves to user-re/po instead of user/re-po 774 # FAS usernames may not contain dashes, so this construction is safe. 775 776 # support access to the group projects using @-notation 777 # todo: remove when yum/dnf plugin is updated to use new url schema 778 if username.startswith("@"): 779 return group_generate_repo_file(group_name=username[1:], coprname=coprname, 780 name_release=name_release, repofile=repofile) 781 782 copr = ComplexLogic.get_copr_safe(username, coprname) 783 return render_generate_repo_file(copr, name_release)
784
785 786 @coprs_ns.route("/g/<group_name>/<coprname>/repo/<name_release>/", defaults={"repofile": None}) 787 @coprs_ns.route("/g/<group_name>/<coprname>/repo/<name_release>/<repofile>") 788 @req_with_copr 789 -def group_generate_repo_file(copr, name_release, repofile):
790 """ Generate repo file for a given repo name. 791 Reponame = username-coprname """ 792 # This solution is used because flask splits off the last part after a 793 # dash, therefore user-re-po resolves to user-re/po instead of user/re-po 794 # FAS usernames may not contain dashes, so this construction is safe. 795 796 return render_generate_repo_file(copr, name_release)
797
798 799 -def render_generate_repo_file(copr, name_release):
800 801 # we need to check if we really got name release or it's a full chroot (caused by old dnf plugin) 802 if name_release in [c.name for c in copr.mock_chroots]: 803 chroot = [c for c in copr.mock_chroots if c.name == name_release][0] 804 kwargs = dict(coprname=copr.name, name_release=chroot.name_release) 805 if copr.is_a_group_project: 806 fixed_url = url_for("coprs_ns.group_generate_repo_file", 807 group_name=copr.group.name, **kwargs) 808 else: 809 fixed_url = url_for("coprs_ns.generate_repo_file", 810 username=copr.user.username, **kwargs) 811 return flask.redirect(fixed_url) 812 813 mock_chroot = coprs_logic.MockChrootsLogic.get_from_name(name_release, noarch=True).first() 814 if not mock_chroot: 815 raise ObjectNotFound("Chroot {} does not exist".format(name_release)) 816 817 url = os.path.join(copr.repo_url, '') # adds trailing slash 818 repo_url = generate_repo_url(mock_chroot, url) 819 pubkey_url = urljoin(url, "pubkey.gpg") 820 response = flask.make_response( 821 flask.render_template("coprs/copr.repo", copr=copr, url=repo_url, pubkey_url=pubkey_url)) 822 response.mimetype = "text/plain" 823 response.headers["Content-Disposition"] = \ 824 "filename={0}.repo".format(copr.repo_name) 825 826 name = REPO_DL_STAT_FMT.format(**{ 827 'copr_user': copr.user.name, 828 'copr_project_name': copr.name, 829 'copr_name_release': name_release, 830 }) 831 CounterStatLogic.incr(name=name, counter_type=CounterStatType.REPO_DL) 832 db.session.commit() 833 834 return response
835
836 837 ######################################################### 838 ### Module repo files ### 839 ######################################################### 840 841 @coprs_ns.route("/<username>/<coprname>/module_repo/<name_release>/<module_nsv>.repo") 842 @coprs_ns.route("/g/<group_name>/<coprname>/module_repo/<name_release>/<module_nsv>.repo") 843 @req_with_copr 844 -def generate_module_repo_file(copr, name_release, module_nsv):
845 """ Generate module repo file for a given project. """ 846 return render_generate_module_repo_file(copr, name_release, module_nsv)
847
848 -def render_generate_module_repo_file(copr, name_release, module_nsv):
849 module = ModulesLogic.get_by_nsv_str(copr, module_nsv).one() 850 mock_chroot = coprs_logic.MockChrootsLogic.get_from_name(name_release, noarch=True).first() 851 url = os.path.join(copr.repo_url, '') # adds trailing slash 852 repo_url = generate_repo_url(mock_chroot, copr.modules_url) 853 baseurl = "{}+{}/latest/$basearch".format(repo_url.rstrip("/"), module_nsv) 854 pubkey_url = urljoin(url, "pubkey.gpg") 855 response = flask.make_response( 856 flask.render_template("coprs/copr-modules.cfg", copr=copr, module=module, 857 baseurl=baseurl, pubkey_url=pubkey_url)) 858 response.mimetype = "text/plain" 859 response.headers["Content-Disposition"] = \ 860 "filename={0}.cfg".format(copr.repo_name) 861 return response
862
863 ######################################################### 864 865 @coprs_ns.route("/<username>/<coprname>/rpm/<name_release>/<rpmfile>") 866 -def copr_repo_rpm_file(username, coprname, name_release, rpmfile):
867 try: 868 packages_dir = os.path.join(app.config["DATA_DIR"], "repo-rpm-packages") 869 with open(os.path.join(packages_dir, rpmfile), "rb") as rpm: 870 response = flask.make_response(rpm.read()) 871 response.mimetype = "application/x-rpm" 872 response.headers["Content-Disposition"] = \ 873 "filename={0}".format(rpmfile) 874 return response 875 except IOError: 876 return flask.render_template("404.html")
877
878 879 -def render_monitor(copr, detailed=False):
880 monitor = builds_logic.BuildsMonitorLogic.get_monitor_data(copr) 881 oses = [chroot.os for chroot in copr.active_chroots_sorted] 882 oses_grouped = [(len(list(group)), key) for key, group in groupby(oses)] 883 archs = [chroot.arch for chroot in copr.active_chroots_sorted] 884 if detailed: 885 template = "coprs/detail/monitor/detailed.html" 886 else: 887 template = "coprs/detail/monitor/simple.html" 888 return flask.Response(stream_with_context(helpers.stream_template(template, 889 copr=copr, 890 monitor=monitor, 891 oses=oses_grouped, 892 archs=archs, 893 status_enum_func=helpers.StatusEnum)))
894
895 896 @coprs_ns.route("/<username>/<coprname>/monitor/") 897 @coprs_ns.route("/<username>/<coprname>/monitor/<detailed>") 898 @req_with_copr 899 -def copr_build_monitor(copr, detailed=False):
900 return render_monitor(copr, detailed == "detailed")
901
902 903 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/") 904 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/<detailed>") 905 @req_with_copr 906 -def group_copr_build_monitor(copr, detailed=False):
907 return render_monitor(copr, detailed == "detailed")
908
909 910 @coprs_ns.route("/<username>/<coprname>/fork/") 911 @coprs_ns.route("/g/<group_name>/<coprname>/fork/") 912 @login_required 913 @req_with_copr 914 -def copr_fork(copr):
915 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)() 916 return render_copr_fork(copr, form)
917
918 919 -def render_copr_fork(copr, form, confirm=False):
920 return flask.render_template("coprs/fork.html", copr=copr, form=form, confirm=confirm)
921
922 923 @coprs_ns.route("/<username>/<coprname>/fork/", methods=["POST"]) 924 @coprs_ns.route("/g/<group_name>/<coprname>/fork/", methods=["POST"]) 925 @login_required 926 @req_with_copr 927 -def copr_fork_post(copr):
928 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)() 929 if form.validate_on_submit(): 930 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0] 931 if flask.g.user.name != form.owner.data and not dstgroup: 932 return generic_error("There is no such group: {}".format(form.owner.data)) 933 934 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup) 935 if created: 936 msg = ("Forking project {} for you into {}. Please be aware that it may take a few minutes " 937 "to duplicate backend data.".format(copr.full_name, fcopr.full_name)) 938 elif not created and form.confirm.data == True: 939 msg = ("Updating packages in {} from {}. Please be aware that it may take a few minutes " 940 "to duplicate backend data.".format(copr.full_name, fcopr.full_name)) 941 else: 942 return render_copr_fork(copr, form, confirm=True) 943 944 db.session.commit() 945 flask.flash(msg) 946 947 return flask.redirect(url_for_copr_details(fcopr)) 948 return render_copr_fork(copr, form)
949
950 951 @coprs_ns.route("/update_search_index/", methods=["POST"]) 952 -def copr_update_search_index():
953 subprocess.call(['/usr/share/copr/coprs_frontend/manage.py', 'update_indexes_quick', '1']) 954 return "OK"
955
956 957 @coprs_ns.route("/<username>/<coprname>/modules/") 958 @coprs_ns.route("/g/<group_name>/<coprname>/modules/") 959 @req_with_copr 960 -def copr_modules(copr):
961 return render_copr_modules(copr)
962
963 964 -def render_copr_modules(copr):
965 modules = ModulesLogic.get_multiple_by_copr(copr=copr).all() 966 return flask.render_template("coprs/detail/modules.html", copr=copr, modules=modules)
967
968 969 @coprs_ns.route("/<username>/<coprname>/create_module/") 970 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/") 971 @login_required 972 @req_with_copr 973 -def copr_create_module(copr):
974 form = forms.CreateModuleForm() 975 return render_create_module(copr, form)
976
977 978 -def render_create_module(copr, form, profiles=2):
979 built_packages = [] 980 for build in filter(None, [p.last_build(successful=True) for p in copr.packages]): 981 for package in build.built_packages.split("\n"): 982 built_packages.append((package.split()[0], build)) 983 984 return flask.render_template("coprs/create_module.html", copr=copr, form=form, built_packages=built_packages, profiles=profiles)
985
986 987 @coprs_ns.route("/<username>/<coprname>/create_module/", methods=["POST"]) 988 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/", methods=["POST"]) 989 @login_required 990 @req_with_copr 991 -def copr_create_module_post(copr):
992 form = forms.CreateModuleForm(copr=copr, csrf_enabled=False) 993 args = [copr, form] 994 if "add_profile" in flask.request.values: 995 return add_profile(*args) 996 if "build_module" in flask.request.values: 997 return build_module(*args)
998 # @TODO Error
999 1000 1001 -def add_profile(copr, form):
1002 n = len(form.profile_names) + 1 1003 form.profile_names.append_entry() 1004 for i in range(2, n): 1005 form.profile_pkgs.append_entry() 1006 return render_create_module(copr, form, profiles=n)
1007
1008 1009 -def build_module(copr, form):
1010 if not form.validate_on_submit(): 1011 # WORKAROUND append those which are not in min_entries 1012 for i in range(2, len(form.profile_names)): 1013 form.profile_pkgs.append_entry() 1014 return render_create_module(copr, form, profiles=len(form.profile_names)) 1015 1016 summary = "Module from Copr repository: {}".format(copr.full_name) 1017 generator = ModulemdGenerator(str(copr.name), summary=summary, config=app.config) 1018 generator.add_filter(form.filter.data) 1019 generator.add_api(form.api.data) 1020 generator.add_profiles(enumerate(zip(form.profile_names.data, form.profile_pkgs.data))) 1021 generator.add_components(form.packages.data, form.filter.data, form.builds.data) 1022 yaml = generator.generate() 1023 1024 facade = None 1025 try: 1026 facade = ModuleBuildFacade(flask.g.user, copr, yaml) 1027 module = facade.submit_build() 1028 db.session.commit() 1029 1030 flask.flash("Modulemd yaml file successfully generated and submitted to be build as {}" 1031 .format(module.nsv), "success") 1032 return flask.redirect(url_for_copr_details(copr)) 1033 1034 except ValidationError as ex: 1035 flask.flash(ex.message, "error") 1036 return render_create_module(copr, form, len(form.profile_names)) 1037 1038 except sqlalchemy.exc.IntegrityError: 1039 flask.flash("Module {}-{}-{} already exists".format( 1040 facade.modulemd.name, facade.modulemd.stream, facade.modulemd.version), "error") 1041 db.session.rollback() 1042 return render_create_module(copr, form, len(form.profile_names))
1043
1044 1045 @coprs_ns.route("/<username>/<coprname>/module/<id>") 1046 @coprs_ns.route("/g/<group_name>/<coprname>/module/<id>") 1047 @req_with_copr 1048 -def copr_module(copr, id):
1049 module = ModulesLogic.get(id).first() 1050 formatter = HtmlFormatter(style="autumn", linenos=False, noclasses=True) 1051 pretty_yaml = highlight(module.yaml, get_lexer_by_name("YAML"), formatter) 1052 1053 # Get the list of chroots with unique name_release attribute 1054 # Once we use jinja in 2.10 version, we can simply use 1055 # {{ copr.active_chroots |unique(attribute='name_release') }} 1056 unique_chroots = [] 1057 unique_name_releases = set() 1058 for chroot in copr.active_chroots_sorted: 1059 if chroot.name_release in unique_name_releases: 1060 continue 1061 unique_chroots.append(chroot) 1062 unique_name_releases.add(chroot.name_release) 1063 1064 return flask.render_template("coprs/detail/module.html", copr=copr, module=module, 1065 yaml=pretty_yaml, unique_chroots=unique_chroots)
1066
1067 1068 @coprs_ns.route("/<username>/<coprname>/module/<id>/raw") 1069 @coprs_ns.route("/g/<group_name>/<coprname>/module/<id>/raw") 1070 @req_with_copr 1071 -def copr_module_raw(copr, id):
1072 module = ModulesLogic.get(id).first() 1073 response = flask.make_response(module.yaml) 1074 response.mimetype = "text/plain" 1075 response.headers["Content-Disposition"] = \ 1076 "filename={}.yaml".format("-".join([str(module.id), module.name, module.stream, str(module.version)])) 1077 return response
1078