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