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 api_login = db.Column(db.String(40), nullable=False, default="abc")
56 api_token = db.Column(db.String(40), nullable=False, default="abc")
57 api_token_expiration = db.Column(
58 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
59
60
61 openid_groups = db.Column(JSONEncodedDict)
62
63 @property
65 """
66 Return the short username of the user, e.g. bkabrda
67 """
68
69 return self.username
70
72 """
73 Get permissions of this user for the given copr.
74 Caches the permission during one request,
75 so use this if you access them multiple times
76 """
77
78 if not hasattr(self, "_permissions_for_copr"):
79 self._permissions_for_copr = {}
80 if copr.name not in self._permissions_for_copr:
81 self._permissions_for_copr[copr.name] = (
82 CoprPermission.query
83 .filter_by(user=self)
84 .filter_by(copr=copr)
85 .first()
86 )
87 return self._permissions_for_copr[copr.name]
88
108
109 @property
115
116 @property
119
121 """
122 :type group: Group
123 """
124 if group.fas_name in self.user_teams:
125 return True
126 else:
127 return False
128
147
148 @property
150
151 return ["id", "name"]
152
153 @property
155 """
156 Get number of coprs for this user.
157 """
158
159 return (Copr.query.filter_by(user=self).
160 filter_by(deleted=False).
161 filter_by(group_id=None).
162 count())
163
164 @property
166 """
167 Return url to libravatar image.
168 """
169
170 try:
171 return libravatar_url(email=self.mail, https=True)
172 except IOError:
173 return ""
174
175
176 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
177
178 """
179 Represents a single copr (private repo with builds, mock chroots, etc.).
180 """
181
182 id = db.Column(db.Integer, primary_key=True)
183
184 name = db.Column(db.String(100), nullable=False)
185 homepage = db.Column(db.Text)
186 contact = db.Column(db.Text)
187
188
189 repos = db.Column(db.Text)
190
191 created_on = db.Column(db.Integer)
192
193 description = db.Column(db.Text)
194 instructions = db.Column(db.Text)
195 deleted = db.Column(db.Boolean, default=False)
196 playground = db.Column(db.Boolean, default=False)
197
198
199 auto_createrepo = db.Column(db.Boolean, default=True)
200
201
202 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
203 user = db.relationship("User", backref=db.backref("coprs"))
204 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
205 group = db.relationship("Group", backref=db.backref("groups"))
206 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
207 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
208 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
209
210
211 webhook_secret = db.Column(db.String(100))
212
213
214 build_enable_net = db.Column(db.Boolean, default=True,
215 server_default="1", nullable=False)
216
217 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
218
219
220 latest_indexed_data_update = db.Column(db.Integer)
221
222
223 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
224
225
226 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
227
228 __mapper_args__ = {
229 "order_by": created_on.desc()
230 }
231
232 @property
234 """
235 Return True if copr belongs to a group
236 """
237 return self.group_id is not None
238
239 @property
245
246 @property
252
253 @property
255 """
256 Return repos of this copr as a list of strings
257 """
258 return self.repos.split()
259
260 @property
267
268 @property
270 """
271 :rtype: list of CoprChroot
272 """
273 return [c for c in self.copr_chroots if c.is_active]
274
275 @property
277 """
278 Return list of active mock_chroots of this copr
279 """
280
281 return sorted(self.active_chroots, key=lambda ch: ch.name)
282
283 @property
285 """
286 Return list of active mock_chroots of this copr
287 """
288
289 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
290 output = []
291 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
292 output.append((os, [ch[1] for ch in chs]))
293
294 return output
295
296 @property
298 """
299 Return number of builds in this copr
300 """
301
302 return len(self.builds)
303
304 @property
308
309 @disable_createrepo.setter
313
314 @property
325
331
332 @property
335
336 @property
339
340 @property
345
346 @property
352
353 @property
355 return "/".join([self.repo_url, "modules"])
356
357 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
358 result = {}
359 for key in ["id", "name", "description", "instructions"]:
360 result[key] = str(copy.copy(getattr(self, key)))
361 result["owner"] = self.owner_name
362 return result
363
364 @property
369
372
375
376 """
377 Association class for Copr<->Permission relation
378 """
379
380
381
382 copr_builder = db.Column(db.SmallInteger, default=0)
383
384 copr_admin = db.Column(db.SmallInteger, default=0)
385
386
387 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
388 user = db.relationship("User", backref=db.backref("copr_permissions"))
389 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
390 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
391
392
393 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
394 """
395 Represents a single package in a project.
396 """
397 __table_args__ = (
398 db.UniqueConstraint('copr_id', 'name', name='packages_copr_pkgname'),
399 )
400
401 id = db.Column(db.Integer, primary_key=True)
402 name = db.Column(db.String(100), nullable=False)
403
404 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
405
406 source_json = db.Column(db.Text)
407
408 webhook_rebuild = db.Column(db.Boolean, default=False)
409
410 enable_net = db.Column(db.Boolean, default=False,
411 server_default="0", nullable=False)
412
413
414
415
416
417
418
419 old_status = db.Column(db.Integer)
420
421 builds = db.relationship("Build", order_by="Build.id")
422
423
424 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
425 copr = db.relationship("Copr", backref=db.backref("packages"))
426
427 @property
430
431 @property
436
437 @property
440
441 @property
443 """
444 Package's source type (and source_json) is being derived from its first build, which works except
445 for "srpm_link" and "srpm_upload" cases. Consider these being equivalent to source_type being unset.
446 """
447 return self.source_type and self.source_type_text != "srpm_link" and self.source_type_text != "srpm_upload"
448
449 @property
454
460
461 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
462 package_dict = super(Package, self).to_dict()
463 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
464
465 if with_latest_build:
466 build = self.last_build(successful=False)
467 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
468 if with_latest_succeeded_build:
469 build = self.last_build(successful=True)
470 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
471 if with_all_builds:
472 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
473
474 return package_dict
475
478
479
480 -class Build(db.Model, helpers.Serializer):
481
482 """
483 Representation of one build in one copr
484 """
485 __table_args__ = (db.Index('build_canceled', "canceled"), )
486
487 id = db.Column(db.Integer, primary_key=True)
488
489 pkgs = db.Column(db.Text)
490
491 built_packages = db.Column(db.Text)
492
493 pkg_version = db.Column(db.Text)
494
495 canceled = db.Column(db.Boolean, default=False)
496
497 repos = db.Column(db.Text)
498
499
500 submitted_on = db.Column(db.Integer, nullable=False)
501
502 results = db.Column(db.Text)
503
504 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
505
506 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
507
508 enable_net = db.Column(db.Boolean, default=False,
509 server_default="0", nullable=False)
510
511 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
512
513 source_json = db.Column(db.Text)
514
515 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
516
517 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
518
519
520 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
521 user = db.relationship("User", backref=db.backref("builds"))
522 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
523 copr = db.relationship("Copr", backref=db.backref("builds"))
524 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
525 package = db.relationship("Package")
526
527 chroots = association_proxy("build_chroots", "mock_chroot")
528
529 @property
532
533 @property
536
537 @property
540
541 @property
542 - def fail_type_text(self):
544
545 @property
547
548
549 return self.build_chroots[0].git_hash is None
550
551 @property
553 if self.repos is None:
554 return list()
555 else:
556 return self.repos.split()
557
558 @property
566
567 @property
572
573 @property
576
577 @property
579 mb_list = [chroot.started_on for chroot in
580 self.build_chroots if chroot.started_on]
581 if len(mb_list) > 0:
582 return min(mb_list)
583 else:
584 return None
585
586 @property
589
590 @property
592 if not self.build_chroots:
593 return None
594 if any(chroot.ended_on is None for chroot in self.build_chroots):
595 return None
596 return max(chroot.ended_on for chroot in self.build_chroots)
597
598 @property
600 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
601
602 @property
604 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
605
606 @property
609
610 @property
619
620 @property
622 return map(lambda chroot: chroot.status, self.build_chroots)
623
625 """
626 Get build chroots with states which present in `states` list
627 If states == None, function returns build_chroots
628 """
629 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
630 if statuses is not None:
631 statuses = set(statuses)
632 else:
633 return self.build_chroots
634
635 return [
636 chroot for chroot, status in chroot_states_map.items()
637 if status in statuses
638 ]
639
640 @property
642 return {b.name: b for b in self.build_chroots}
643
644 @property
651
652 @property
657
658 @property
661
662 @property
673
674 @property
676 """
677 Return text representation of status of this build
678 """
679
680 if self.status is not None:
681 return StatusEnum(self.status)
682
683 return "unknown"
684
685 @property
687 """
688 Find out if this build is cancelable.
689
690 Build is cancelabel only when it's pending (not started)
691 """
692
693 return self.status == StatusEnum("pending") or \
694 self.status == StatusEnum("importing") or \
695 self.status == StatusEnum("running")
696
697 @property
699 """
700 Find out if this build is repeatable.
701
702 Build is repeatable only if it's not pending, starting or running
703 """
704 return self.status not in [StatusEnum("pending"),
705 StatusEnum("starting"),
706 StatusEnum("running"),
707 StatusEnum("forked")]
708
709 @property
711 """
712 Find out if this build is in finished state.
713
714 Build is finished only if all its build_chroots are in finished state.
715 """
716 return all([(chroot.state in ["succeeded", "forked", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
717
718 @property
720 """
721 Find out if this build is persistent.
722
723 This property is inherited from the project.
724 """
725 return self.copr.persistent
726
727 @property
729 """
730 Extract source package name from source name or url
731 todo: obsolete
732 """
733 try:
734 src_rpm_name = self.pkgs.split("/")[-1]
735 except:
736 return None
737 if src_rpm_name.endswith(".src.rpm"):
738 return src_rpm_name[:-8]
739 else:
740 return src_rpm_name
741
742 @property
744 try:
745 return self.package.name
746 except:
747 return None
748
749 - def to_dict(self, options=None, with_chroot_states=False):
762
763
764 -class MockChroot(db.Model, helpers.Serializer):
765
766 """
767 Representation of mock chroot
768 """
769
770 id = db.Column(db.Integer, primary_key=True)
771
772 os_release = db.Column(db.String(50), nullable=False)
773
774 os_version = db.Column(db.String(50), nullable=False)
775
776 arch = db.Column(db.String(50), nullable=False)
777 is_active = db.Column(db.Boolean, default=True)
778
779 @property
781 """
782 Textual representation of name of this chroot
783 """
784 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
785
786 @property
788 """
789 Textual representation of name of this or release
790 """
791 return "{}-{}".format(self.os_release, self.os_version)
792
793 @property
795 """
796 Textual representation of name of this or release
797 """
798 return "{} {}".format(self.os_release, self.os_version)
799
800 @property
802 """
803 Textual representation of the operating system name
804 """
805 return "{0} {1}".format(self.os_release, self.os_version)
806
807 @property
812
813
814 -class CoprChroot(db.Model, helpers.Serializer):
815
816 """
817 Representation of Copr<->MockChroot relation
818 """
819
820 buildroot_pkgs = db.Column(db.Text)
821 repos = db.Column(db.Text, default="", server_default="", nullable=False)
822 mock_chroot_id = db.Column(
823 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
824 mock_chroot = db.relationship(
825 "MockChroot", backref=db.backref("copr_chroots"))
826 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
827 copr = db.relationship("Copr",
828 backref=db.backref(
829 "copr_chroots",
830 single_parent=True,
831 cascade="all,delete,delete-orphan"))
832
833 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
834 comps_name = db.Column(db.String(127), nullable=True)
835
836 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
837 module_md_name = db.Column(db.String(127), nullable=True)
838
840 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
841
843 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
844
845 @property
848
849 @property
851 return self.repos.split()
852
853 @property
857
858 @property
862
863 @property
869
870 @property
876
877 @property
880
881 @property
884
886 options = {"__columns_only__": [
887 "buildroot_pkgs", "repos", "comps_name", "copr_id"
888 ]}
889 d = super(CoprChroot, self).to_dict(options=options)
890 d["mock_chroot"] = self.mock_chroot.name
891 return d
892
895
896 """
897 Representation of Build<->MockChroot relation
898 """
899
900 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
901 primary_key=True)
902 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
903 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
904 primary_key=True)
905 build = db.relationship("Build", backref=db.backref("build_chroots"))
906 git_hash = db.Column(db.String(40))
907 status = db.Column(db.Integer, default=StatusEnum("importing"))
908
909 started_on = db.Column(db.Integer)
910 ended_on = db.Column(db.Integer, index=True)
911
912 last_deferred = db.Column(db.Integer)
913
914 @property
916 """
917 Textual representation of name of this chroot
918 """
919
920 return self.mock_chroot.name
921
922 @property
924 """
925 Return text representation of status of this build chroot
926 """
927
928 if self.status is not None:
929 return StatusEnum(self.status)
930
931 return "unknown"
932
933 @property
936
937 @property
940
941 @property
953
954 @property
960
961 @property
966
967 @property
988
990 return "<BuildChroot: {}>".format(self.to_dict())
991
992
993 -class LegalFlag(db.Model, helpers.Serializer):
994 id = db.Column(db.Integer, primary_key=True)
995
996 raise_message = db.Column(db.Text)
997
998 raised_on = db.Column(db.Integer)
999
1000 resolved_on = db.Column(db.Integer)
1001
1002
1003 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1004
1005 copr = db.relationship(
1006 "Copr", backref=db.backref("legal_flags", cascade="all"))
1007
1008 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1009 reporter = db.relationship("User",
1010 backref=db.backref("legal_flags_raised"),
1011 foreign_keys=[reporter_id],
1012 primaryjoin="LegalFlag.reporter_id==User.id")
1013
1014 resolver_id = db.Column(
1015 db.Integer, db.ForeignKey("user.id"), nullable=True)
1016 resolver = db.relationship("User",
1017 backref=db.backref("legal_flags_resolved"),
1018 foreign_keys=[resolver_id],
1019 primaryjoin="LegalFlag.resolver_id==User.id")
1020
1021
1022 -class Action(db.Model, helpers.Serializer):
1079
1080
1081 -class Krb5Login(db.Model, helpers.Serializer):
1082 """
1083 Represents additional user information for kerberos authentication.
1084 """
1085
1086 __tablename__ = "krb5_login"
1087
1088
1089 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1090
1091
1092 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1093
1094
1095 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1096
1097 user = db.relationship("User", backref=db.backref("krb5_logins"))
1098
1101 """
1102 Generic store for simple statistics.
1103 """
1104
1105 name = db.Column(db.String(127), primary_key=True)
1106 counter_type = db.Column(db.String(30))
1107
1108 counter = db.Column(db.Integer, default=0, server_default="0")
1109
1110
1111 -class Group(db.Model, helpers.Serializer):
1112 """
1113 Represents FAS groups and their aliases in Copr
1114 """
1115 id = db.Column(db.Integer, primary_key=True)
1116 name = db.Column(db.String(127))
1117
1118
1119 fas_name = db.Column(db.String(127))
1120
1121 @property
1123 return u"@{}".format(self.name)
1124
1127
1130
1131
1132 -class Module(db.Model, helpers.Serializer):
1133 id = db.Column(db.Integer, primary_key=True)
1134 name = db.Column(db.String(100), nullable=False)
1135 stream = db.Column(db.String(100), nullable=False)
1136 version = db.Column(db.Integer, nullable=False)
1137 summary = db.Column(db.String(100), nullable=False)
1138 description = db.Column(db.Text)
1139 created_on = db.Column(db.Integer, nullable=True)
1140
1141
1142
1143
1144
1145
1146
1147 yaml_b64 = db.Column(db.Text)
1148
1149
1150 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1151 copr = db.relationship("Copr", backref=db.backref("modules"))
1152
1153 @property
1155 return base64.b64decode(self.yaml_b64)
1156
1157 @property
1159 mmd = modulemd.ModuleMetadata()
1160 mmd.loads(self.yaml)
1161 return mmd
1162
1163 @property
1166
1167 @property
1170
1171 @property
1174
1176
1177
1178
1179 module_dir = "fedora-24-{}+{}-{}-{}".format(arch, self.name, self.stream, self.version)
1180 return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch])
1181