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 
 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 paginator=paginator, 81 tasks_info=ComplexLogic.get_queue_sizes(), 82 users_builds=users_builds, 83 graph=data)
84
85 86 @coprs_ns.route("/<username>/", defaults={"page": 1}) 87 @coprs_ns.route("/<username>/<int:page>/") 88 -def coprs_by_user(username=None, page=1):
89 user = users_logic.UsersLogic.get(username).first() 90 if not user: 91 return page_not_found( 92 "User {0} does not exist.".format(username)) 93 94 query = CoprsLogic.get_multiple_owned_by_username(username) 95 query = CoprsLogic.filter_without_group_projects(query) 96 query = CoprsLogic.set_query_order(query, desc=True) 97 98 paginator = helpers.Paginator(query, query.count(), page) 99 100 coprs = paginator.sliced_query 101 102 # flask.g.user is none when no user is logged - showing builds from everyone 103 users_builds = builds_logic.BuildsLogic.get_recent_tasks(flask.g.user, 4) 104 105 data = builds_logic.BuildsLogic.get_small_graph_data('30min') 106 107 return flask.render_template("coprs/show/user.html", 108 user=user, 109 coprs=coprs, 110 paginator=paginator, 111 tasks_info=ComplexLogic.get_queue_sizes(), 112 users_builds=users_builds, 113 graph=data)
114
115 116 @coprs_ns.route("/fulltext/", defaults={"page": 1}) 117 @coprs_ns.route("/fulltext/<int:page>/") 118 -def coprs_fulltext_search(page=1):
119 fulltext = flask.request.args.get("fulltext", "") 120 try: 121 query = coprs_logic.CoprsLogic.get_multiple_fulltext(fulltext) 122 except ValueError as e: 123 flask.flash(str(e), "error") 124 return flask.redirect(flask.request.referrer or 125 flask.url_for("coprs_ns.coprs_show")) 126 127 paginator = helpers.Paginator(query, query.count(), page, 128 additional_params={"fulltext": fulltext}) 129 130 data = builds_logic.BuildsLogic.get_small_graph_data('30min') 131 132 coprs = paginator.sliced_query 133 return render_template("coprs/show/fulltext.html", 134 coprs=coprs, 135 paginator=paginator, 136 fulltext=fulltext, 137 tasks_info=ComplexLogic.get_queue_sizes(), 138 graph=data)
139
140 141 @coprs_ns.route("/<username>/add/") 142 @coprs_ns.route("/g/<group_name>/add/") 143 @login_required 144 -def copr_add(username=None, group_name=None):
145 form = forms.CoprFormFactory.create_form_cls()() 146 if group_name: 147 group = ComplexLogic.get_group_by_name_safe(group_name) 148 return flask.render_template("coprs/group_add.html", form=form, group=group) 149 return flask.render_template("coprs/add.html", form=form)
150
151 152 @coprs_ns.route("/<username>/new/", methods=["POST"]) 153 @coprs_ns.route("/g/<group_name>/new/", methods=["POST"]) 154 @login_required 155 -def copr_new(username=None, group_name=None):
156 return process_copr_new(group_name)
157
158 159 -def process_copr_new(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 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e: 193 flask.flash(str(e), "error") 194 return flask.render_template(redirect, form=form) 195 196 db.session.commit() 197 after_the_project_creation(copr, form) 198 199 return flask.redirect(url_for_copr_details(copr)) 200 else: 201 return flask.render_template(redirect, form=form)
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 repos_info_list = sorted(repos_info.values(), key=lambda rec: rec["name_release"]) 302 builds = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr).limit(1).all() 303 304 return flask.render_template( 305 "coprs/detail/overview.html", 306 copr=copr, 307 user=flask.g.user, 308 form=form, 309 repo_dl_stat=repo_dl_stat, 310 repos_info_list=repos_info_list, 311 latest_build=builds[0] if len(builds) == 1 else None, 312 )
313
314 315 @coprs_ns.route("/<username>/<coprname>/permissions/") 316 @req_with_copr 317 -def copr_permissions(copr):
318 permissions = coprs_logic.CoprPermissionsLogic.get_for_copr(copr).all() 319 if flask.g.user: 320 user_perm = flask.g.user.permissions_for_copr(copr) 321 else: 322 user_perm = None 323 324 permissions_applier_form = None 325 permissions_form = None 326 327 # generate a proper form for displaying 328 if flask.g.user: 329 # https://github.com/ajford/flask-wtf/issues/58 330 permissions_applier_form = \ 331 forms.PermissionsApplierFormFactory.create_form_cls( 332 user_perm)(formdata=None) 333 334 if flask.g.user.can_edit(copr): 335 permissions_form = forms.PermissionsFormFactory.create_form_cls( 336 permissions)() 337 338 return flask.render_template( 339 "coprs/detail/settings/permissions.html", 340 copr=copr, 341 permissions_form=permissions_form, 342 permissions_applier_form=permissions_applier_form, 343 permissions=permissions, 344 current_user_permissions=user_perm)
345
346 347 -def render_copr_integrations(copr, pagure_form):
348 if not copr.webhook_secret: 349 copr.new_webhook_secret() 350 db.session.add(copr) 351 db.session.commit() 352 353 bitbucket_url = "https://{}/webhooks/bitbucket/{}/{}/".format( 354 app.config["PUBLIC_COPR_HOSTNAME"], 355 copr.id, 356 copr.webhook_secret) 357 358 github_url = "https://{}/webhooks/github/{}/{}/".format( 359 app.config["PUBLIC_COPR_HOSTNAME"], 360 copr.id, 361 copr.webhook_secret) 362 363 gitlab_url = "https://{}/webhooks/gitlab/{}/{}/".format( 364 app.config["PUBLIC_COPR_HOSTNAME"], 365 copr.id, 366 copr.webhook_secret) 367 368 custom_url = "https://{}/webhooks/custom/{}/{}/".format( 369 app.config["PUBLIC_COPR_HOSTNAME"], 370 copr.id, 371 copr.webhook_secret) + "<PACKAGE_NAME>/" 372 373 return flask.render_template( 374 "coprs/detail/settings/integrations.html", 375 copr=copr, bitbucket_url=bitbucket_url, github_url=github_url, 376 gitlab_url=gitlab_url, custom_url=custom_url, pagure_form=pagure_form)
377
378 379 @coprs_ns.route("/<username>/<coprname>/integrations/") 380 @coprs_ns.route("/g/<group_name>/<coprname>/integrations/") 381 @login_required 382 @req_with_copr 383 -def copr_integrations(copr):
384 if not flask.g.user.can_edit(copr): 385 flask.flash("You don't have access to this page.", "error") 386 return flask.redirect(url_for_copr_details(copr)) 387 388 if copr.scm_api_type == 'pagure': 389 pagure_api_key = copr.scm_api_auth.get('api_key', '') 390 else: 391 pagure_api_key = '' 392 393 pagure_form = forms.PagureIntegrationForm( 394 api_key=pagure_api_key, repo_url=copr.scm_repo_url) 395 return render_copr_integrations(copr, pagure_form)
396
397 398 @coprs_ns.route("/<username>/<coprname>/integrations/update", methods=["POST"]) 399 @coprs_ns.route("/g/<group_name>/<coprname>/integrations/update", methods=["POST"]) 400 @login_required 401 @req_with_copr 402 -def copr_integrations_update(copr):
403 if not flask.g.user.can_edit(copr): 404 flask.flash("Access denied.", "error") 405 return flask.redirect(url_for_copr_details(copr)) 406 407 pagure_form = forms.PagureIntegrationForm() 408 409 if pagure_form.validate_on_submit(): 410 copr.scm_repo_url = pagure_form.repo_url.data 411 copr.scm_api_type = 'pagure' 412 copr.scm_api_auth_json = json.dumps({'api_key': pagure_form.api_key.data}) 413 db.session.add(copr) 414 db.session.commit() 415 flask.flash("Integrations have been updated.", 'success') 416 return flask.redirect(helpers.copr_url("coprs_ns.copr_integrations", copr)) 417 else: 418 return render_copr_integrations(copr, pagure_form)
419
420 421 -def render_copr_edit(copr, form, view):
422 if not form: 423 form = forms.CoprFormFactory.create_form_cls( 424 copr.mock_chroots, copr=copr)(obj=copr) 425 return flask.render_template( 426 "coprs/detail/settings/edit.html", 427 copr=copr, form=form, view=view)
428
429 430 @coprs_ns.route("/<username>/<coprname>/edit/") 431 @coprs_ns.route("/g/<group_name>/<coprname>/edit/") 432 @login_required 433 @req_with_copr 434 -def copr_edit(copr, form=None):
435 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
436
437 438 -def _check_rpmfusion(repos):
439 if "rpmfusion" in repos: 440 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>.') 441 flask.flash(message, "error")
442
443 444 -def process_copr_update(copr, form):
445 copr.name = form.name.data 446 copr.homepage = form.homepage.data 447 copr.contact = form.contact.data 448 copr.repos = form.repos.data.replace("\n", " ") 449 copr.description = form.description.data 450 copr.instructions = form.instructions.data 451 copr.disable_createrepo = form.disable_createrepo.data 452 copr.build_enable_net = form.build_enable_net.data 453 copr.unlisted_on_hp = form.unlisted_on_hp.data 454 copr.use_bootstrap_container = form.use_bootstrap_container.data 455 copr.follow_fedora_branching = form.follow_fedora_branching.data 456 copr.delete_after_days = form.delete_after_days.data 457 if flask.g.user.admin: 458 copr.auto_prune = form.auto_prune.data 459 else: 460 copr.auto_prune = True 461 coprs_logic.CoprChrootsLogic.update_from_names( 462 flask.g.user, copr, form.selected_chroots) 463 try: 464 # form validation checks for duplicates 465 coprs_logic.CoprsLogic.update(flask.g.user, copr) 466 except (exceptions.ActionInProgressException, 467 exceptions.InsufficientRightsException) as e: 468 469 flask.flash(str(e), "error") 470 db.session.rollback() 471 else: 472 flask.flash("Project has been updated successfully.", "success") 473 db.session.commit() 474 _check_rpmfusion(copr.repos)
475
476 477 @coprs_ns.route("/<username>/<coprname>/update/", methods=["POST"]) 478 @coprs_ns.route("/g/<group_name>/<coprname>/update/", methods=["POST"]) 479 @login_required 480 @req_with_copr 481 -def copr_update(copr):
482 form = forms.CoprFormFactory.create_form_cls(user=copr.user, group=copr.group)() 483 484 if form.validate_on_submit(): 485 process_copr_update(copr, form) 486 return flask.redirect(url_for_copr_details(copr)) 487 else: 488 return render_copr_edit(copr, form, 'coprs_ns.copr_update')
489 490 491 @coprs_ns.route("/<username>/<coprname>/permissions_applier_change/", 492 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(flask.url_for("coprs_ns.copr_detail", 528 username=copr.user.name, 529 coprname=copr.name))
530
531 532 @coprs_ns.route("/<username>/<coprname>/update_permissions/", methods=["POST"]) 533 @login_required 534 @req_with_copr 535 -def copr_update_permissions(copr):
536 permissions = copr.copr_permissions 537 permissions_form = forms.PermissionsFormFactory.create_form_cls( 538 permissions)() 539 540 if permissions_form.validate_on_submit(): 541 # we don't change owner (yet) 542 try: 543 # if admin is changing his permissions, his must be changed last 544 # so that we don't get InsufficientRightsException 545 permissions.sort( 546 key=lambda x: -1 if x.user_id == flask.g.user.id else 1) 547 for perm in permissions: 548 old_builder = perm.copr_builder 549 old_admin = perm.copr_admin 550 new_builder = permissions_form[ 551 "copr_builder_{0}".format(perm.user_id)].data 552 new_admin = permissions_form[ 553 "copr_admin_{0}".format(perm.user_id)].data 554 coprs_logic.CoprPermissionsLogic.update_permissions( 555 flask.g.user, copr, perm, new_builder, new_admin) 556 if flask.current_app.config.get("SEND_EMAILS", False) and \ 557 (old_builder is not new_builder or old_admin is not new_admin): 558 permission_dict = {"old_builder": old_builder, "old_admin": old_admin, 559 "new_builder": new_builder, "new_admin": new_admin} 560 msg = PermissionChangeMessage(copr, permission_dict) 561 send_mail(perm.user.mail, msg) 562 # for now, we don't check for actions here, as permissions operation 563 # don't collide with any actions 564 except exceptions.InsufficientRightsException as e: 565 db.session.rollback() 566 flask.flash(str(e), "error") 567 else: 568 db.session.commit() 569 flask.flash("Project permissions were updated successfully.", "success") 570 571 return flask.redirect(url_for_copr_details(copr))
572
573 574 @coprs_ns.route("/<username>/<coprname>/repositories/") 575 @coprs_ns.route("/g/<group_name>/<coprname>/repositories/") 576 @login_required 577 @req_with_copr 578 -def copr_repositories(copr):
579 if not flask.g.user.can_edit(copr): 580 flask.flash("You don't have access to this page.", "error") 581 return flask.redirect(url_for_copr_details(copr)) 582 583 return render_copr_repositories(copr)
584
585 586 -def render_copr_repositories(copr):
587 outdated_chroots = copr.outdated_chroots 588 return flask.render_template("coprs/detail/settings/repositories.html", copr=copr, 589 outdated_chroots=outdated_chroots)
590
591 592 @coprs_ns.route("/<username>/<coprname>/repositories/", methods=["POST"]) 593 @coprs_ns.route("/g/<group_name>/<coprname>/repositories/", methods=["POST"]) 594 @login_required 595 @req_with_copr 596 -def copr_repositories_post(copr):
597 if not flask.g.user.can_edit(copr): 598 flask.flash("You don't have access to this page.", "error") 599 return flask.redirect(url_for_copr_details(copr)) 600 601 form = forms.CoprChrootExtend() 602 if form.extend.data: 603 delete_after_days = app.config["DELETE_EOL_CHROOTS_AFTER"] + 1 604 chroot_name = form.extend.data 605 flask.flash("Repository for {} will be preserved for another {} days from now" 606 .format(chroot_name, app.config["DELETE_EOL_CHROOTS_AFTER"])) 607 elif form.expire.data: 608 delete_after_days = 0 609 chroot_name = form.expire.data 610 flask.flash("Repository for {} is scheduled to be removed." 611 "If you changed your mind, click 'Extend` to revert your decision." 612 .format(chroot_name)) 613 else: 614 raise ValidationError("Copr chroot needs to be either extended or expired") 615 616 copr_chroot = coprs_logic.CoprChrootsLogic.get_by_name(copr, chroot_name, active_only=False).one() 617 delete_after_timestamp = datetime.datetime.now() + datetime.timedelta(days=delete_after_days) 618 coprs_logic.CoprChrootsLogic.update_chroot(flask.g.user, copr_chroot, 619 delete_after=delete_after_timestamp) 620 db.session.commit() 621 return render_copr_repositories(copr)
622
623 624 @coprs_ns.route("/id/<copr_id>/createrepo/", methods=["POST"]) 625 @login_required 626 -def copr_createrepo(copr_id):
627 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 628 if not flask.g.user.can_edit(copr): 629 flask.flash( 630 "You are not allowed to recreate repository metadata of copr with id {}.".format(copr_id), "error") 631 return flask.redirect(url_for_copr_details(copr)) 632 633 actions_logic.ActionsLogic.send_createrepo(copr) 634 db.session.commit() 635 636 flask.flash("Repository metadata in all directories will be regenerated...", "success") 637 return flask.redirect(url_for_copr_details(copr))
638
639 640 -def process_delete(copr, url_on_error, url_on_success):
641 form = forms.CoprDeleteForm() 642 if form.validate_on_submit(): 643 644 try: 645 ComplexLogic.delete_copr(copr) 646 except (exceptions.ActionInProgressException, 647 exceptions.InsufficientRightsException) as e: 648 649 db.session.rollback() 650 flask.flash(str(e), "error") 651 return flask.redirect(url_on_error) 652 else: 653 db.session.commit() 654 flask.flash("Project has been deleted successfully.") 655 return flask.redirect(url_on_success) 656 else: 657 return render_template("coprs/detail/settings/delete.html", form=form, copr=copr)
658
659 660 @coprs_ns.route("/<username>/<coprname>/delete/", methods=["GET", "POST"]) 661 @coprs_ns.route("/g/<group_name>/<coprname>/delete/", methods=["GET", "POST"]) 662 @login_required 663 @req_with_copr 664 -def copr_delete(copr):
665 if copr.group: 666 url_on_success = url_for("groups_ns.list_projects_by_group", group_name=copr.group.name) 667 else: 668 url_on_success = url_for("coprs_ns.coprs_by_user", username=copr.user.username) 669 url_on_error = helpers.copr_url("coprs_ns.copr_detail", copr) 670 return process_delete(copr, url_on_error, url_on_success)
671 679 697
698 699 @coprs_ns.route("/<username>/<copr_dirname>/repo/<name_release>/", defaults={"repofile": None}) 700 @coprs_ns.route("/<username>/<copr_dirname>/repo/<name_release>/<repofile>") 701 @coprs_ns.route("/g/<group_name>/<copr_dirname>/repo/<name_release>/", defaults={"repofile": None}) 702 @coprs_ns.route("/g/<group_name>/<copr_dirname>/repo/<name_release>/<repofile>") 703 -def generate_repo_file(copr_dirname, name_release, repofile, username=None, group_name=None):
704 """ Generate repo file for a given repo name. 705 Reponame = username-coprname """ 706 707 ownername = username if username else ('@'+group_name) 708 copr_dir = ComplexLogic.get_copr_dir_safe(ownername, copr_dirname) 709 return render_generate_repo_file(copr_dir, name_release)
710
711 712 -def render_generate_repo_file(copr_dir, 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 = helpers.get_copr_repo_id(copr_dir) 719 url = os.path.join(copr_dir.repo_url, '') # adds trailing slash 720 repo_url = generate_repo_url(mock_chroot, url) 721 pubkey_url = urljoin(url, "pubkey.gpg") 722 response = flask.make_response( 723 flask.render_template("coprs/copr_dir.repo", copr_dir=copr_dir, url=repo_url, pubkey_url=pubkey_url, 724 repo_id=repo_id)) 725 response.mimetype = "text/plain" 726 response.headers["Content-Disposition"] = \ 727 "filename={0}.repo".format(copr_dir.repo_name) 728 729 name = REPO_DL_STAT_FMT.format(**{ 730 'copr_user': copr_dir.copr.user.name, 731 'copr_project_name': copr_dir.copr.name, 732 'copr_name_release': name_release, 733 }) 734 CounterStatLogic.incr(name=name, counter_type=CounterStatType.REPO_DL) 735 db.session.commit() 736 737 return response
738
739 740 ######################################################### 741 ### Module repo files ### 742 ######################################################### 743 744 @coprs_ns.route("/<username>/<coprname>/module_repo/<name_release>/<module_nsv>.repo") 745 @coprs_ns.route("/g/<group_name>/<coprname>/module_repo/<name_release>/<module_nsv>.repo") 746 @req_with_copr 747 -def generate_module_repo_file(copr, name_release, module_nsv):
748 """ Generate module repo file for a given project. """ 749 return render_generate_module_repo_file(copr, name_release, module_nsv)
750
751 -def render_generate_module_repo_file(copr, name_release, module_nsv):
752 module = ModulesLogic.get_by_nsv_str(copr, module_nsv).one() 753 mock_chroot = coprs_logic.MockChrootsLogic.get_from_name(name_release, noarch=True).first() 754 url = os.path.join(copr.main_dir.repo_url, '') # adds trailing slash 755 repo_url = generate_repo_url(mock_chroot, copr.modules_url) 756 baseurl = "{}+{}/latest/$basearch".format(repo_url.rstrip("/"), module_nsv) 757 pubkey_url = urljoin(url, "pubkey.gpg") 758 response = flask.make_response( 759 flask.render_template("coprs/copr-modules.cfg", copr=copr, module=module, 760 baseurl=baseurl, pubkey_url=pubkey_url)) 761 response.mimetype = "text/plain" 762 response.headers["Content-Disposition"] = \ 763 "filename={0}.cfg".format(copr.repo_name) 764 return response
765
766 ######################################################### 767 768 @coprs_ns.route("/<username>/<coprname>/rpm/<name_release>/<rpmfile>") 769 -def copr_repo_rpm_file(username, coprname, name_release, rpmfile):
770 try: 771 packages_dir = os.path.join(app.config["DATA_DIR"], "repo-rpm-packages") 772 with open(os.path.join(packages_dir, rpmfile), "rb") as rpm: 773 response = flask.make_response(rpm.read()) 774 response.mimetype = "application/x-rpm" 775 response.headers["Content-Disposition"] = \ 776 "filename={0}".format(rpmfile) 777 return response 778 except IOError: 779 return flask.render_template("404.html")
780
781 782 -def render_monitor(copr, detailed=False):
783 monitor = builds_logic.BuildsMonitorLogic.get_monitor_data(copr) 784 oses = [chroot.os for chroot in copr.active_chroots_sorted] 785 oses_grouped = [(len(list(group)), key) for key, group in groupby(oses)] 786 archs = [chroot.arch for chroot in copr.active_chroots_sorted] 787 if detailed: 788 template = "coprs/detail/monitor/detailed.html" 789 else: 790 template = "coprs/detail/monitor/simple.html" 791 return flask.Response(stream_with_context(helpers.stream_template(template, 792 copr=copr, 793 monitor=monitor, 794 oses=oses_grouped, 795 archs=archs, 796 status_enum_func=StatusEnum)))
797
798 799 @coprs_ns.route("/<username>/<coprname>/monitor/") 800 @coprs_ns.route("/<username>/<coprname>/monitor/<detailed>") 801 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/") 802 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/<detailed>") 803 @req_with_copr 804 -def copr_build_monitor(copr, detailed=False):
805 return render_monitor(copr, detailed == "detailed")
806
807 808 @coprs_ns.route("/<username>/<coprname>/fork/") 809 @coprs_ns.route("/g/<group_name>/<coprname>/fork/") 810 @login_required 811 @req_with_copr 812 -def copr_fork(copr):
813 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)() 814 return render_copr_fork(copr, form)
815
816 817 -def render_copr_fork(copr, form, confirm=False):
818 return flask.render_template("coprs/fork.html", copr=copr, form=form, confirm=confirm)
819
820 821 @coprs_ns.route("/<username>/<coprname>/fork/", methods=["POST"]) 822 @coprs_ns.route("/g/<group_name>/<coprname>/fork/", methods=["POST"]) 823 @login_required 824 @req_with_copr 825 -def copr_fork_post(copr):
826 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)() 827 if form.validate_on_submit(): 828 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0] 829 if flask.g.user.name != form.owner.data and not dstgroup: 830 return generic_error("There is no such group: {}".format(form.owner.data)) 831 832 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup) 833 if created: 834 msg = ("Forking project {} for you into {}. Please be aware that it may take a few minutes " 835 "to duplicate backend data.".format(copr.full_name, fcopr.full_name)) 836 elif not created and form.confirm.data == True: 837 msg = ("Updating packages in {} from {}. Please be aware that it may take a few minutes " 838 "to duplicate backend data.".format(copr.full_name, fcopr.full_name)) 839 else: 840 return render_copr_fork(copr, form, confirm=True) 841 842 db.session.commit() 843 flask.flash(msg) 844 845 return flask.redirect(url_for_copr_details(fcopr)) 846 return render_copr_fork(copr, form)
847
848 849 @coprs_ns.route("/<username>/<coprname>/forks/") 850 @coprs_ns.route("/g/<group_name>/<coprname>/forks/") 851 @req_with_copr 852 -def copr_forks(copr):
853 return flask.render_template("coprs/detail/forks.html", copr=copr)
854
855 856 @coprs_ns.route("/update_search_index/", methods=["POST"]) 857 -def copr_update_search_index():
858 subprocess.call(['/usr/share/copr/coprs_frontend/manage.py', 'update_indexes_quick', '1']) 859 return "OK"
860
861 862 @coprs_ns.route("/<username>/<coprname>/modules/") 863 @coprs_ns.route("/g/<group_name>/<coprname>/modules/") 864 @req_with_copr 865 -def copr_modules(copr):
866 return render_copr_modules(copr)
867
868 869 -def render_copr_modules(copr):
870 modules = ModulesLogic.get_multiple_by_copr(copr=copr).all() 871 return flask.render_template("coprs/detail/modules.html", copr=copr, modules=modules)
872
873 874 @coprs_ns.route("/<username>/<coprname>/create_module/") 875 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/") 876 @login_required 877 @req_with_copr 878 -def copr_create_module(copr):
879 form = forms.CreateModuleForm() 880 return render_create_module(copr, form)
881
882 883 -def render_create_module(copr, form, profiles=2):
884 built_packages = [] 885 for build in filter(None, [p.last_build(successful=True) for p in copr.packages]): 886 for package in build.built_packages.split("\n"): 887 built_packages.append((package.split()[0], build)) 888 889 return flask.render_template("coprs/create_module.html", copr=copr, form=form, built_packages=built_packages, profiles=profiles)
890
891 892 @coprs_ns.route("/<username>/<coprname>/create_module/", methods=["POST"]) 893 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/", methods=["POST"]) 894 @login_required 895 @req_with_copr 896 -def copr_create_module_post(copr):
897 form = forms.CreateModuleForm(copr=copr, meta={'csrf': False}) 898 args = [copr, form] 899 if "add_profile" in flask.request.values: 900 return add_profile(*args) 901 if "build_module" in flask.request.values: 902 return build_module(*args)
903 # @TODO Error
904 905 906 -def add_profile(copr, form):
907 n = len(form.profile_names) + 1 908 form.profile_names.append_entry() 909 for i in range(2, n): 910 form.profile_pkgs.append_entry() 911 return render_create_module(copr, form, profiles=n)
912
913 914 -def build_module(copr, form):
915 if not form.validate_on_submit(): 916 # WORKAROUND append those which are not in min_entries 917 for i in range(2, len(form.profile_names)): 918 form.profile_pkgs.append_entry() 919 return render_create_module(copr, form, profiles=len(form.profile_names)) 920 921 summary = "Module from Copr repository: {}".format(copr.full_name) 922 generator = ModulemdGenerator(str(copr.name), summary=summary, config=app.config) 923 generator.add_filter(form.filter.data) 924 generator.add_api(form.api.data) 925 generator.add_profiles(enumerate(zip(form.profile_names.data, form.profile_pkgs.data))) 926 generator.add_components(form.packages.data, form.filter.data, form.builds.data) 927 yaml = generator.generate() 928 929 facade = None 930 try: 931 facade = ModuleBuildFacade(flask.g.user, copr, yaml) 932 module = facade.submit_build() 933 db.session.commit() 934 935 flask.flash("Modulemd yaml file successfully generated and submitted to be build as {}" 936 .format(module.nsv), "success") 937 return flask.redirect(url_for_copr_details(copr)) 938 939 except ValidationError as ex: 940 flask.flash(ex.message, "error") 941 return render_create_module(copr, form, len(form.profile_names)) 942 943 except sqlalchemy.exc.IntegrityError: 944 flask.flash("Module {}-{}-{} already exists".format( 945 facade.modulemd.name, facade.modulemd.stream, facade.modulemd.version), "error") 946 db.session.rollback() 947 return render_create_module(copr, form, len(form.profile_names))
948
949 950 @coprs_ns.route("/<username>/<coprname>/module/<id>") 951 @coprs_ns.route("/g/<group_name>/<coprname>/module/<id>") 952 @req_with_copr 953 -def copr_module(copr, id):
954 module = ModulesLogic.get(id).first() 955 formatter = HtmlFormatter(style="autumn", linenos=False, noclasses=True) 956 pretty_yaml = highlight(module.yaml, get_lexer_by_name("YAML"), formatter) 957 958 # Get the list of chroots with unique name_release attribute 959 # Once we use jinja in 2.10 version, we can simply use 960 # {{ copr.active_chroots |unique(attribute='name_release') }} 961 unique_chroots = [] 962 unique_name_releases = set() 963 for chroot in copr.active_chroots_sorted: 964 if chroot.name_release in unique_name_releases: 965 continue 966 unique_chroots.append(chroot) 967 unique_name_releases.add(chroot.name_release) 968 969 return flask.render_template("coprs/detail/module.html", copr=copr, module=module, 970 yaml=pretty_yaml, unique_chroots=unique_chroots)
971
972 973 @coprs_ns.route("/<username>/<coprname>/module/<id>/raw") 974 @coprs_ns.route("/g/<group_name>/<coprname>/module/<id>/raw") 975 @req_with_copr 976 -def copr_module_raw(copr, id):
977 module = ModulesLogic.get(id).first() 978 response = flask.make_response(module.yaml) 979 response.mimetype = "text/plain" 980 response.headers["Content-Disposition"] = \ 981 "filename={}.yaml".format("-".join([str(module.id), module.name, module.stream, str(module.version)])) 982 return response
983