1 import copy
2 import datetime
3 import json
4 import os
5 import flask
6 import json
7 import base64
8 import modulemd
9
10 from sqlalchemy.ext.associationproxy import association_proxy
11 from libravatar import libravatar_url
12 import zlib
13
14 from coprs import constants
15 from coprs import db
16 from coprs import helpers
17 from coprs import app
18
19 import itertools
20 import operator
21 from coprs.helpers import BuildSourceEnum, StatusEnum, ActionTypeEnum, JSONEncodedDict
27
28
29 -class User(db.Model, helpers.Serializer):
30
31 """
32 Represents user of the copr frontend
33 """
34
35
36 id = db.Column(db.Integer, primary_key=True)
37
38
39 username = db.Column(db.String(100), nullable=False, unique=True)
40
41
42 mail = db.Column(db.String(150), nullable=False)
43
44
45 timezone = db.Column(db.String(50), nullable=True)
46
47
48
49 proven = db.Column(db.Boolean, default=False)
50
51
52 admin = db.Column(db.Boolean, default=False)
53
54
55 proxy = db.Column(db.Boolean, default=False)
56
57
58 api_login = db.Column(db.String(40), nullable=False, default="abc")
59 api_token = db.Column(db.String(40), nullable=False, default="abc")
60 api_token_expiration = db.Column(
61 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
62
63
64 openid_groups = db.Column(JSONEncodedDict)
65
66 @property
68 """
69 Return the short username of the user, e.g. bkabrda
70 """
71
72 return self.username
73
75 """
76 Get permissions of this user for the given copr.
77 Caches the permission during one request,
78 so use this if you access them multiple times
79 """
80
81 if not hasattr(self, "_permissions_for_copr"):
82 self._permissions_for_copr = {}
83 if copr.name not in self._permissions_for_copr:
84 self._permissions_for_copr[copr.name] = (
85 CoprPermission.query
86 .filter_by(user=self)
87 .filter_by(copr=copr)
88 .first()
89 )
90 return self._permissions_for_copr[copr.name]
91
111
112 @property
118
119 @property
122
124 """
125 :type group: Group
126 """
127 if group.fas_name in self.user_teams:
128 return True
129 else:
130 return False
131
150
151 @property
153
154 return ["id", "name"]
155
156 @property
158 """
159 Get number of coprs for this user.
160 """
161
162 return (Copr.query.filter_by(user=self).
163 filter_by(deleted=False).
164 filter_by(group_id=None).
165 count())
166
167 @property
169 """
170 Return url to libravatar image.
171 """
172
173 try:
174 return libravatar_url(email=self.mail, https=True)
175 except IOError:
176 return ""
177
178
179 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
180
181 """
182 Represents a single copr (private repo with builds, mock chroots, etc.).
183 """
184
185 id = db.Column(db.Integer, primary_key=True)
186
187 name = db.Column(db.String(100), nullable=False)
188 homepage = db.Column(db.Text)
189 contact = db.Column(db.Text)
190
191
192 repos = db.Column(db.Text)
193
194 created_on = db.Column(db.Integer)
195
196 description = db.Column(db.Text)
197 instructions = db.Column(db.Text)
198 deleted = db.Column(db.Boolean, default=False)
199 playground = db.Column(db.Boolean, default=False)
200
201
202 auto_createrepo = db.Column(db.Boolean, default=True)
203
204
205 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
206 user = db.relationship("User", backref=db.backref("coprs"))
207 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
208 group = db.relationship("Group", backref=db.backref("groups"))
209 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
210 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
211 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
212
213
214 webhook_secret = db.Column(db.String(100))
215
216
217 build_enable_net = db.Column(db.Boolean, default=True,
218 server_default="1", nullable=False)
219
220 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
221
222
223 latest_indexed_data_update = db.Column(db.Integer)
224
225
226 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
227
228
229 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
230
231 __mapper_args__ = {
232 "order_by": created_on.desc()
233 }
234
235 @property
237 """
238 Return True if copr belongs to a group
239 """
240 return self.group_id is not None
241
242 @property
248
249 @property
255
256 @property
258 """
259 Return repos of this copr as a list of strings
260 """
261 return self.repos.split()
262
263 @property
270
271 @property
273 """
274 :rtype: list of CoprChroot
275 """
276 return [c for c in self.copr_chroots if c.is_active]
277
278 @property
280 """
281 Return list of active mock_chroots of this copr
282 """
283
284 return sorted(self.active_chroots, key=lambda ch: ch.name)
285
286 @property
288 """
289 Return list of active mock_chroots of this copr
290 """
291
292 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
293 output = []
294 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
295 output.append((os, [ch[1] for ch in chs]))
296
297 return output
298
299 @property
301 """
302 Return number of builds in this copr
303 """
304
305 return len(self.builds)
306
307 @property
311
312 @disable_createrepo.setter
316
317 @property
328
334
335 @property
338
339 @property
342
343 @property
348
349 @property
355
356 @property
358 return "/".join([self.repo_url, "modules"])
359
360 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
361 result = {}
362 for key in ["id", "name", "description", "instructions"]:
363 result[key] = str(copy.copy(getattr(self, key)))
364 result["owner"] = self.owner_name
365 return result
366
367 @property
372
375
378
379 """
380 Association class for Copr<->Permission relation
381 """
382
383
384
385 copr_builder = db.Column(db.SmallInteger, default=0)
386
387 copr_admin = db.Column(db.SmallInteger, default=0)
388
389
390 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
391 user = db.relationship("User", backref=db.backref("copr_permissions"))
392 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
393 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
394
395
396 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
397 """
398 Represents a single package in a project.
399 """
400 __table_args__ = (
401 db.UniqueConstraint('copr_id', 'name', name='packages_copr_pkgname'),
402 )
403
404 id = db.Column(db.Integer, primary_key=True)
405 name = db.Column(db.String(100), nullable=False)
406
407 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
408
409 source_json = db.Column(db.Text)
410
411 webhook_rebuild = db.Column(db.Boolean, default=False)
412
413 enable_net = db.Column(db.Boolean, default=False,
414 server_default="0", nullable=False)
415
416
417
418
419
420
421
422 old_status = db.Column(db.Integer)
423
424 builds = db.relationship("Build", order_by="Build.id")
425
426
427 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
428 copr = db.relationship("Copr", backref=db.backref("packages"))
429
430 @property
433
434 @property
439
440 @property
443
444 @property
446 """
447 Package's source type (and source_json) is being derived from its first build, which works except
448 for "srpm_link" and "srpm_upload" cases. Consider these being equivalent to source_type being unset.
449 """
450 return self.source_type and self.source_type_text != "srpm_link" and self.source_type_text != "srpm_upload"
451
452 @property
457
463
464 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
465 package_dict = super(Package, self).to_dict()
466 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
467
468 if with_latest_build:
469 build = self.last_build(successful=False)
470 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
471 if with_latest_succeeded_build:
472 build = self.last_build(successful=True)
473 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
474 if with_all_builds:
475 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
476
477 return package_dict
478
481
482
483 -class Build(db.Model, helpers.Serializer):
484
485 """
486 Representation of one build in one copr
487 """
488 __table_args__ = (db.Index('build_canceled', "canceled"), )
489
490 id = db.Column(db.Integer, primary_key=True)
491
492 pkgs = db.Column(db.Text)
493
494 built_packages = db.Column(db.Text)
495
496 pkg_version = db.Column(db.Text)
497
498 canceled = db.Column(db.Boolean, default=False)
499
500 repos = db.Column(db.Text)
501
502
503 submitted_on = db.Column(db.Integer, nullable=False)
504
505 results = db.Column(db.Text)
506
507 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
508
509 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
510
511 enable_net = db.Column(db.Boolean, default=False,
512 server_default="0", nullable=False)
513
514 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
515
516 source_json = db.Column(db.Text)
517
518 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
519
520 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
521
522
523 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
524 user = db.relationship("User", backref=db.backref("builds"))
525 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
526 copr = db.relationship("Copr", backref=db.backref("builds"))
527 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
528 package = db.relationship("Package")
529
530 chroots = association_proxy("build_chroots", "mock_chroot")
531
532 @property
535
536 @property
539
540 @property
543
544 @property
545 - def fail_type_text(self):
547
548 @property
550
551
552 return self.build_chroots[0].git_hash is None
553
554 @property
556 if self.repos is None:
557 return list()
558 else:
559 return self.repos.split()
560
561 @property
569
570 @property
575
576 @property
579
580 @property
582 mb_list = [chroot.started_on for chroot in
583 self.build_chroots if chroot.started_on]
584 if len(mb_list) > 0:
585 return min(mb_list)
586 else:
587 return None
588
589 @property
592
593 @property
595 if not self.build_chroots:
596 return None
597 if any(chroot.ended_on is None for chroot in self.build_chroots):
598 return None
599 return max(chroot.ended_on for chroot in self.build_chroots)
600
601 @property
603 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
604
605 @property
607 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
608
609 @property
612
613 @property
622
623 @property
625 return map(lambda chroot: chroot.status, self.build_chroots)
626
628 """
629 Get build chroots with states which present in `states` list
630 If states == None, function returns build_chroots
631 """
632 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
633 if statuses is not None:
634 statuses = set(statuses)
635 else:
636 return self.build_chroots
637
638 return [
639 chroot for chroot, status in chroot_states_map.items()
640 if status in statuses
641 ]
642
643 @property
645 return {b.name: b for b in self.build_chroots}
646
647 @property
654
655 @property
660
661 @property
664
665 @property
676
677 @property
679 """
680 Return text representation of status of this build
681 """
682
683 if self.status is not None:
684 return StatusEnum(self.status)
685
686 return "unknown"
687
688 @property
690 """
691 Find out if this build is cancelable.
692
693 Build is cancelabel only when it's pending (not started)
694 """
695
696 return self.status == StatusEnum("pending") or \
697 self.status == StatusEnum("importing") or \
698 self.status == StatusEnum("running")
699
700 @property
702 """
703 Find out if this build is repeatable.
704
705 Build is repeatable only if it's not pending, starting or running
706 """
707 return self.status not in [StatusEnum("pending"),
708 StatusEnum("starting"),
709 StatusEnum("running"),
710 StatusEnum("forked")]
711
712 @property
714 """
715 Find out if this build is in finished state.
716
717 Build is finished only if all its build_chroots are in finished state.
718 """
719 return all([(chroot.state in ["succeeded", "forked", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
720
721 @property
723 """
724 Find out if this build is persistent.
725
726 This property is inherited from the project.
727 """
728 return self.copr.persistent
729
730 @property
732 """
733 Extract source package name from source name or url
734 todo: obsolete
735 """
736 try:
737 src_rpm_name = self.pkgs.split("/")[-1]
738 except:
739 return None
740 if src_rpm_name.endswith(".src.rpm"):
741 return src_rpm_name[:-8]
742 else:
743 return src_rpm_name
744
745 @property
747 try:
748 return self.package.name
749 except:
750 return None
751
752 - def to_dict(self, options=None, with_chroot_states=False):
765
766
767 -class MockChroot(db.Model, helpers.Serializer):
768
769 """
770 Representation of mock chroot
771 """
772 __table_args__ = (
773 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
774 )
775
776 id = db.Column(db.Integer, primary_key=True)
777
778 os_release = db.Column(db.String(50), nullable=False)
779
780 os_version = db.Column(db.String(50), nullable=False)
781
782 arch = db.Column(db.String(50), nullable=False)
783 is_active = db.Column(db.Boolean, default=True)
784
785 @property
787 """
788 Textual representation of name of this chroot
789 """
790 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
791
792 @property
794 """
795 Textual representation of name of this or release
796 """
797 return "{}-{}".format(self.os_release, self.os_version)
798
799 @property
801 """
802 Textual representation of name of this or release
803 """
804 return "{} {}".format(self.os_release, self.os_version)
805
806 @property
808 """
809 Textual representation of the operating system name
810 """
811 return "{0} {1}".format(self.os_release, self.os_version)
812
813 @property
818
819
820 -class CoprChroot(db.Model, helpers.Serializer):
821
822 """
823 Representation of Copr<->MockChroot relation
824 """
825
826 buildroot_pkgs = db.Column(db.Text)
827 repos = db.Column(db.Text, default="", server_default="", nullable=False)
828 mock_chroot_id = db.Column(
829 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
830 mock_chroot = db.relationship(
831 "MockChroot", backref=db.backref("copr_chroots"))
832 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
833 copr = db.relationship("Copr",
834 backref=db.backref(
835 "copr_chroots",
836 single_parent=True,
837 cascade="all,delete,delete-orphan"))
838
839 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
840 comps_name = db.Column(db.String(127), nullable=True)
841
842 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
843 module_md_name = db.Column(db.String(127), nullable=True)
844
846 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
847
849 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
850
851 @property
854
855 @property
857 return self.repos.split()
858
859 @property
863
864 @property
868
869 @property
875
876 @property
882
883 @property
886
887 @property
890
892 options = {"__columns_only__": [
893 "buildroot_pkgs", "repos", "comps_name", "copr_id"
894 ]}
895 d = super(CoprChroot, self).to_dict(options=options)
896 d["mock_chroot"] = self.mock_chroot.name
897 return d
898
901
902 """
903 Representation of Build<->MockChroot relation
904 """
905
906 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
907 primary_key=True)
908 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
909 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
910 primary_key=True)
911 build = db.relationship("Build", backref=db.backref("build_chroots"))
912 git_hash = db.Column(db.String(40))
913 status = db.Column(db.Integer, default=StatusEnum("importing"))
914
915 started_on = db.Column(db.Integer)
916 ended_on = db.Column(db.Integer, index=True)
917
918 last_deferred = db.Column(db.Integer)
919
920 @property
922 """
923 Textual representation of name of this chroot
924 """
925
926 return self.mock_chroot.name
927
928 @property
930 """
931 Return text representation of status of this build chroot
932 """
933
934 if self.status is not None:
935 return StatusEnum(self.status)
936
937 return "unknown"
938
939 @property
942
943 @property
946
947 @property
959
960 @property
966
967 @property
972
973 @property
994
996 return "<BuildChroot: {}>".format(self.to_dict())
997
998
999 -class LegalFlag(db.Model, helpers.Serializer):
1000 id = db.Column(db.Integer, primary_key=True)
1001
1002 raise_message = db.Column(db.Text)
1003
1004 raised_on = db.Column(db.Integer)
1005
1006 resolved_on = db.Column(db.Integer)
1007
1008
1009 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1010
1011 copr = db.relationship(
1012 "Copr", backref=db.backref("legal_flags", cascade="all"))
1013
1014 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1015 reporter = db.relationship("User",
1016 backref=db.backref("legal_flags_raised"),
1017 foreign_keys=[reporter_id],
1018 primaryjoin="LegalFlag.reporter_id==User.id")
1019
1020 resolver_id = db.Column(
1021 db.Integer, db.ForeignKey("user.id"), nullable=True)
1022 resolver = db.relationship("User",
1023 backref=db.backref("legal_flags_resolved"),
1024 foreign_keys=[resolver_id],
1025 primaryjoin="LegalFlag.resolver_id==User.id")
1026
1027
1028 -class Action(db.Model, helpers.Serializer):
1085
1086
1087 -class Krb5Login(db.Model, helpers.Serializer):
1088 """
1089 Represents additional user information for kerberos authentication.
1090 """
1091
1092 __tablename__ = "krb5_login"
1093
1094
1095 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1096
1097
1098 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1099
1100
1101 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1102
1103 user = db.relationship("User", backref=db.backref("krb5_logins"))
1104
1107 """
1108 Generic store for simple statistics.
1109 """
1110
1111 name = db.Column(db.String(127), primary_key=True)
1112 counter_type = db.Column(db.String(30))
1113
1114 counter = db.Column(db.Integer, default=0, server_default="0")
1115
1116
1117 -class Group(db.Model, helpers.Serializer):
1118 """
1119 Represents FAS groups and their aliases in Copr
1120 """
1121 id = db.Column(db.Integer, primary_key=True)
1122 name = db.Column(db.String(127))
1123
1124
1125 fas_name = db.Column(db.String(127))
1126
1127 @property
1129 return u"@{}".format(self.name)
1130
1133
1136
1137
1138 -class Module(db.Model, helpers.Serializer):
1139 id = db.Column(db.Integer, primary_key=True)
1140 name = db.Column(db.String(100), nullable=False)
1141 stream = db.Column(db.String(100), nullable=False)
1142 version = db.Column(db.Integer, nullable=False)
1143 summary = db.Column(db.String(100), nullable=False)
1144 description = db.Column(db.Text)
1145 created_on = db.Column(db.Integer, nullable=True)
1146
1147
1148
1149
1150
1151
1152
1153 yaml_b64 = db.Column(db.Text)
1154
1155
1156 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1157 copr = db.relationship("Copr", backref=db.backref("modules"))
1158
1159 @property
1161 return base64.b64decode(self.yaml_b64)
1162
1163 @property
1165 mmd = modulemd.ModuleMetadata()
1166 mmd.loads(self.yaml)
1167 return mmd
1168
1169 @property
1172
1173 @property
1176
1177 @property
1180
1182
1183
1184
1185 module_dir = "fedora-24-{}+{}-{}-{}".format(arch, self.name, self.stream, self.version)
1186 return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch])
1187