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 copr_common.enums import StatusEnum 
  23  from coprs import app 
  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   
  39  from coprs.views.misc import (login_required, page_not_found, req_with_copr, 
  40                                generic_error, req_with_copr_dir) 
  41   
  42  from coprs.views.coprs_ns import coprs_ns 
  43   
  44  from coprs.logic import builds_logic, coprs_logic, actions_logic, users_logic 
  45  from coprs.helpers import generate_repo_url, CHROOT_RPMS_DL_STAT_FMT, \ 
  46      url_for_copr_view, REPO_DL_STAT_FMT, CounterStatType 
47 48 -def url_for_copr_details(copr):
49 return url_for_copr_view( 50 "coprs_ns.copr_detail", 51 "coprs_ns.copr_detail", 52 copr)
53
54 55 -def url_for_copr_edit(copr):
56 return url_for_copr_view( 57 "coprs_ns.copr_edit", 58 "coprs_ns.copr_edit", 59 copr)
60
61 62 @coprs_ns.route("/", defaults={"page": 1}) 63 @coprs_ns.route("/<int:page>/") 64 -def coprs_show(page=1):
65 query = CoprsLogic.get_multiple(include_unlisted_on_hp=False) 66 query = CoprsLogic.set_query_order(query, desc=True) 67 68 paginator = helpers.Paginator(query, query.count(), page) 69 70 coprs = paginator.sliced_query 71 72 # flask.g.user is none when no user is logged - showing builds from everyone 73 # TODO: builds_logic.BuildsLogic.get_recent_tasks(flask.g.user, 5) takes too much time, optimize sql 74 # users_builds = builds_logic.BuildsLogic.get_recent_tasks(flask.g.user, 5) 75 users_builds = builds_logic.BuildsLogic.get_recent_tasks(None, 4) 76 77 data = builds_logic.BuildsLogic.get_small_graph_data('30min') 78 79 return flask.render_template("coprs/show/all.html", 80 coprs=coprs, 81 pinned=[], 82 paginator=paginator, 83 tasks_info=ComplexLogic.get_queue_sizes(), 84 users_builds=users_builds, 85 graph=data)
86
87 88 @coprs_ns.route("/<username>/", defaults={"page": 1}) 89 @coprs_ns.route("/<username>/<int:page>/") 90 -def coprs_by_user(username=None, page=1):
91 user = users_logic.UsersLogic.get(username).first() 92 if not user: 93 return page_not_found( 94 "User {0} does not exist.".format(username)) 95 96 pinned = [pin.copr for pin in PinnedCoprsLogic.get_by_user_id(user.id)] if page == 1 else [] 97 query = CoprsLogic.get_multiple_owned_by_username(username) 98 query = CoprsLogic.filter_without_ids(query, [copr.id for copr in pinned]) 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 coprs = paginator.sliced_query 104 105 # flask.g.user is none when no user is logged - showing builds from everyone 106 users_builds = builds_logic.BuildsLogic.get_recent_tasks(flask.g.user, 4) 107 108 data = builds_logic.BuildsLogic.get_small_graph_data('30min') 109 110 return flask.render_template("coprs/show/user.html", 111 user=user, 112 coprs=coprs, 113 pinned=pinned, 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_small_graph_data('30min') 135 136 coprs = paginator.sliced_query 137 return render_template("coprs/show/fulltext.html", 138 coprs=coprs, 139 pinned=[], 140 paginator=paginator, 141 fulltext=fulltext, 142 tasks_info=ComplexLogic.get_queue_sizes(), 143 graph=data)
144
145 146 @coprs_ns.route("/<username>/add/") 147 @coprs_ns.route("/g/<group_name>/add/") 148 @login_required 149 -def copr_add(username=None, group_name=None):
150 form = forms.CoprFormFactory.create_form_cls()() 151 if group_name: 152 group = ComplexLogic.get_group_by_name_safe(group_name) 153 return flask.render_template("coprs/group_add.html", form=form, group=group) 154 return flask.render_template("coprs/add.html", form=form)
155
156 157 @coprs_ns.route("/<username>/new/", methods=["POST"]) 158 @coprs_ns.route("/g/<group_name>/new/", methods=["POST"]) 159 @login_required 160 -def copr_new(username=None, group_name=None):
161 """ 162 Receive information from the user (and group) on how to create its new copr 163 and create it accordingly. 164 """ 165 group = None 166 redirect = "coprs/add.html" 167 if group_name: 168 group = ComplexLogic.get_group_by_name_safe(group_name) 169 redirect = "coprs/group_add.html" 170 171 form = forms.CoprFormFactory.create_form_cls(group=group)() 172 if form.validate_on_submit(): 173 try: 174 copr = coprs_logic.CoprsLogic.add( 175 flask.g.user, 176 name=form.name.data, 177 homepage=form.homepage.data, 178 contact=form.contact.data, 179 repos=form.repos.data.replace("\n", " "), 180 selected_chroots=form.selected_chroots, 181 description=form.description.data, 182 instructions=form.instructions.data, 183 disable_createrepo=form.disable_createrepo.data, 184 build_enable_net=form.build_enable_net.data, 185 unlisted_on_hp=form.unlisted_on_hp.data, 186 group=group, 187 persistent=form.persistent.data, 188 auto_prune=(form.auto_prune.data if flask.g.user.admin else True), 189 use_bootstrap_container=form.use_bootstrap_container.data, 190 follow_fedora_branching=form.follow_fedora_branching.data, 191 delete_after_days=form.delete_after_days.data, 192 multilib=form.multilib.data, 193 ) 194 195 db.session.commit() 196 after_the_project_creation(copr, form) 197 return flask.redirect(url_for_copr_details(copr)) 198 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e: 199 flask.flash(str(e), "error") 200 201 return flask.render_template(redirect, form=form, group=group)
202
203 204 -def after_the_project_creation(copr, form):
205 flask.flash("New project has been created successfully.", "success") 206 _check_rpmfusion(copr.repos) 207 if form.initial_pkgs.data: 208 pkgs = form.initial_pkgs.data.replace("\n", " ").split(" ") 209 210 # validate (and skip bad) urls 211 bad_urls = [] 212 for pkg in pkgs: 213 if not pkg.endswith(".src.rpm"): 214 bad_urls.append(pkg) 215 flask.flash("Bad url: {0} (skipped)".format(pkg)) 216 for bad_url in bad_urls: 217 pkgs.remove(bad_url) 218 219 if not pkgs: 220 flask.flash("No initial packages submitted") 221 else: 222 # build each package as a separate build 223 for pkg in pkgs: 224 builds_logic.BuildsLogic.add( 225 flask.g.user, 226 pkgs=pkg, 227 srpm_url=pkg, 228 copr=copr, 229 enable_net=form.build_enable_net.data 230 ) 231 232 db.session.commit() 233 flask.flash("Initial packages were successfully submitted " 234 "for building.")
235
236 237 @coprs_ns.route("/<username>/<coprname>/report-abuse") 238 @coprs_ns.route("/g/<group_name>/<coprname>/report-abuse") 239 @req_with_copr 240 @login_required 241 -def copr_report_abuse(copr):
242 return render_copr_report_abuse(copr)
243
244 245 -def render_copr_report_abuse(copr):
246 form = forms.CoprLegalFlagForm() 247 return render_template("coprs/report_abuse.html", copr=copr, form=form)
248
249 250 @coprs_ns.route("/<username>/<coprname>/") 251 @coprs_ns.route("/g/<group_name>/<coprname>/") 252 @req_with_copr 253 -def copr_detail(copr):
254 return render_copr_detail(copr)
255
256 257 -def render_copr_detail(copr):
258 repo_dl_stat = CounterStatLogic.get_copr_repo_dl_stat(copr) 259 form = forms.CoprLegalFlagForm() 260 repos_info = {} 261 for chroot in copr.active_chroots: 262 chroot_rpms_dl_stat_key = CHROOT_RPMS_DL_STAT_FMT.format( 263 copr_user=copr.owner_name, 264 copr_project_name=copr.name, 265 copr_chroot=chroot.name, 266 ) 267 chroot_rpms_dl_stat = TimedStatEvents.get_count( 268 rconnect=rcp.get_connection(), 269 name=chroot_rpms_dl_stat_key, 270 ) 271 272 logoset = set() 273 logodir = app.static_folder + "/chroot_logodir" 274 for logo in os.listdir(logodir): 275 # glob.glob() uses listdir() and fnmatch anyways 276 if fnmatch.fnmatch(logo, "*.png"): 277 logoset.add(logo[:-4]) 278 279 if chroot.name_release not in repos_info: 280 logo = None 281 if chroot.name_release in logoset: 282 logo = chroot.name_release + ".png" 283 elif chroot.os_release in logoset: 284 logo = chroot.os_release + ".png" 285 286 repos_info[chroot.name_release] = { 287 "name_release": chroot.name_release, 288 "os_release": chroot.os_release, 289 "os_version": chroot.os_version, 290 "logo": logo, 291 "arch_list": [chroot.arch], 292 "repo_file": "{}-{}.repo".format(copr.repo_id, chroot.name_release), 293 "dl_stat": repo_dl_stat[chroot.name_release], 294 "rpm_dl_stat": { 295 chroot.arch: chroot_rpms_dl_stat 296 } 297 } 298 else: 299 repos_info[chroot.name_release]["arch_list"].append(chroot.arch) 300 repos_info[chroot.name_release]["rpm_dl_stat"][chroot.arch] = chroot_rpms_dl_stat 301 302 if copr.multilib: 303 for name_release in repos_info: 304 arches = repos_info[name_release]['arch_list'] 305 arch_repos = {} 306 for ch64, ch32 in models.MockChroot.multilib_pairs.items(): 307 if set([ch64, ch32]).issubset(set(arches)): 308 arch_repos[ch64] = ch32 309 310 repos_info[name_release]['arch_repos'] = arch_repos 311 312 313 repos_info_list = sorted(repos_info.values(), key=lambda rec: rec["name_release"]) 314 builds = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr).limit(1).all() 315 316 return flask.render_template( 317 "coprs/detail/overview.html", 318 copr=copr, 319 user=flask.g.user, 320 form=form, 321 repo_dl_stat=repo_dl_stat, 322 repos_info_list=repos_info_list, 323 latest_build=builds[0] if len(builds) == 1 else None, 324 )
325
326 327 @coprs_ns.route("/<username>/<coprname>/permissions/") 328 @coprs_ns.route("/g/<group_name>/<coprname>/permissions/") 329 @req_with_copr 330 -def copr_permissions(copr):
331 permissions = coprs_logic.CoprPermissionsLogic.get_for_copr(copr).all() 332 if flask.g.user: 333 user_perm = flask.g.user.permissions_for_copr(copr) 334 else: 335 user_perm = None 336 337 permissions_applier_form = None 338 permissions_form = None 339 340 # generate a proper form for displaying 341 if flask.g.user: 342 # https://github.com/ajford/flask-wtf/issues/58 343 permissions_applier_form = \ 344 forms.PermissionsApplierFormFactory.create_form_cls( 345 user_perm)(formdata=None) 346 347 if flask.g.user.can_edit(copr): 348 permissions_form = forms.PermissionsFormFactory.create_form_cls( 349 permissions)() 350 351 return flask.render_template( 352 "coprs/detail/settings/permissions.html", 353 copr=copr, 354 permissions_form=permissions_form, 355 permissions_applier_form=permissions_applier_form, 356 permissions=permissions, 357 current_user_permissions=user_perm)
358
359 360 -def render_copr_integrations(copr, pagure_form):
361 if not copr.webhook_secret: 362 copr.new_webhook_secret() 363 db.session.add(copr) 364 db.session.commit() 365 366 bitbucket_url = "https://{}/webhooks/bitbucket/{}/{}/".format( 367 app.config["PUBLIC_COPR_HOSTNAME"], 368 copr.id, 369 copr.webhook_secret) 370 371 github_url = "https://{}/webhooks/github/{}/{}/".format( 372 app.config["PUBLIC_COPR_HOSTNAME"], 373 copr.id, 374 copr.webhook_secret) 375 376 gitlab_url = "https://{}/webhooks/gitlab/{}/{}/".format( 377 app.config["PUBLIC_COPR_HOSTNAME"], 378 copr.id, 379 copr.webhook_secret) 380 381 custom_url = "https://{}/webhooks/custom/{}/{}/".format( 382 app.config["PUBLIC_COPR_HOSTNAME"], 383 copr.id, 384 copr.webhook_secret) + "<PACKAGE_NAME>/" 385 386 return flask.render_template( 387 "coprs/detail/settings/integrations.html", 388 copr=copr, bitbucket_url=bitbucket_url, github_url=github_url, 389 gitlab_url=gitlab_url, custom_url=custom_url, pagure_form=pagure_form)
390
391 392 @coprs_ns.route("/<username>/<coprname>/integrations/") 393 @coprs_ns.route("/g/<group_name>/<coprname>/integrations/") 394 @login_required 395 @req_with_copr 396 -def copr_integrations(copr):
397 if not flask.g.user.can_edit(copr): 398 flask.flash("You don't have access to this page.", "error") 399 return flask.redirect(url_for_copr_details(copr)) 400 401 if copr.scm_api_type == 'pagure': 402 pagure_api_key = copr.scm_api_auth.get('api_key', '') 403 else: 404 pagure_api_key = '' 405 406 pagure_form = forms.PagureIntegrationForm( 407 api_key=pagure_api_key, repo_url=copr.scm_repo_url) 408 return render_copr_integrations(copr, pagure_form)
409
410 411 @coprs_ns.route("/<username>/<coprname>/integrations/update", methods=["POST"]) 412 @coprs_ns.route("/g/<group_name>/<coprname>/integrations/update", methods=["POST"]) 413 @login_required 414 @req_with_copr 415 -def copr_integrations_update(copr):
416 if not flask.g.user.can_edit(copr): 417 flask.flash("Access denied.", "error") 418 return flask.redirect(url_for_copr_details(copr)) 419 420 pagure_form = forms.PagureIntegrationForm() 421 422 if pagure_form.validate_on_submit(): 423 copr.scm_repo_url = pagure_form.repo_url.data 424 copr.scm_api_type = 'pagure' 425 copr.scm_api_auth_json = json.dumps({'api_key': pagure_form.api_key.data}) 426 db.session.add(copr) 427 db.session.commit() 428 flask.flash("Integrations have been updated.", 'success') 429 return flask.redirect(helpers.copr_url("coprs_ns.copr_integrations", copr)) 430 else: 431 return render_copr_integrations(copr, pagure_form)
432
433 434 -def render_copr_edit(copr, form, view):
435 if not form: 436 form = forms.CoprFormFactory.create_form_cls( 437 copr.mock_chroots, copr=copr)(obj=copr) 438 comments = {} 439 for chroot in MockChrootsLogic.get_multiple(active_only=True): 440 comments[chroot.name] = chroot.comment 441 return flask.render_template( 442 "coprs/detail/settings/edit.html", 443 copr=copr, form=form, view=view, comments=comments)
444
445 446 @coprs_ns.route("/<username>/<coprname>/edit/") 447 @coprs_ns.route("/g/<group_name>/<coprname>/edit/") 448 @login_required 449 @req_with_copr 450 -def copr_edit(copr, form=None):
451 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
452
453 454 -def _check_rpmfusion(repos):
455 if "rpmfusion" in repos: 456 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>.') 457 flask.flash(message, "error")
458
459 460 -def process_copr_update(copr, form):
461 copr.name = form.name.data 462 copr.homepage = form.homepage.data 463 copr.contact = form.contact.data 464 copr.repos = form.repos.data.replace("\n", " ") 465 copr.description = form.description.data 466 copr.instructions = form.instructions.data 467 copr.disable_createrepo = form.disable_createrepo.data 468 copr.build_enable_net = form.build_enable_net.data 469 copr.unlisted_on_hp = form.unlisted_on_hp.data 470 copr.use_bootstrap_container = form.use_bootstrap_container.data 471 copr.follow_fedora_branching = form.follow_fedora_branching.data 472 copr.delete_after_days = form.delete_after_days.data 473 copr.multilib = form.multilib.data 474 if flask.g.user.admin: 475 copr.auto_prune = form.auto_prune.data 476 else: 477 copr.auto_prune = True 478 coprs_logic.CoprChrootsLogic.update_from_names( 479 flask.g.user, copr, form.selected_chroots) 480 try: 481 # form validation checks for duplicates 482 coprs_logic.CoprsLogic.update(flask.g.user, copr) 483 except (exceptions.ActionInProgressException, 484 exceptions.InsufficientRightsException) as e: 485 486 flask.flash(str(e), "error") 487 db.session.rollback() 488 else: 489 flask.flash("Project has been updated successfully.", "success") 490 db.session.commit() 491 _check_rpmfusion(copr.repos)
492
493 494 @coprs_ns.route("/<username>/<coprname>/update/", methods=["POST"]) 495 @coprs_ns.route("/g/<group_name>/<coprname>/update/", methods=["POST"]) 496 @login_required 497 @req_with_copr 498 -def copr_update(copr):
499 form = forms.CoprFormFactory.create_form_cls(user=copr.user, group=copr.group)() 500 501 if form.validate_on_submit(): 502 process_copr_update(copr, form) 503 return flask.redirect(url_for_copr_details(copr)) 504 else: 505 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
506 507 508 @coprs_ns.route("/<username>/<coprname>/permissions_applier_change/", 509 methods=["POST"])
510 @coprs_ns.route("/g/<group_name>/<coprname>/permissions_applier_change/", methods=["POST"]) 511 @login_required 512 @req_with_copr 513 -def copr_permissions_applier_change(copr):
514 permission = coprs_logic.CoprPermissionsLogic.get(copr, flask.g.user).first() 515 applier_permissions_form = \ 516 forms.PermissionsApplierFormFactory.create_form_cls(permission)() 517 518 if copr.user == flask.g.user: 519 flask.flash("Owner cannot request permissions for his own project.", "error") 520 elif applier_permissions_form.validate_on_submit(): 521 # we rely on these to be 0 or 1 from form. TODO: abstract from that 522 if permission is not None: 523 old_builder = permission.copr_builder 524 old_admin = permission.copr_admin 525 else: 526 old_builder = 0 527 old_admin = 0 528 new_builder = applier_permissions_form.copr_builder.data 529 new_admin = applier_permissions_form.copr_admin.data 530 coprs_logic.CoprPermissionsLogic.update_permissions_by_applier( 531 flask.g.user, copr, permission, new_builder, new_admin) 532 db.session.commit() 533 flask.flash( 534 "Successfully updated permissions for project '{0}'." 535 .format(copr.name)) 536 537 # sending emails 538 if flask.current_app.config.get("SEND_EMAILS", False): 539 for mail in copr.admin_mails: 540 permission_dict = {"old_builder": old_builder, "old_admin": old_admin, 541 "new_builder": new_builder, "new_admin": new_admin} 542 msg = PermissionRequestMessage(copr, flask.g.user, permission_dict) 543 send_mail([mail], msg) 544 545 return flask.redirect(helpers.copr_url("coprs_ns.copr_detail", copr))
546
547 548 @coprs_ns.route("/<username>/<coprname>/update_permissions/", methods=["POST"]) 549 @coprs_ns.route("/g/<group_name>/<coprname>/update_permissions/", methods=["POST"]) 550 @login_required 551 @req_with_copr 552 -def copr_update_permissions(copr):
553 permissions = copr.copr_permissions 554 permissions_form = forms.PermissionsFormFactory.create_form_cls( 555 permissions)() 556 557 if permissions_form.validate_on_submit(): 558 # we don't change owner (yet) 559 try: 560 # if admin is changing his permissions, his must be changed last 561 # so that we don't get InsufficientRightsException 562 permissions.sort( 563 key=lambda x: -1 if x.user_id == flask.g.user.id else 1) 564 for perm in permissions: 565 old_builder = perm.copr_builder 566 old_admin = perm.copr_admin 567 new_builder = permissions_form[ 568 "copr_builder_{0}".format(perm.user_id)].data 569 new_admin = permissions_form[ 570 "copr_admin_{0}".format(perm.user_id)].data 571 coprs_logic.CoprPermissionsLogic.update_permissions( 572 flask.g.user, copr, perm, new_builder, new_admin) 573 if flask.current_app.config.get("SEND_EMAILS", False) and \ 574 (old_builder is not new_builder or old_admin is not new_admin): 575 permission_dict = {"old_builder": old_builder, "old_admin": old_admin, 576 "new_builder": new_builder, "new_admin": new_admin} 577 msg = PermissionChangeMessage(copr, permission_dict) 578 send_mail(perm.user.mail, msg) 579 # for now, we don't check for actions here, as permissions operation 580 # don't collide with any actions 581 except exceptions.InsufficientRightsException as e: 582 db.session.rollback() 583 flask.flash(str(e), "error") 584 else: 585 db.session.commit() 586 flask.flash("Project permissions were updated successfully.", "success") 587 588 return flask.redirect(url_for_copr_details(copr))
589
590 591 @coprs_ns.route("/<username>/<coprname>/repositories/") 592 @coprs_ns.route("/g/<group_name>/<coprname>/repositories/") 593 @login_required 594 @req_with_copr 595 -def copr_repositories(copr):
596 if not flask.g.user.can_edit(copr): 597 flask.flash("You don't have access to this page.", "error") 598 return flask.redirect(url_for_copr_details(copr)) 599 600 return render_copr_repositories(copr)
601
602 603 -def render_copr_repositories(copr):
604 outdated_chroots = copr.outdated_chroots 605 return flask.render_template("coprs/detail/settings/repositories.html", copr=copr, 606 outdated_chroots=outdated_chroots)
607
608 609 @coprs_ns.route("/<username>/<coprname>/repositories/", methods=["POST"]) 610 @coprs_ns.route("/g/<group_name>/<coprname>/repositories/", methods=["POST"]) 611 @login_required 612 @req_with_copr 613 -def copr_repositories_post(copr):
614 if not flask.g.user.can_edit(copr): 615 flask.flash("You don't have access to this page.", "error") 616 return flask.redirect(url_for_copr_details(copr)) 617 618 form = forms.CoprChrootExtend() 619 if form.extend.data: 620 delete_after_days = app.config["DELETE_EOL_CHROOTS_AFTER"] + 1 621 chroot_name = form.extend.data 622 flask.flash("Repository for {} will be preserved for another {} days from now" 623 .format(chroot_name, app.config["DELETE_EOL_CHROOTS_AFTER"])) 624 elif form.expire.data: 625 delete_after_days = 0 626 chroot_name = form.expire.data 627 flask.flash("Repository for {} is scheduled to be removed." 628 "If you changed your mind, click 'Extend` to revert your decision." 629 .format(chroot_name)) 630 else: 631 raise ValidationError("Copr chroot needs to be either extended or expired") 632 633 copr_chroot = coprs_logic.CoprChrootsLogic.get_by_name(copr, chroot_name, active_only=False).one() 634 delete_after_timestamp = datetime.datetime.now() + datetime.timedelta(days=delete_after_days) 635 coprs_logic.CoprChrootsLogic.update_chroot(flask.g.user, copr_chroot, 636 delete_after=delete_after_timestamp) 637 db.session.commit() 638 return render_copr_repositories(copr)
639
640 641 @coprs_ns.route("/id/<copr_id>/createrepo/", methods=["POST"]) 642 @login_required 643 -def copr_createrepo(copr_id):
644 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 645 if not flask.g.user.can_edit(copr): 646 flask.flash( 647 "You are not allowed to recreate repository metadata of copr with id {}.".format(copr_id), "error") 648 return flask.redirect(url_for_copr_details(copr)) 649 650 actions_logic.ActionsLogic.send_createrepo(copr) 651 db.session.commit() 652 653 flask.flash("Repository metadata in all directories will be regenerated...", "success") 654 return flask.redirect(url_for_copr_details(copr))
655
656 657 -def process_delete(copr, url_on_error, url_on_success):
658 form = forms.CoprDeleteForm() 659 if form.validate_on_submit(): 660 661 try: 662 ComplexLogic.delete_copr(copr) 663 except (exceptions.ActionInProgressException, 664 exceptions.InsufficientRightsException) as e: 665 666 db.session.rollback() 667 flask.flash(str(e), "error") 668 return flask.redirect(url_on_error) 669 else: 670 db.session.commit() 671 flask.flash("Project has been deleted successfully.") 672 return flask.redirect(url_on_success) 673 else: 674 return render_template("coprs/detail/settings/delete.html", form=form, copr=copr)
675
676 677 @coprs_ns.route("/<username>/<coprname>/delete/", methods=["GET", "POST"]) 678 @coprs_ns.route("/g/<group_name>/<coprname>/delete/", methods=["GET", "POST"]) 679 @login_required 680 @req_with_copr 681 -def copr_delete(copr):
682 if copr.group: 683 url_on_success = url_for("groups_ns.list_projects_by_group", group_name=copr.group.name) 684 else: 685 url_on_success = url_for("coprs_ns.coprs_by_user", username=copr.user.username) 686 url_on_error = helpers.copr_url("coprs_ns.copr_detail", copr) 687 return process_delete(copr, url_on_error, url_on_success)
688 696 714
715 716 @coprs_ns.route("/<username>/<copr_dirname>/repo/<name_release>/", defaults={"repofile": None}) 717 @coprs_ns.route("/<username>/<copr_dirname>/repo/<name_release>/<repofile>") 718 @coprs_ns.route("/g/<group_name>/<copr_dirname>/repo/<name_release>/", defaults={"repofile": None}) 719 @coprs_ns.route("/g/<group_name>/<copr_dirname>/repo/<name_release>/<repofile>") 720 @req_with_copr_dir 721 -def generate_repo_file(copr_dir, name_release, repofile):
722 """ Generate repo file for a given repo name. 723 Reponame = username-coprname """ 724 725 arch = flask.request.args.get('arch') 726 return render_generate_repo_file(copr_dir, name_release, arch)
727
728 729 -def render_repo_template(copr_dir, mock_chroot, arch=None):
730 repo_id = "copr:{0}:{1}:{2}{3}".format( 731 app.config["PUBLIC_COPR_HOSTNAME"].split(":")[0], 732 copr_dir.copr.owner_name.replace("@", "group_"), 733 copr_dir.name, 734 ":ml" if arch else "" 735 ) 736 url = os.path.join(copr_dir.repo_url, '') # adds trailing slash 737 repo_url = generate_repo_url(mock_chroot, url, arch) 738 pubkey_url = urljoin(url, "pubkey.gpg") 739 return flask.render_template("coprs/copr_dir.repo", copr_dir=copr_dir, 740 url=repo_url, pubkey_url=pubkey_url, 741 repo_id=repo_id) + "\n"
742
743 744 -def render_generate_repo_file(copr_dir, name_release, arch=None):
745 copr = copr_dir.copr 746 747 # redirect the aliased chroot only if it is not enabled yet 748 if not any([ch.name.startswith(name_release) for ch in copr.active_chroots]): 749 name_release = app.config["CHROOT_NAME_RELEASE_ALIAS"].get(name_release, name_release) 750 751 # if the arch isn't specified, find the fist one starting with name_release 752 searched_chroot = name_release if not arch else name_release + "-" + arch 753 754 mock_chroot = None 755 for mc in copr.active_chroots: 756 if not mc.name.startswith(searched_chroot): 757 continue 758 mock_chroot = mc 759 760 if not mock_chroot: 761 raise ObjectNotFound("Chroot {} does not exist in {}".format( 762 searched_chroot, copr.full_name)) 763 764 # normal, arch agnostic repofile 765 response_content = render_repo_template(copr_dir, mock_chroot) 766 767 # append multilib counterpart repo only upon explicit request (ach != None), 768 # and only if the chroot actually is multilib capable 769 copr = copr_dir.copr 770 if arch and copr.multilib and mock_chroot in copr.active_multilib_chroots: 771 response_content += "\n" + render_repo_template(copr_dir, mock_chroot, 'i386') 772 773 response = flask.make_response(response_content) 774 775 response.mimetype = "text/plain" 776 response.headers["Content-Disposition"] = \ 777 "filename={0}.repo".format(copr_dir.repo_name) 778 779 name = REPO_DL_STAT_FMT.format(**{ 780 'copr_user': copr_dir.copr.user.name, 781 'copr_project_name': copr_dir.copr.name, 782 'copr_name_release': name_release, 783 }) 784 CounterStatLogic.incr(name=name, counter_type=CounterStatType.REPO_DL) 785 db.session.commit() 786 787 return response
788
789 790 ######################################################### 791 ### Module repo files ### 792 ######################################################### 793 794 @coprs_ns.route("/<username>/<coprname>/module_repo/<name_release>/<module_nsv>.repo") 795 @coprs_ns.route("/g/<group_name>/<coprname>/module_repo/<name_release>/<module_nsv>.repo") 796 @req_with_copr 797 -def generate_module_repo_file(copr, name_release, module_nsv):
798 """ Generate module repo file for a given project. """ 799 return render_generate_module_repo_file(copr, name_release, module_nsv)
800
801 -def render_generate_module_repo_file(copr, name_release, module_nsv):
802 module = ModulesLogic.get_by_nsv_str(copr, module_nsv).one() 803 mock_chroot = coprs_logic.MockChrootsLogic.get_from_name(name_release, noarch=True).first() 804 url = os.path.join(copr.main_dir.repo_url, '') # adds trailing slash 805 repo_url = generate_repo_url(mock_chroot, copr.modules_url) 806 baseurl = "{}+{}/latest/$basearch".format(repo_url.rstrip("/"), module_nsv) 807 pubkey_url = urljoin(url, "pubkey.gpg") 808 response = flask.make_response( 809 flask.render_template("coprs/copr-modules.cfg", copr=copr, module=module, 810 baseurl=baseurl, pubkey_url=pubkey_url)) 811 response.mimetype = "text/plain" 812 response.headers["Content-Disposition"] = \ 813 "filename={0}.cfg".format(copr.repo_name) 814 return response
815
816 ######################################################### 817 818 @coprs_ns.route("/<username>/<coprname>/rpm/<name_release>/<rpmfile>") 819 -def copr_repo_rpm_file(username, coprname, name_release, rpmfile):
820 try: 821 packages_dir = os.path.join(app.config["DATA_DIR"], "repo-rpm-packages") 822 with open(os.path.join(packages_dir, rpmfile), "rb") as rpm: 823 response = flask.make_response(rpm.read()) 824 response.mimetype = "application/x-rpm" 825 response.headers["Content-Disposition"] = \ 826 "filename={0}".format(rpmfile) 827 return response 828 except IOError: 829 return flask.render_template("404.html")
830
831 832 -def render_monitor(copr, detailed=False):
833 monitor = builds_logic.BuildsMonitorLogic.get_monitor_data(copr) 834 oses = [chroot.os for chroot in copr.active_chroots_sorted] 835 oses_grouped = [(len(list(group)), key) for key, group in groupby(oses)] 836 archs = [chroot.arch for chroot in copr.active_chroots_sorted] 837 if detailed: 838 template = "coprs/detail/monitor/detailed.html" 839 else: 840 template = "coprs/detail/monitor/simple.html" 841 return flask.Response(stream_with_context(helpers.stream_template(template, 842 copr=copr, 843 monitor=monitor, 844 oses=oses_grouped, 845 archs=archs, 846 status_enum_func=StatusEnum)))
847
848 849 @coprs_ns.route("/<username>/<coprname>/monitor/") 850 @coprs_ns.route("/<username>/<coprname>/monitor/<detailed>") 851 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/") 852 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/<detailed>") 853 @req_with_copr 854 -def copr_build_monitor(copr, detailed=False):
855 return render_monitor(copr, detailed == "detailed")
856
857 858 @coprs_ns.route("/<username>/<coprname>/fork/") 859 @coprs_ns.route("/g/<group_name>/<coprname>/fork/") 860 @login_required 861 @req_with_copr 862 -def copr_fork(copr):
863 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)() 864 return render_copr_fork(copr, form)
865
866 867 -def render_copr_fork(copr, form, confirm=False):
868 return flask.render_template("coprs/fork.html", copr=copr, form=form, confirm=confirm)
869
870 871 @coprs_ns.route("/<username>/<coprname>/fork/", methods=["POST"]) 872 @coprs_ns.route("/g/<group_name>/<coprname>/fork/", methods=["POST"]) 873 @login_required 874 @req_with_copr 875 -def copr_fork_post(copr):
876 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)() 877 if form.validate_on_submit(): 878 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0] 879 if flask.g.user.name != form.owner.data and not dstgroup: 880 return generic_error("There is no such group: {}".format(form.owner.data)) 881 882 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup) 883 if created: 884 msg = ("Forking project {} for you into {}. Please be aware that it may take a few minutes " 885 "to duplicate backend data.".format(copr.full_name, fcopr.full_name)) 886 elif not created and form.confirm.data == True: 887 msg = ("Updating packages in {} from {}. Please be aware that it may take a few minutes " 888 "to duplicate backend data.".format(copr.full_name, fcopr.full_name)) 889 else: 890 return render_copr_fork(copr, form, confirm=True) 891 892 db.session.commit() 893 flask.flash(msg) 894 895 return flask.redirect(url_for_copr_details(fcopr)) 896 return render_copr_fork(copr, form)
897
898 899 @coprs_ns.route("/<username>/<coprname>/forks/") 900 @coprs_ns.route("/g/<group_name>/<coprname>/forks/") 901 @req_with_copr 902 -def copr_forks(copr):
903 return flask.render_template("coprs/detail/forks.html", copr=copr)
904
905 906 @coprs_ns.route("/update_search_index/", methods=["POST"]) 907 -def copr_update_search_index():
908 subprocess.call(['/usr/share/copr/coprs_frontend/manage.py', 'update_indexes_quick', '1']) 909 return "OK"
910
911 912 @coprs_ns.route("/<username>/<coprname>/modules/") 913 @coprs_ns.route("/g/<group_name>/<coprname>/modules/") 914 @req_with_copr 915 -def copr_modules(copr):
916 return render_copr_modules(copr)
917
918 919 -def render_copr_modules(copr):
920 modules = ModulesLogic.get_multiple_by_copr(copr=copr).all() 921 return flask.render_template("coprs/detail/modules.html", copr=copr, modules=modules)
922
923 924 @coprs_ns.route("/<username>/<coprname>/create_module/") 925 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/") 926 @login_required 927 @req_with_copr 928 -def copr_create_module(copr):
929 form = forms.CreateModuleForm() 930 return render_create_module(copr, form)
931
932 933 -def render_create_module(copr, form, profiles=2):
934 built_packages = [] 935 for build in filter(None, [p.last_build(successful=True) for p in copr.packages]): 936 for package in build.built_packages.split("\n"): 937 built_packages.append((package.split()[0], build)) 938 939 return flask.render_template("coprs/create_module.html", copr=copr, form=form, built_packages=built_packages, profiles=profiles)
940
941 942 @coprs_ns.route("/<username>/<coprname>/create_module/", methods=["POST"]) 943 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/", methods=["POST"]) 944 @login_required 945 @req_with_copr 946 -def copr_create_module_post(copr):
947 form = forms.CreateModuleForm(copr=copr, meta={'csrf': False}) 948 args = [copr, form] 949 if "add_profile" in flask.request.values: 950 return add_profile(*args) 951 if "build_module" in flask.request.values: 952 return build_module(*args)
953 # @TODO Error
954 955 956 -def add_profile(copr, form):
957 n = len(form.profile_names) + 1 958 form.profile_names.append_entry() 959 for i in range(2, n): 960 form.profile_pkgs.append_entry() 961 return render_create_module(copr, form, profiles=n)
962
963 964 -def build_module(copr, form):
965 if not form.validate_on_submit(): 966 # WORKAROUND append those which are not in min_entries 967 for i in range(2, len(form.profile_names)): 968 form.profile_pkgs.append_entry() 969 return render_create_module(copr, form, profiles=len(form.profile_names)) 970 971 summary = "Module from Copr repository: {}".format(copr.full_name) 972 generator = ModulemdGenerator(str(copr.name), summary=summary, config=app.config) 973 generator.add_filter(form.filter.data) 974 generator.add_api(form.api.data) 975 generator.add_profiles(enumerate(zip(form.profile_names.data, form.profile_pkgs.data))) 976 generator.add_components(form.packages.data, form.filter.data, form.builds.data) 977 yaml = generator.generate() 978 979 facade = None 980 try: 981 facade = ModuleBuildFacade(flask.g.user, copr, yaml) 982 module = facade.submit_build() 983 db.session.commit() 984 985 flask.flash("Modulemd yaml file successfully generated and submitted to be build as {}" 986 .format(module.nsv), "success") 987 return flask.redirect(url_for_copr_details(copr)) 988 989 except ValidationError as ex: 990 flask.flash(ex.message, "error") 991 return render_create_module(copr, form, len(form.profile_names)) 992 993 except sqlalchemy.exc.IntegrityError: 994 flask.flash("Module {}-{}-{} already exists".format( 995 facade.modulemd.name, facade.modulemd.stream, facade.modulemd.version), "error") 996 db.session.rollback() 997 return render_create_module(copr, form, len(form.profile_names))
998
999 1000 @coprs_ns.route("/<username>/<coprname>/module/<id>") 1001 @coprs_ns.route("/g/<group_name>/<coprname>/module/<id>") 1002 @req_with_copr 1003 -def copr_module(copr, id):
1004 module = ModulesLogic.get(id).first() 1005 formatter = HtmlFormatter(style="autumn", linenos=False, noclasses=True) 1006 pretty_yaml = highlight(module.yaml, get_lexer_by_name("YAML"), formatter) 1007 1008 # Get the list of chroots with unique name_release attribute 1009 # Once we use jinja in 2.10 version, we can simply use 1010 # {{ copr.active_chroots |unique(attribute='name_release') }} 1011 unique_chroots = [] 1012 unique_name_releases = set() 1013 for chroot in copr.active_chroots_sorted: 1014 if chroot.name_release in unique_name_releases: 1015 continue 1016 unique_chroots.append(chroot) 1017 unique_name_releases.add(chroot.name_release) 1018 1019 return flask.render_template("coprs/detail/module.html", copr=copr, module=module, 1020 yaml=pretty_yaml, unique_chroots=unique_chroots)
1021
1022 1023 @coprs_ns.route("/<username>/<coprname>/module/<id>/raw") 1024 @coprs_ns.route("/g/<group_name>/<coprname>/module/<id>/raw") 1025 @req_with_copr 1026 -def copr_module_raw(copr, id):
1027 module = ModulesLogic.get(id).first() 1028 response = flask.make_response(module.yaml) 1029 response.mimetype = "text/plain" 1030 response.headers["Content-Disposition"] = \ 1031 "filename={}.yaml".format("-".join([str(module.id), module.name, module.stream, str(module.version)])) 1032 return response
1033