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