1 import copy
2 import datetime
3 import json
4 import os
5 import flask
6
7 from sqlalchemy.ext.associationproxy import association_proxy
8 from libravatar import libravatar_url
9 import zlib
10
11 from coprs import constants
12 from coprs import db
13 from coprs import helpers
14 from coprs import app
15
16 import itertools
17 import operator
18 from coprs.helpers import BuildSourceEnum, StatusEnum, ActionTypeEnum, JSONEncodedDict
24
25
26 -class User(db.Model, helpers.Serializer):
27
28 """
29 Represents user of the copr frontend
30 """
31
32
33 id = db.Column(db.Integer, primary_key=True)
34
35
36 username = db.Column(db.String(100), nullable=False, unique=True)
37
38
39 mail = db.Column(db.String(150), nullable=False)
40
41
42 timezone = db.Column(db.String(50), nullable=True)
43
44
45
46 proven = db.Column(db.Boolean, default=False)
47
48
49 admin = db.Column(db.Boolean, default=False)
50
51
52 api_login = db.Column(db.String(40), nullable=False, default="abc")
53 api_token = db.Column(db.String(40), nullable=False, default="abc")
54 api_token_expiration = db.Column(
55 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
56
57
58 openid_groups = db.Column(JSONEncodedDict)
59
60 @property
62 """
63 Return the short username of the user, e.g. bkabrda
64 """
65
66 return self.username
67
69 """
70 Get permissions of this user for the given copr.
71 Caches the permission during one request,
72 so use this if you access them multiple times
73 """
74
75 if not hasattr(self, "_permissions_for_copr"):
76 self._permissions_for_copr = {}
77 if copr.name not in self._permissions_for_copr:
78 self._permissions_for_copr[copr.name] = (
79 CoprPermission.query
80 .filter_by(user=self)
81 .filter_by(copr=copr)
82 .first()
83 )
84 return self._permissions_for_copr[copr.name]
85
105
106 @property
112
113 @property
116
118 """
119 :type group: Group
120 """
121 if group.fas_name in self.user_teams:
122 return True
123 else:
124 return False
125
144
145 @property
147
148 return ["id", "name"]
149
150 @property
152 """
153 Get number of coprs for this user.
154 """
155
156 return (Copr.query.filter_by(user=self).
157 filter_by(deleted=False).
158 filter_by(group_id=None).
159 count())
160
161 @property
163 """
164 Return url to libravatar image.
165 """
166
167 try:
168 return libravatar_url(email=self.mail, https=True)
169 except IOError:
170 return ""
171
172
173 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
174
175 """
176 Represents a single copr (private repo with builds, mock chroots, etc.).
177 """
178
179 id = db.Column(db.Integer, primary_key=True)
180
181 name = db.Column(db.String(100), nullable=False)
182 homepage = db.Column(db.Text)
183 contact = db.Column(db.Text)
184
185
186 repos = db.Column(db.Text)
187
188 created_on = db.Column(db.Integer)
189
190 description = db.Column(db.Text)
191 instructions = db.Column(db.Text)
192 deleted = db.Column(db.Boolean, default=False)
193 playground = db.Column(db.Boolean, default=False)
194
195
196 auto_createrepo = db.Column(db.Boolean, default=True)
197
198
199 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
200 user = db.relationship("User", backref=db.backref("coprs"))
201 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
202 group = db.relationship("Group", backref=db.backref("groups"))
203 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
204 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
205 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
206
207
208 webhook_secret = db.Column(db.String(100))
209
210
211 build_enable_net = db.Column(db.Boolean, default=True,
212 server_default="1", nullable=False)
213
214 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
215
216
217 latest_indexed_data_update = db.Column(db.Integer)
218
219
220 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
221
222 __mapper_args__ = {
223 "order_by": created_on.desc()
224 }
225
226 @property
228 """
229 Return True if copr belongs to a group
230 """
231 return self.group_id is not None
232
233 @property
239
240 @property
246
247 @property
249 """
250 Return repos of this copr as a list of strings
251 """
252 return self.repos.split()
253
254 @property
256 """
257 Return list of active mock_chroots of this copr
258 """
259
260 return filter(lambda x: x.is_active, self.mock_chroots)
261
262 @property
264 """
265 :rtype: list of CoprChroot
266 """
267 return [c for c in self.copr_chroots if c.is_active]
268
269 @property
271 """
272 Return list of active mock_chroots of this copr
273 """
274
275 return sorted(self.active_chroots, key=lambda ch: ch.name)
276
277 @property
279 """
280 Return list of active mock_chroots of this copr
281 """
282
283 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
284 output = []
285 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
286 output.append((os, [ch[1] for ch in chs]))
287
288 return output
289
290 @property
292 """
293 Return number of builds in this copr
294 """
295
296 return len(self.builds)
297
298 @property
302
303 @disable_createrepo.setter
307
308 @property
318
324
325 @property
328
329 @property
332
333 @property
338
339 @property
345
346 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
347 result = {}
348 for key in ["id", "name", "description", "instructions"]:
349 result[key] = str(copy.copy(getattr(self, key)))
350 result["owner"] = self.owner_name
351 return result
352
353 @property
358
361
364
365 """
366 Association class for Copr<->Permission relation
367 """
368
369
370
371 copr_builder = db.Column(db.SmallInteger, default=0)
372
373 copr_admin = db.Column(db.SmallInteger, default=0)
374
375
376 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
377 user = db.relationship("User", backref=db.backref("copr_permissions"))
378 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
379 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
380
381
382 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
383 """
384 Represents a single package in a project.
385 """
386 id = db.Column(db.Integer, primary_key=True)
387 name = db.Column(db.String(100), nullable=False)
388
389 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
390
391 source_json = db.Column(db.Text)
392
393 webhook_rebuild = db.Column(db.Boolean, default=False)
394
395 enable_net = db.Column(db.Boolean, default=False,
396 server_default="0", nullable=False)
397
398
399
400
401
402
403
404 old_status = db.Column(db.Integer)
405
406 builds = db.relationship("Build", order_by="Build.id")
407
408
409 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
410 copr = db.relationship("Copr", backref=db.backref("packages"))
411
412 @property
415
416 @property
421
422 @property
425
426 @property
428 """
429 Package's source type (and source_json) is being derived from its first build, which works except
430 for "srpm_link" and "srpm_upload" cases. Consider these being equivalent to source_type being unset.
431 """
432 return self.source_type and self.source_type_text != "srpm_link" and self.source_type_text != "srpm_upload"
433
434 @property
439
445
446 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
447 package_dict = super(Package, self).to_dict()
448 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
449
450 if with_latest_build:
451 build = self.last_build(successful=False)
452 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
453 if with_latest_succeeded_build:
454 build = self.last_build(successful=True)
455 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
456 if with_all_builds:
457 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
458
459 return package_dict
460
463
464
465 -class Build(db.Model, helpers.Serializer):
466
467 """
468 Representation of one build in one copr
469 """
470 __table_args__ = (db.Index('build_canceled', "canceled"), )
471
472 id = db.Column(db.Integer, primary_key=True)
473
474 pkgs = db.Column(db.Text)
475
476 built_packages = db.Column(db.Text)
477
478 pkg_version = db.Column(db.Text)
479
480 canceled = db.Column(db.Boolean, default=False)
481
482 repos = db.Column(db.Text)
483
484
485 submitted_on = db.Column(db.Integer, nullable=False)
486
487 results = db.Column(db.Text)
488
489 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
490
491 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
492
493 enable_net = db.Column(db.Boolean, default=False,
494 server_default="0", nullable=False)
495
496 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
497
498 source_json = db.Column(db.Text)
499
500 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
501
502 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
503
504
505 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
506 user = db.relationship("User", backref=db.backref("builds"))
507 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
508 copr = db.relationship("Copr", backref=db.backref("builds"))
509 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
510 package = db.relationship("Package")
511
512 chroots = association_proxy("build_chroots", "mock_chroot")
513
514 @property
517
518 @property
521
522 @property
525
526 @property
527 - def fail_type_text(self):
529
530 @property
532
533
534 return self.build_chroots[0].git_hash is None
535
536 @property
538 if self.repos is None:
539 return list()
540 else:
541 return self.repos.split()
542
543 @property
551
552 @property
557
558 @property
561
562 @property
564 mb_list = [chroot.started_on for chroot in
565 self.build_chroots if chroot.started_on]
566 if len(mb_list) > 0:
567 return min(mb_list)
568 else:
569 return None
570
571 @property
574
575 @property
577 if not self.build_chroots:
578 return None
579 if any(chroot.ended_on is None for chroot in self.build_chroots):
580 return None
581 return max(chroot.ended_on for chroot in self.build_chroots)
582
583 @property
585 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
586
587 @property
589 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
590
591 @property
594
595 @property
604
605 @property
607 return map(lambda chroot: chroot.status, self.build_chroots)
608
610 """
611 Get build chroots with states which present in `states` list
612 If states == None, function returns build_chroots
613 """
614 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
615 if statuses is not None:
616 statuses = set(statuses)
617 else:
618 return self.build_chroots
619
620 return [
621 chroot for chroot, status in chroot_states_map.items()
622 if status in statuses
623 ]
624
625 @property
627 return {b.name: b for b in self.build_chroots}
628
629 @property
636
637 @property
642
643 @property
646
647 @property
658
659 @property
661 """
662 Return text representation of status of this build
663 """
664
665 if self.status is not None:
666 return StatusEnum(self.status)
667
668 return "unknown"
669
670 @property
672 """
673 Find out if this build is cancelable.
674
675 Build is cancelabel only when it's pending (not started)
676 """
677
678 return self.status == StatusEnum("pending") or \
679 self.status == StatusEnum("importing")
680
681 @property
683 """
684 Find out if this build is repeatable.
685
686 Build is repeatable only if it's not pending, starting or running
687 """
688 return self.status not in [StatusEnum("pending"),
689 StatusEnum("starting"),
690 StatusEnum("running"), ]
691
692 @property
694 """
695 Find out if this build is in finished state.
696
697 Build is finished only if all its build_chroots are in finished state.
698 """
699 return all([(chroot.state in ["succeeded", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
700
701 @property
703 """
704 Find out if this build is persistent.
705
706 This property is inherited from the project.
707 """
708 return self.copr.persistent
709
710 @property
712 """
713 Extract source package name from source name or url
714 todo: obsolete
715 """
716 try:
717 src_rpm_name = self.pkgs.split("/")[-1]
718 except:
719 return None
720 if src_rpm_name.endswith(".src.rpm"):
721 return src_rpm_name[:-8]
722 else:
723 return src_rpm_name
724
725 @property
727 try:
728 return self.package.name
729 except:
730 return None
731
732 - def to_dict(self, options=None, with_chroot_states=False):
745
746
747 -class MockChroot(db.Model, helpers.Serializer):
748
749 """
750 Representation of mock chroot
751 """
752
753 id = db.Column(db.Integer, primary_key=True)
754
755 os_release = db.Column(db.String(50), nullable=False)
756
757 os_version = db.Column(db.String(50), nullable=False)
758
759 arch = db.Column(db.String(50), nullable=False)
760 is_active = db.Column(db.Boolean, default=True)
761
762 @property
764 """
765 Textual representation of name of this chroot
766 """
767 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
768
769 @property
771 """
772 Textual representation of name of this or release
773 """
774 return "{}-{}".format(self.os_release, self.os_version)
775
776 @property
778 """
779 Textual representation of name of this or release
780 """
781 return "{} {}".format(self.os_release, self.os_version)
782
783 @property
785 """
786 Textual representation of the operating system name
787 """
788 return "{0} {1}".format(self.os_release, self.os_version)
789
790 @property
795
796
797 -class CoprChroot(db.Model, helpers.Serializer):
798
799 """
800 Representation of Copr<->MockChroot relation
801 """
802
803 buildroot_pkgs = db.Column(db.Text)
804 mock_chroot_id = db.Column(
805 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
806 mock_chroot = db.relationship(
807 "MockChroot", backref=db.backref("copr_chroots"))
808 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
809 copr = db.relationship("Copr",
810 backref=db.backref(
811 "copr_chroots",
812 single_parent=True,
813 cascade="all,delete,delete-orphan"))
814
815 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
816 comps_name = db.Column(db.String(127), nullable=True)
817
818 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
819 module_md_name = db.Column(db.String(127), nullable=True)
820
822 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
823
825 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
826
827 @property
830
831 @property
835
836 @property
840
841 @property
847
848 @property
854
855 @property
858
859 @property
862
865
866 """
867 Representation of Build<->MockChroot relation
868 """
869
870 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
871 primary_key=True)
872 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
873 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
874 primary_key=True)
875 build = db.relationship("Build", backref=db.backref("build_chroots"))
876 git_hash = db.Column(db.String(40))
877 status = db.Column(db.Integer, default=StatusEnum("importing"))
878
879 started_on = db.Column(db.Integer)
880 ended_on = db.Column(db.Integer)
881
882 last_deferred = db.Column(db.Integer)
883
884 @property
886 """
887 Textual representation of name of this chroot
888 """
889
890 return self.mock_chroot.name
891
892 @property
894 """
895 Return text representation of status of this build chroot
896 """
897
898 if self.status is not None:
899 return StatusEnum(self.status)
900
901 return "unknown"
902
903 @property
906
907 @property
910
911 @property
918
919 @property
925
926 @property
931
932 @property
953
955 return "<BuildChroot: {}>".format(self.to_dict())
956
957
958 -class LegalFlag(db.Model, helpers.Serializer):
959 id = db.Column(db.Integer, primary_key=True)
960
961 raise_message = db.Column(db.Text)
962
963 raised_on = db.Column(db.Integer)
964
965 resolved_on = db.Column(db.Integer)
966
967
968 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
969
970 copr = db.relationship(
971 "Copr", backref=db.backref("legal_flags", cascade="all"))
972
973 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
974 reporter = db.relationship("User",
975 backref=db.backref("legal_flags_raised"),
976 foreign_keys=[reporter_id],
977 primaryjoin="LegalFlag.reporter_id==User.id")
978
979 resolver_id = db.Column(
980 db.Integer, db.ForeignKey("user.id"), nullable=True)
981 resolver = db.relationship("User",
982 backref=db.backref("legal_flags_resolved"),
983 foreign_keys=[resolver_id],
984 primaryjoin="LegalFlag.resolver_id==User.id")
985
986
987 -class Action(db.Model, helpers.Serializer):
1031
1032
1033 -class Krb5Login(db.Model, helpers.Serializer):
1034 """
1035 Represents additional user information for kerberos authentication.
1036 """
1037
1038 __tablename__ = "krb5_login"
1039
1040
1041 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1042
1043
1044 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1045
1046
1047 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1048
1049 user = db.relationship("User", backref=db.backref("krb5_logins"))
1050
1053 """
1054 Generic store for simple statistics.
1055 """
1056
1057 name = db.Column(db.String(127), primary_key=True)
1058 counter_type = db.Column(db.String(30))
1059
1060 counter = db.Column(db.Integer, default=0, server_default="0")
1061
1062
1063 -class Group(db.Model, helpers.Serializer):
1064 """
1065 Represents FAS groups and their aliases in Copr
1066 """
1067 id = db.Column(db.Integer, primary_key=True)
1068 name = db.Column(db.String(127))
1069
1070
1071 fas_name = db.Column(db.String(127))
1072
1073 @property
1075 return u"@{}".format(self.name)
1076
1079
1082