1
2
3 import os
4 import time
5 import os
6 import re
7 import uuid
8 import subprocess
9 from six.moves.urllib.parse import urljoin
10
11 import flask
12 from flask import render_template, url_for, stream_with_context
13 import platform
14 import smtplib
15 import sqlalchemy
16 from email.mime.text import MIMEText
17 from itertools import groupby
18
19 from coprs import app
20 from coprs import db
21 from coprs import rcp
22 from coprs import exceptions
23 from coprs import forms
24 from coprs import helpers
25 from coprs import models
26 from coprs.exceptions import ObjectNotFound
27 from coprs.logic.coprs_logic import CoprsLogic
28 from coprs.logic.stat_logic import CounterStatLogic
29 from coprs.logic.users_logic import UsersLogic
30 from coprs.rmodels import TimedStatEvents
31
32 from coprs.logic.complex_logic import ComplexLogic
33
34 from coprs.views.misc import login_required, page_not_found, req_with_copr, req_with_copr, generic_error
35
36 from coprs.views.coprs_ns import coprs_ns
37 from coprs.views.groups_ns import groups_ns
38
39 from coprs.logic import builds_logic, coprs_logic, actions_logic, users_logic
40 from coprs.helpers import parse_package_name, generate_repo_url, CHROOT_RPMS_DL_STAT_FMT, CHROOT_REPO_MD_DL_STAT_FMT, \
41 str2bool, url_for_copr_view
49
56
57
58 @coprs_ns.route("/", defaults={"page": 1})
59 @coprs_ns.route("/<int:page>/")
60 -def coprs_show(page=1):
78
79
80 @coprs_ns.route("/<username>/", defaults={"page": 1})
81 @coprs_ns.route("/<username>/<int:page>/")
82 -def coprs_by_user(username=None, page=1):
105
106
107 @coprs_ns.route("/fulltext/", defaults={"page": 1})
108 @coprs_ns.route("/fulltext/<int:page>/")
109 -def coprs_fulltext_search(page=1):
110 fulltext = flask.request.args.get("fulltext", "")
111 try:
112 query = coprs_logic.CoprsLogic.get_multiple_fulltext(fulltext)
113 except ValueError as e:
114 flask.flash(str(e), "error")
115 return flask.redirect(flask.request.referrer or
116 flask.url_for("coprs_ns.coprs_show"))
117
118 paginator = helpers.Paginator(query, query.count(), page,
119 additional_params={"fulltext": fulltext})
120
121 coprs = paginator.sliced_query
122 return render_template(
123 "coprs/show/fulltext.html",
124 coprs=coprs,
125 paginator=paginator,
126 fulltext=fulltext,
127 tasks_info=ComplexLogic.get_queues_size(),
128 )
129
130
131 @coprs_ns.route("/<username>/add/")
132 @login_required
133 -def copr_add(username):
137
138
139 @coprs_ns.route("/g/<group_name>/add/")
140 @login_required
141 -def group_copr_add(group_name):
147
148
149 @coprs_ns.route("/g/<group_name>/new/", methods=["POST"])
152 group = ComplexLogic.get_group_by_name_safe(group_name)
153 form = forms.CoprFormFactory.create_form_cls(group=group)()
154
155 if form.validate_on_submit():
156 try:
157 copr = coprs_logic.CoprsLogic.add(
158 flask.g.user,
159 name=form.name.data,
160 homepage=form.homepage.data,
161 contact=form.contact.data,
162 repos=form.repos.data.replace("\n", " "),
163 selected_chroots=form.selected_chroots,
164 description=form.description.data,
165 instructions=form.instructions.data,
166 disable_createrepo=form.disable_createrepo.data,
167 build_enable_net=form.build_enable_net.data,
168 unlisted_on_hp=form.unlisted_on_hp.data,
169 group=group,
170 persistent=form.persistent.data,
171 )
172 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e:
173 flask.flash(str(e), "error")
174 return flask.render_template("coprs/group_add.html", form=form, group=group)
175
176 db.session.add(copr)
177 db.session.commit()
178 after_the_project_creation(copr, form)
179
180 return flask.redirect(url_for_copr_details(copr))
181 else:
182 return flask.render_template("coprs/group_add.html", form=form, group=group)
183
184
185 @coprs_ns.route("/<username>/new/", methods=["POST"])
186 @login_required
187 -def copr_new(username):
188 """
189 Receive information from the user on how to create its new copr
190 and create it accordingly.
191 """
192
193 form = forms.CoprFormFactory.create_form_cls()()
194 if form.validate_on_submit():
195 try:
196 copr = coprs_logic.CoprsLogic.add(
197 flask.g.user,
198 name=form.name.data,
199 homepage=form.homepage.data,
200 contact=form.contact.data,
201 repos=form.repos.data.replace("\n", " "),
202 selected_chroots=form.selected_chroots,
203 description=form.description.data,
204 instructions=form.instructions.data,
205 disable_createrepo=form.disable_createrepo.data,
206 build_enable_net=form.build_enable_net.data,
207 unlisted_on_hp=form.unlisted_on_hp.data,
208 persistent=form.persistent.data,
209 )
210 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e:
211 flask.flash(str(e), "error")
212 return flask.render_template("coprs/add.html", form=form)
213
214 db.session.commit()
215 after_the_project_creation(copr, form)
216
217 return flask.redirect(url_for_copr_details(copr))
218 else:
219 return flask.render_template("coprs/add.html", form=form)
220
223 flask.flash("New project has been created successfully.", "success")
224 _check_rpmfusion(copr.repos)
225 if form.initial_pkgs.data:
226 pkgs = form.initial_pkgs.data.replace("\n", " ").split(" ")
227
228
229 bad_urls = []
230 for pkg in pkgs:
231 if not re.match("^.*\.src\.rpm$", pkg):
232 bad_urls.append(pkg)
233 flask.flash("Bad url: {0} (skipped)".format(pkg))
234 for bad_url in bad_urls:
235 pkgs.remove(bad_url)
236
237 if not pkgs:
238 flask.flash("No initial packages submitted")
239 else:
240
241 for pkg in pkgs:
242 builds_logic.BuildsLogic.add(
243 flask.g.user,
244 pkgs=pkg,
245 copr=copr,
246 enable_net=form.build_enable_net.data
247 )
248
249 db.session.commit()
250 flask.flash("Initial packages were successfully submitted "
251 "for building.")
252
253
254 @coprs_ns.route("/<username>/<coprname>/report-abuse")
255 @req_with_copr
256 -def copr_report_abuse(copr):
258
259
260 @coprs_ns.route("/g/<group_name>/<coprname>/report-abuse")
261 @req_with_copr
262 -def group_copr_report_abuse(copr):
264
269
270
271 @coprs_ns.route("/g/<group_name>/<coprname>/")
272 @req_with_copr
273 -def group_copr_detail(copr):
275
276
277 @coprs_ns.route("/<username>/<coprname>/")
278 @req_with_copr
279 -def copr_detail(copr):
283
286 repo_dl_stat = CounterStatLogic.get_copr_repo_dl_stat(copr)
287 form = forms.CoprLegalFlagForm()
288 repos_info = {}
289 for chroot in copr.active_chroots:
290
291
292
293
294
295 chroot_rpms_dl_stat_key = CHROOT_RPMS_DL_STAT_FMT.format(
296 copr_user=copr.user.name,
297 copr_project_name=copr.name,
298 copr_chroot=chroot.name,
299 )
300 chroot_rpms_dl_stat = TimedStatEvents.get_count(
301 rconnect=rcp.get_connection(),
302 name=chroot_rpms_dl_stat_key,
303 )
304
305 if chroot.name_release not in repos_info:
306 repos_info[chroot.name_release] = {
307 "name_release": chroot.name_release,
308 "name_release_human": chroot.name_release_human,
309 "os_release": chroot.os_release,
310 "os_version": chroot.os_version,
311 "arch_list": [chroot.arch],
312 "repo_file": "{}-{}.repo".format(copr.repo_id, chroot.name_release),
313 "dl_stat": repo_dl_stat[chroot.name_release],
314 "rpm_dl_stat": {
315 chroot.arch: chroot_rpms_dl_stat
316 }
317 }
318 else:
319 repos_info[chroot.name_release]["arch_list"].append(chroot.arch)
320 repos_info[chroot.name_release]["rpm_dl_stat"][chroot.arch] = chroot_rpms_dl_stat
321 repos_info_list = sorted(repos_info.values(), key=lambda rec: rec["name_release"])
322 builds = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr).limit(1).all()
323
324 return flask.render_template(
325 "coprs/detail/overview.html",
326 copr=copr,
327 user=flask.g.user,
328 form=form,
329 repo_dl_stat=repo_dl_stat,
330 repos_info_list=repos_info_list,
331 latest_build=builds[0] if len(builds) == 1 else None,
332 )
333
334
335 @coprs_ns.route("/<username>/<coprname>/permissions/")
336 @req_with_copr
337 -def copr_permissions(copr):
365
381
382
383 @coprs_ns.route("/g/<group_name>/<coprname>/webhooks/")
384 @login_required
385 @req_with_copr
386 -def group_copr_webhooks(copr):
388
389
390 @coprs_ns.route("/<username>/<coprname>/webhooks/")
391 @login_required
392 @req_with_copr
393 -def copr_webhooks(copr):
395
404
405
406 @coprs_ns.route("/g/<group_name>/<coprname>/edit/")
407 @login_required
408 @req_with_copr
409 -def group_copr_edit(copr, form=None):
411
412
413 @coprs_ns.route("/<username>/<coprname>/edit/")
414 @login_required
415 @req_with_copr
416 -def copr_edit(copr, form=None):
418
421 if "rpmfusion" in repos:
422 message = flask.Markup('Using rpmfusion as dependency is nearly always wrong. Please see <a href="https://fedorahosted.org/copr/wiki/UserDocs#WhatIcanbuildinCopr">What I can build in Copr</a>.')
423 flask.flash(message, "error")
424
450
451
452 @coprs_ns.route("/g/<group_name>/<coprname>/update/", methods=["POST"])
467
468
469 @coprs_ns.route("/<username>/<coprname>/update/", methods=["POST"])
470 @login_required
471 @req_with_copr
472 -def copr_update(copr):
480
481
482 @coprs_ns.route("/<username>/<coprname>/permissions_applier_change/",
483 methods=["POST"])
487 permission = coprs_logic.CoprPermissionsLogic.get(copr, flask.g.user).first()
488 applier_permissions_form = \
489 forms.PermissionsApplierFormFactory.create_form_cls(permission)()
490
491 if copr.user == flask.g.user:
492 flask.flash("Owner cannot request permissions for his own project.", "error")
493 elif applier_permissions_form.validate_on_submit():
494
495 if permission is not None:
496 old_builder = permission.copr_builder
497 old_admin = permission.copr_admin
498 else:
499 old_builder = 0
500 old_admin = 0
501 new_builder = applier_permissions_form.copr_builder.data
502 new_admin = applier_permissions_form.copr_admin.data
503 coprs_logic.CoprPermissionsLogic.update_permissions_by_applier(
504 flask.g.user, copr, permission, new_builder, new_admin)
505 db.session.commit()
506 flask.flash(
507 "Successfuly updated permissions for project '{0}'."
508 .format(copr.name))
509 admin_mails = [copr.user.mail]
510 for perm in copr.copr_permissions:
511
512 if perm.copr_admin == 2:
513 admin_mails.append(perm.user.mail)
514
515
516 if flask.current_app.config.get("SEND_EMAILS", False):
517 for mail in admin_mails:
518 msg = MIMEText(
519 "{6} is asking for these permissions:\n\n"
520 "Builder: {0} -> {1}\nAdmin: {2} -> {3}\n\n"
521 "Project: {4}\nOwner: {5}".format(
522 helpers.PermissionEnum(old_builder),
523 helpers.PermissionEnum(new_builder),
524 helpers.PermissionEnum(old_admin),
525 helpers.PermissionEnum(new_admin),
526 copr.name, copr.user.name, flask.g.user.name))
527
528 msg["Subject"] = "[Copr] {0}: {1} is asking permissons".format(copr.name, flask.g.user.name)
529 msg["From"] = "root@{0}".format(platform.node())
530 msg["To"] = mail
531 s = smtplib.SMTP("localhost")
532 s.sendmail("root@{0}".format(platform.node()), mail, msg.as_string())
533 s.quit()
534
535 return flask.redirect(flask.url_for("coprs_ns.copr_detail",
536 username=copr.user.name,
537 coprname=copr.name))
538
539
540 @coprs_ns.route("/<username>/<coprname>/update_permissions/", methods=["POST"])
544 permissions = copr.copr_permissions
545 permissions_form = forms.PermissionsFormFactory.create_form_cls(
546 permissions)()
547
548 if permissions_form.validate_on_submit():
549
550 try:
551
552
553 permissions.sort(
554 key=lambda x: -1 if x.user_id == flask.g.user.id else 1)
555 for perm in permissions:
556 old_builder = perm.copr_builder
557 old_admin = perm.copr_admin
558 new_builder = permissions_form[
559 "copr_builder_{0}".format(perm.user_id)].data
560 new_admin = permissions_form[
561 "copr_admin_{0}".format(perm.user_id)].data
562 coprs_logic.CoprPermissionsLogic.update_permissions(
563 flask.g.user, copr, perm, new_builder, new_admin)
564 if flask.current_app.config.get("SEND_EMAILS", False) and \
565 (old_builder is not new_builder or old_admin is not new_admin):
566
567 msg = MIMEText(
568 "Your permissions have changed:\n\n"
569 "Builder: {0} -> {1}\nAdmin: {2} -> {3}\n\n"
570 "Project: {4}\nOwner: {5}".format(
571 helpers.PermissionEnum(old_builder),
572 helpers.PermissionEnum(new_builder),
573 helpers.PermissionEnum(old_admin),
574 helpers.PermissionEnum(new_admin),
575 copr.name, copr.user.name))
576
577 msg["Subject"] = "[Copr] {0}: Your permissions have changed".format(copr.name)
578 msg["From"] = "root@{0}".format(platform.node())
579 msg["To"] = perm.user.mail
580 s = smtplib.SMTP("localhost")
581 s.sendmail("root@{0}".format(platform.node()), perm.user.mail, msg.as_string())
582 s.quit()
583
584
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("/id/<copr_id>/createrepo/", methods=["POST"])
608
611 form = forms.CoprDeleteForm()
612 if form.validate_on_submit():
613
614 try:
615 ComplexLogic.delete_copr(copr)
616 except (exceptions.ActionInProgressException,
617 exceptions.InsufficientRightsException) as e:
618
619 db.session.rollback()
620 flask.flash(str(e), "error")
621 return flask.redirect(url_on_error)
622 else:
623 db.session.commit()
624 flask.flash("Project has been deleted successfully.")
625 return flask.redirect(url_on_success)
626 else:
627 return render_template("coprs/detail/settings/delete.html", form=form, copr=copr)
628
629
630 @coprs_ns.route("/<username>/<coprname>/delete/", methods=["GET", "POST"])
631 @login_required
632 @req_with_copr
633 -def copr_delete(copr):
640
641
642 @coprs_ns.route("/g/<group_name>/<coprname>/delete/", methods=["GET", "POST"])
654
655
656 @coprs_ns.route("/<username>/<coprname>/legal_flag/", methods=["POST"])
662
663
664 @coprs_ns.route("/g/<group_name>/<coprname>/legal_flag/", methods=["POST"])
670
673 form = forms.CoprLegalFlagForm()
674 legal_flag = models.LegalFlag(raise_message=form.comment.data,
675 raised_on=int(time.time()),
676 copr=copr,
677 reporter=flask.g.user)
678 db.session.add(legal_flag)
679 db.session.commit()
680 send_to = app.config["SEND_LEGAL_TO"] or ["root@localhost"]
681 hostname = platform.node()
682 navigate_to = "\nNavigate to http://{0}{1}".format(
683 hostname, flask.url_for("admin_ns.legal_flag"))
684 contact = "\nContact on owner is: {}".format(contact_info)
685 reported_by = "\nReported by {0} <{1}>".format(flask.g.user.name,
686 flask.g.user.mail)
687 try:
688 msg = MIMEText(
689 form.comment.data + navigate_to + contact + reported_by, "plain")
690 except UnicodeEncodeError:
691 msg = MIMEText(form.comment.data.encode(
692 "utf-8") + navigate_to + contact + reported_by, "plain", "utf-8")
693 msg["Subject"] = "Legal flag raised on {0}".format(copr.name)
694 msg["From"] = "root@{0}".format(hostname)
695 msg["To"] = ", ".join(send_to)
696 s = smtplib.SMTP("localhost")
697 s.sendmail("root@{0}".format(hostname), send_to, msg.as_string())
698 s.quit()
699 flask.flash("Admin has been noticed about your report"
700 " and will investigate the project shortly.")
701 return flask.redirect(url_for_copr_details(copr))
702
703
704 @coprs_ns.route("/<username>/<coprname>/repo/<name_release>/", defaults={"repofile": None})
705 @coprs_ns.route("/<username>/<coprname>/repo/<name_release>/<repofile>")
706 -def generate_repo_file(username, coprname, name_release, repofile):
721
722
723 @coprs_ns.route("/g/<group_name>/<coprname>/repo/<name_release>/", defaults={"repofile": None})
724 @coprs_ns.route("/g/<group_name>/<coprname>/repo/<name_release>/<repofile>")
725 @req_with_copr
726 -def group_generate_repo_file(copr, name_release, repofile):
734
737
738
739 if name_release in [c.name for c in copr.mock_chroots]:
740 chroot = [c for c in copr.mock_chroots if c.name == name_release][0]
741 kwargs = dict(coprname=copr.name, name_release=chroot.name_release)
742 if copr.is_a_group_project:
743 fixed_url = url_for("coprs_ns.group_generate_repo_file",
744 group_name=copr.group.name, **kwargs)
745 else:
746 fixed_url = url_for("coprs_ns.generate_repo_file",
747 username=copr.user.username, **kwargs)
748 return flask.redirect(fixed_url)
749
750 mock_chroot = coprs_logic.MockChrootsLogic.get_from_name(name_release, noarch=True).first()
751 if not mock_chroot:
752 raise ObjectNotFound("Chroot {} does not exist".format(name_release))
753
754 url = os.path.join(copr.repo_url, '')
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.repo", copr=copr, url=repo_url, pubkey_url=pubkey_url))
759 response.mimetype = "text/plain"
760 response.headers["Content-Disposition"] = \
761 "filename={0}.repo".format(copr.repo_name)
762 return response
763
764
765 @coprs_ns.route("/<username>/<coprname>/rpm/<name_release>/<rpmfile>")
766 -def copr_repo_rpm_file(username, coprname, name_release, rpmfile):
767 try:
768 packages_dir = os.path.join(app.config["DATA_DIR"], "repo-rpm-packages")
769 with open(os.path.join(packages_dir, rpmfile), "rb") as rpm:
770 response = flask.make_response(rpm.read())
771 response.mimetype = "application/x-rpm"
772 response.headers["Content-Disposition"] = \
773 "filename={0}".format(rpmfile)
774 return response
775 except IOError:
776 return flask.render_template("404.html")
777
794
795
796 @coprs_ns.route("/<username>/<coprname>/monitor/")
797 @coprs_ns.route("/<username>/<coprname>/monitor/<detailed>")
798 @req_with_copr
799 -def copr_build_monitor(copr, detailed=False):
801
802
803 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/")
804 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/<detailed>")
805 @req_with_copr
806 -def group_copr_build_monitor(copr, detailed=False):
808
809
810 @coprs_ns.route("/<username>/<coprname>/fork/")
811 @coprs_ns.route("/g/<group_name>/<coprname>/fork/")
812 @login_required
813 @req_with_copr
814 -def copr_fork(copr):
817
820 return flask.render_template("coprs/fork.html", copr=copr, form=form, confirm=confirm)
821
822
823 @coprs_ns.route("/<username>/<coprname>/fork/", methods=["POST"])
824 @coprs_ns.route("/g/<group_name>/<coprname>/fork/", methods=["POST"])
825 @login_required
826 @req_with_copr
827 -def copr_fork_post(copr):
828 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)()
829 if form.validate_on_submit():
830 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0]
831 if flask.g.user.name != form.owner.data and not dstgroup:
832 return generic_error("There is no such group: {}".format(form.owner.data))
833
834 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup)
835 if created:
836 msg = ("Forking project {} for you into {}. Please be aware that it may take a few minutes "
837 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
838 elif not created and form.confirm.data == True:
839 msg = ("Updating packages in {} from {}. Please be aware that it may take a few minutes "
840 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
841 else:
842 return render_copr_fork(copr, form, confirm=True)
843
844 db.session.commit()
845 flask.flash(msg)
846
847 return flask.redirect(url_for_copr_details(fcopr))
848 return render_copr_fork(copr, form)
849
850
851 @coprs_ns.route("/update_search_index/", methods=["POST"])
853 subprocess.call(['/usr/share/copr/coprs_frontend/manage.py', 'update_indexes_quick', '1'])
854 return "OK"
855