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