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 import modulemd
17 from email.mime.text import MIMEText
18 from itertools import groupby
19
20 from coprs import app
21 from coprs import db
22 from coprs import rcp
23 from coprs import exceptions
24 from coprs import forms
25 from coprs import helpers
26 from coprs import models
27 from coprs.exceptions import ObjectNotFound
28 from coprs.logic.coprs_logic import CoprsLogic
29 from coprs.logic.packages_logic import PackagesLogic
30 from coprs.logic.stat_logic import CounterStatLogic
31 from coprs.logic.users_logic import UsersLogic
32 from coprs.rmodels import TimedStatEvents
33
34 from coprs.logic.complex_logic import ComplexLogic
35
36 from coprs.views.misc import login_required, page_not_found, req_with_copr, req_with_copr, generic_error
37
38 from coprs.views.coprs_ns import coprs_ns
39 from coprs.views.groups_ns import groups_ns
40
41 from coprs.logic import builds_logic, coprs_logic, actions_logic, users_logic
42 from coprs.helpers import parse_package_name, generate_repo_url, CHROOT_RPMS_DL_STAT_FMT, CHROOT_REPO_MD_DL_STAT_FMT, \
43 str2bool, url_for_copr_view
51
58
59
60 @coprs_ns.route("/", defaults={"page": 1})
61 @coprs_ns.route("/<int:page>/")
62 -def coprs_show(page=1):
80
81
82 @coprs_ns.route("/<username>/", defaults={"page": 1})
83 @coprs_ns.route("/<username>/<int:page>/")
84 -def coprs_by_user(username=None, page=1):
107
108
109 @coprs_ns.route("/fulltext/", defaults={"page": 1})
110 @coprs_ns.route("/fulltext/<int:page>/")
111 -def coprs_fulltext_search(page=1):
112 fulltext = flask.request.args.get("fulltext", "")
113 try:
114 query = coprs_logic.CoprsLogic.get_multiple_fulltext(fulltext)
115 except ValueError as e:
116 flask.flash(str(e), "error")
117 return flask.redirect(flask.request.referrer or
118 flask.url_for("coprs_ns.coprs_show"))
119
120 paginator = helpers.Paginator(query, query.count(), page,
121 additional_params={"fulltext": fulltext})
122
123 coprs = paginator.sliced_query
124 return render_template(
125 "coprs/show/fulltext.html",
126 coprs=coprs,
127 paginator=paginator,
128 fulltext=fulltext,
129 tasks_info=ComplexLogic.get_queues_size(),
130 )
131
132
133 @coprs_ns.route("/<username>/add/")
134 @login_required
135 -def copr_add(username):
139
140
141 @coprs_ns.route("/g/<group_name>/add/")
142 @login_required
143 -def group_copr_add(group_name):
149
150
151 @coprs_ns.route("/g/<group_name>/new/", methods=["POST"])
154 group = ComplexLogic.get_group_by_name_safe(group_name)
155 form = forms.CoprFormFactory.create_form_cls(group=group)()
156
157 if form.validate_on_submit():
158 try:
159 copr = coprs_logic.CoprsLogic.add(
160 flask.g.user,
161 name=form.name.data,
162 homepage=form.homepage.data,
163 contact=form.contact.data,
164 repos=form.repos.data.replace("\n", " "),
165 selected_chroots=form.selected_chroots,
166 description=form.description.data,
167 instructions=form.instructions.data,
168 disable_createrepo=form.disable_createrepo.data,
169 build_enable_net=form.build_enable_net.data,
170 unlisted_on_hp=form.unlisted_on_hp.data,
171 group=group,
172 persistent=form.persistent.data,
173 )
174 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e:
175 flask.flash(str(e), "error")
176 return flask.render_template("coprs/group_add.html", form=form, group=group)
177
178 db.session.add(copr)
179 db.session.commit()
180 after_the_project_creation(copr, form)
181
182 return flask.redirect(url_for_copr_details(copr))
183 else:
184 return flask.render_template("coprs/group_add.html", form=form, group=group)
185
186
187 @coprs_ns.route("/<username>/new/", methods=["POST"])
188 @login_required
189 -def copr_new(username):
190 """
191 Receive information from the user on how to create its new copr
192 and create it accordingly.
193 """
194
195 form = forms.CoprFormFactory.create_form_cls()()
196 if form.validate_on_submit():
197 try:
198 copr = coprs_logic.CoprsLogic.add(
199 flask.g.user,
200 name=form.name.data,
201 homepage=form.homepage.data,
202 contact=form.contact.data,
203 repos=form.repos.data.replace("\n", " "),
204 selected_chroots=form.selected_chroots,
205 description=form.description.data,
206 instructions=form.instructions.data,
207 disable_createrepo=form.disable_createrepo.data,
208 build_enable_net=form.build_enable_net.data,
209 unlisted_on_hp=form.unlisted_on_hp.data,
210 persistent=form.persistent.data,
211 )
212 except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e:
213 flask.flash(str(e), "error")
214 return flask.render_template("coprs/add.html", form=form)
215
216 db.session.commit()
217 after_the_project_creation(copr, form)
218
219 return flask.redirect(url_for_copr_details(copr))
220 else:
221 return flask.render_template("coprs/add.html", form=form)
222
225 flask.flash("New project has been created successfully.", "success")
226 _check_rpmfusion(copr.repos)
227 if form.initial_pkgs.data:
228 pkgs = form.initial_pkgs.data.replace("\n", " ").split(" ")
229
230
231 bad_urls = []
232 for pkg in pkgs:
233 if not re.match("^.*\.src\.rpm$", pkg):
234 bad_urls.append(pkg)
235 flask.flash("Bad url: {0} (skipped)".format(pkg))
236 for bad_url in bad_urls:
237 pkgs.remove(bad_url)
238
239 if not pkgs:
240 flask.flash("No initial packages submitted")
241 else:
242
243 for pkg in pkgs:
244 builds_logic.BuildsLogic.add(
245 flask.g.user,
246 pkgs=pkg,
247 copr=copr,
248 enable_net=form.build_enable_net.data
249 )
250
251 db.session.commit()
252 flask.flash("Initial packages were successfully submitted "
253 "for building.")
254
255
256 @coprs_ns.route("/<username>/<coprname>/report-abuse")
257 @req_with_copr
258 @login_required
259 -def copr_report_abuse(copr):
261
262
263 @coprs_ns.route("/g/<group_name>/<coprname>/report-abuse")
264 @req_with_copr
265 @login_required
266 -def group_copr_report_abuse(copr):
268
273
274
275 @coprs_ns.route("/g/<group_name>/<coprname>/")
276 @req_with_copr
277 -def group_copr_detail(copr):
279
280
281 @coprs_ns.route("/<username>/<coprname>/")
282 @req_with_copr
283 -def copr_detail(copr):
287
290 repo_dl_stat = CounterStatLogic.get_copr_repo_dl_stat(copr)
291 form = forms.CoprLegalFlagForm()
292 repos_info = {}
293 for chroot in copr.active_chroots:
294
295
296
297
298
299 chroot_rpms_dl_stat_key = CHROOT_RPMS_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 = TimedStatEvents.get_count(
305 rconnect=rcp.get_connection(),
306 name=chroot_rpms_dl_stat_key,
307 )
308
309 if chroot.name_release not in repos_info:
310 repos_info[chroot.name_release] = {
311 "name_release": chroot.name_release,
312 "name_release_human": chroot.name_release_human,
313 "os_release": chroot.os_release,
314 "os_version": chroot.os_version,
315 "arch_list": [chroot.arch],
316 "repo_file": "{}-{}.repo".format(copr.repo_id, chroot.name_release),
317 "dl_stat": repo_dl_stat[chroot.name_release],
318 "rpm_dl_stat": {
319 chroot.arch: chroot_rpms_dl_stat
320 }
321 }
322 else:
323 repos_info[chroot.name_release]["arch_list"].append(chroot.arch)
324 repos_info[chroot.name_release]["rpm_dl_stat"][chroot.arch] = chroot_rpms_dl_stat
325 repos_info_list = sorted(repos_info.values(), key=lambda rec: rec["name_release"])
326 builds = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr).limit(1).all()
327
328 return flask.render_template(
329 "coprs/detail/overview.html",
330 copr=copr,
331 user=flask.g.user,
332 form=form,
333 repo_dl_stat=repo_dl_stat,
334 repos_info_list=repos_info_list,
335 latest_build=builds[0] if len(builds) == 1 else None,
336 )
337
338
339 @coprs_ns.route("/<username>/<coprname>/permissions/")
340 @req_with_copr
341 -def copr_permissions(copr):
369
385
386
387 @coprs_ns.route("/g/<group_name>/<coprname>/webhooks/")
388 @login_required
389 @req_with_copr
390 -def group_copr_webhooks(copr):
392
393
394 @coprs_ns.route("/<username>/<coprname>/webhooks/")
395 @login_required
396 @req_with_copr
397 -def copr_webhooks(copr):
399
408
409
410 @coprs_ns.route("/g/<group_name>/<coprname>/edit/")
411 @login_required
412 @req_with_copr
413 -def group_copr_edit(copr, form=None):
415
416
417 @coprs_ns.route("/<username>/<coprname>/edit/")
418 @login_required
419 @req_with_copr
420 -def copr_edit(copr, form=None):
422
425 if "rpmfusion" in repos:
426 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>.')
427 flask.flash(message, "error")
428
454
455
456 @coprs_ns.route("/g/<group_name>/<coprname>/update/", methods=["POST"])
471
472
473 @coprs_ns.route("/<username>/<coprname>/update/", methods=["POST"])
474 @login_required
475 @req_with_copr
476 -def copr_update(copr):
484
485
486 @coprs_ns.route("/<username>/<coprname>/permissions_applier_change/",
487 methods=["POST"])
491 permission = coprs_logic.CoprPermissionsLogic.get(copr, flask.g.user).first()
492 applier_permissions_form = \
493 forms.PermissionsApplierFormFactory.create_form_cls(permission)()
494
495 if copr.user == flask.g.user:
496 flask.flash("Owner cannot request permissions for his own project.", "error")
497 elif applier_permissions_form.validate_on_submit():
498
499 if permission is not None:
500 old_builder = permission.copr_builder
501 old_admin = permission.copr_admin
502 else:
503 old_builder = 0
504 old_admin = 0
505 new_builder = applier_permissions_form.copr_builder.data
506 new_admin = applier_permissions_form.copr_admin.data
507 coprs_logic.CoprPermissionsLogic.update_permissions_by_applier(
508 flask.g.user, copr, permission, new_builder, new_admin)
509 db.session.commit()
510 flask.flash(
511 "Successfuly updated permissions for project '{0}'."
512 .format(copr.name))
513 admin_mails = [copr.user.mail]
514 for perm in copr.copr_permissions:
515
516 if perm.copr_admin == 2:
517 admin_mails.append(perm.user.mail)
518
519
520 if flask.current_app.config.get("SEND_EMAILS", False):
521 for mail in admin_mails:
522 msg = MIMEText(
523 "{6} is asking for these permissions:\n\n"
524 "Builder: {0} -> {1}\nAdmin: {2} -> {3}\n\n"
525 "Project: {4}\nOwner: {5}".format(
526 helpers.PermissionEnum(old_builder),
527 helpers.PermissionEnum(new_builder),
528 helpers.PermissionEnum(old_admin),
529 helpers.PermissionEnum(new_admin),
530 copr.name, copr.user.name, flask.g.user.name))
531
532 msg["Subject"] = "[Copr] {0}: {1} is asking permissons".format(copr.name, flask.g.user.name)
533 msg["From"] = "root@{0}".format(platform.node())
534 msg["To"] = mail
535 s = smtplib.SMTP("localhost")
536 s.sendmail("root@{0}".format(platform.node()), mail, msg.as_string())
537 s.quit()
538
539 return flask.redirect(flask.url_for("coprs_ns.copr_detail",
540 username=copr.user.name,
541 coprname=copr.name))
542
543
544 @coprs_ns.route("/<username>/<coprname>/update_permissions/", methods=["POST"])
548 permissions = copr.copr_permissions
549 permissions_form = forms.PermissionsFormFactory.create_form_cls(
550 permissions)()
551
552 if permissions_form.validate_on_submit():
553
554 try:
555
556
557 permissions.sort(
558 key=lambda x: -1 if x.user_id == flask.g.user.id else 1)
559 for perm in permissions:
560 old_builder = perm.copr_builder
561 old_admin = perm.copr_admin
562 new_builder = permissions_form[
563 "copr_builder_{0}".format(perm.user_id)].data
564 new_admin = permissions_form[
565 "copr_admin_{0}".format(perm.user_id)].data
566 coprs_logic.CoprPermissionsLogic.update_permissions(
567 flask.g.user, copr, perm, new_builder, new_admin)
568 if flask.current_app.config.get("SEND_EMAILS", False) and \
569 (old_builder is not new_builder or old_admin is not new_admin):
570
571 msg = MIMEText(
572 "Your permissions have changed:\n\n"
573 "Builder: {0} -> {1}\nAdmin: {2} -> {3}\n\n"
574 "Project: {4}\nOwner: {5}".format(
575 helpers.PermissionEnum(old_builder),
576 helpers.PermissionEnum(new_builder),
577 helpers.PermissionEnum(old_admin),
578 helpers.PermissionEnum(new_admin),
579 copr.name, copr.user.name))
580
581 msg["Subject"] = "[Copr] {0}: Your permissions have changed".format(copr.name)
582 msg["From"] = "root@{0}".format(platform.node())
583 msg["To"] = perm.user.mail
584 s = smtplib.SMTP("localhost")
585 s.sendmail("root@{0}".format(platform.node()), perm.user.mail, msg.as_string())
586 s.quit()
587
588
589 except exceptions.InsufficientRightsException as e:
590 db.session.rollback()
591 flask.flash(str(e), "error")
592 else:
593 db.session.commit()
594 flask.flash("Project permissions were updated successfully.", "success")
595
596 return flask.redirect(url_for_copr_details(copr))
597
598
599 @coprs_ns.route("/id/<copr_id>/createrepo/", methods=["POST"])
612
615 form = forms.CoprDeleteForm()
616 if form.validate_on_submit():
617
618 try:
619 ComplexLogic.delete_copr(copr)
620 except (exceptions.ActionInProgressException,
621 exceptions.InsufficientRightsException) as e:
622
623 db.session.rollback()
624 flask.flash(str(e), "error")
625 return flask.redirect(url_on_error)
626 else:
627 db.session.commit()
628 flask.flash("Project has been deleted successfully.")
629 return flask.redirect(url_on_success)
630 else:
631 return render_template("coprs/detail/settings/delete.html", form=form, copr=copr)
632
633
634 @coprs_ns.route("/<username>/<coprname>/delete/", methods=["GET", "POST"])
635 @login_required
636 @req_with_copr
637 -def copr_delete(copr):
644
645
646 @coprs_ns.route("/g/<group_name>/<coprname>/delete/", methods=["GET", "POST"])
658
659
660 @coprs_ns.route("/<username>/<coprname>/legal_flag/", methods=["POST"])
666
667
668 @coprs_ns.route("/g/<group_name>/<coprname>/legal_flag/", methods=["POST"])
674
677 form = forms.CoprLegalFlagForm()
678 legal_flag = models.LegalFlag(raise_message=form.comment.data,
679 raised_on=int(time.time()),
680 copr=copr,
681 reporter=flask.g.user)
682 db.session.add(legal_flag)
683 db.session.commit()
684 send_to = app.config["SEND_LEGAL_TO"] or ["root@localhost"]
685 hostname = platform.node()
686 navigate_to = "\nNavigate to http://{0}{1}".format(
687 hostname, flask.url_for("admin_ns.legal_flag"))
688 contact = "\nContact on owner is: {}".format(contact_info)
689 reported_by = "\nReported by {0} <{1}>".format(flask.g.user.name,
690 flask.g.user.mail)
691 try:
692 msg = MIMEText(
693 form.comment.data + navigate_to + contact + reported_by, "plain")
694 except UnicodeEncodeError:
695 msg = MIMEText(form.comment.data.encode(
696 "utf-8") + navigate_to + contact + reported_by, "plain", "utf-8")
697 msg["Subject"] = "Legal flag raised on {0}".format(copr.name)
698 msg["From"] = "root@{0}".format(hostname)
699 msg["To"] = ", ".join(send_to)
700 s = smtplib.SMTP("localhost")
701 s.sendmail("root@{0}".format(hostname), send_to, msg.as_string())
702 s.quit()
703 flask.flash("Admin has been noticed about your report"
704 " and will investigate the project shortly.")
705 return flask.redirect(url_for_copr_details(copr))
706
707
708 @coprs_ns.route("/<username>/<coprname>/repo/<name_release>/", defaults={"repofile": None})
709 @coprs_ns.route("/<username>/<coprname>/repo/<name_release>/<repofile>")
710 -def generate_repo_file(username, coprname, name_release, repofile):
725
726
727 @coprs_ns.route("/g/<group_name>/<coprname>/repo/<name_release>/", defaults={"repofile": None})
728 @coprs_ns.route("/g/<group_name>/<coprname>/repo/<name_release>/<repofile>")
729 @req_with_copr
730 -def group_generate_repo_file(copr, name_release, repofile):
738
741
742
743 if name_release in [c.name for c in copr.mock_chroots]:
744 chroot = [c for c in copr.mock_chroots if c.name == name_release][0]
745 kwargs = dict(coprname=copr.name, name_release=chroot.name_release)
746 if copr.is_a_group_project:
747 fixed_url = url_for("coprs_ns.group_generate_repo_file",
748 group_name=copr.group.name, **kwargs)
749 else:
750 fixed_url = url_for("coprs_ns.generate_repo_file",
751 username=copr.user.username, **kwargs)
752 return flask.redirect(fixed_url)
753
754 mock_chroot = coprs_logic.MockChrootsLogic.get_from_name(name_release, noarch=True).first()
755 if not mock_chroot:
756 raise ObjectNotFound("Chroot {} does not exist".format(name_release))
757
758 url = os.path.join(copr.repo_url, '')
759 repo_url = generate_repo_url(mock_chroot, url)
760 pubkey_url = urljoin(url, "pubkey.gpg")
761 response = flask.make_response(
762 flask.render_template("coprs/copr.repo", copr=copr, url=repo_url, pubkey_url=pubkey_url))
763 response.mimetype = "text/plain"
764 response.headers["Content-Disposition"] = \
765 "filename={0}.repo".format(copr.repo_name)
766 return response
767
768
769
770
771
772
773 @coprs_ns.route("/<username>/<coprname>/repo/modules/")
774 @coprs_ns.route("/@<group_name>/<coprname>/repo/modules/")
775 @coprs_ns.route("/g/<group_name>/<coprname>/repo/modules/")
776 @req_with_copr
777 -def generate_module_repo_file(copr):
780
782 url = os.path.join(copr.repo_url, '')
783 pubkey_url = urljoin(url, "pubkey.gpg")
784 response = flask.make_response(
785 flask.render_template("coprs/copr-modules.cfg", copr=copr, url=url, pubkey_url=pubkey_url))
786 response.mimetype = "text/plain"
787 response.headers["Content-Disposition"] = \
788 "filename={0}.cfg".format(copr.repo_name)
789 return response
790
791
792
793 @coprs_ns.route("/<username>/<coprname>/rpm/<name_release>/<rpmfile>")
794 -def copr_repo_rpm_file(username, coprname, name_release, rpmfile):
795 try:
796 packages_dir = os.path.join(app.config["DATA_DIR"], "repo-rpm-packages")
797 with open(os.path.join(packages_dir, rpmfile), "rb") as rpm:
798 response = flask.make_response(rpm.read())
799 response.mimetype = "application/x-rpm"
800 response.headers["Content-Disposition"] = \
801 "filename={0}".format(rpmfile)
802 return response
803 except IOError:
804 return flask.render_template("404.html")
805
822
823
824 @coprs_ns.route("/<username>/<coprname>/monitor/")
825 @coprs_ns.route("/<username>/<coprname>/monitor/<detailed>")
826 @req_with_copr
827 -def copr_build_monitor(copr, detailed=False):
829
830
831 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/")
832 @coprs_ns.route("/g/<group_name>/<coprname>/monitor/<detailed>")
833 @req_with_copr
834 -def group_copr_build_monitor(copr, detailed=False):
836
837
838 @coprs_ns.route("/<username>/<coprname>/fork/")
839 @coprs_ns.route("/g/<group_name>/<coprname>/fork/")
840 @login_required
841 @req_with_copr
842 -def copr_fork(copr):
845
848 return flask.render_template("coprs/fork.html", copr=copr, form=form, confirm=confirm)
849
850
851 @coprs_ns.route("/<username>/<coprname>/fork/", methods=["POST"])
852 @coprs_ns.route("/g/<group_name>/<coprname>/fork/", methods=["POST"])
853 @login_required
854 @req_with_copr
855 -def copr_fork_post(copr):
856 form = forms.CoprForkFormFactory.create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)()
857 if form.validate_on_submit():
858 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0]
859 if flask.g.user.name != form.owner.data and not dstgroup:
860 return generic_error("There is no such group: {}".format(form.owner.data))
861
862 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup)
863 if created:
864 msg = ("Forking project {} for you into {}. Please be aware that it may take a few minutes "
865 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
866 elif not created and form.confirm.data == True:
867 msg = ("Updating packages in {} from {}. Please be aware that it may take a few minutes "
868 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
869 else:
870 return render_copr_fork(copr, form, confirm=True)
871
872 db.session.commit()
873 flask.flash(msg)
874
875 return flask.redirect(url_for_copr_details(fcopr))
876 return render_copr_fork(copr, form)
877
878
879 @coprs_ns.route("/update_search_index/", methods=["POST"])
881 subprocess.call(['/usr/share/copr/coprs_frontend/manage.py', 'update_indexes_quick', '1'])
882 return "OK"
883
884
885 @coprs_ns.route("/<username>/<coprname>/create_module/")
886 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/")
887 @login_required
888 @req_with_copr
889 -def copr_create_module(copr):
892
901
902
903 @coprs_ns.route("/<username>/<coprname>/create_module/", methods=["POST"])
904 @coprs_ns.route("/g/<group_name>/<coprname>/create_module/", methods=["POST"])
905 @login_required
906 @req_with_copr
907 -def copr_create_module_post(copr):
908 form = forms.CreateModuleForm(csrf_enabled=False)
909 args = [copr, form]
910 if "add_profile" in flask.request.values:
911 return add_profile(*args)
912 if "build_module" in flask.request.values:
913 return build_module(*args)
914
923
956