Package coprs :: Package views :: Package coprs_ns :: Module coprs_general
[hide private]
[frames] | no frames]

Source Code for Module coprs.views.coprs_ns.coprs_general

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