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 six.moves.urllib.parse import urljoin
12 from libravatar import libravatar_url
13 import zlib
14
15 from coprs import constants
16 from coprs import db
17 from coprs import helpers
18 from coprs import app
19
20 import itertools
21 import operator
22 from coprs.helpers import BuildSourceEnum, StatusEnum, ActionTypeEnum, JSONEncodedDict
28
29
30 -class User(db.Model, helpers.Serializer):
31
32 """
33 Represents user of the copr frontend
34 """
35
36
37 id = db.Column(db.Integer, primary_key=True)
38
39
40 username = db.Column(db.String(100), nullable=False, unique=True)
41
42
43 mail = db.Column(db.String(150), nullable=False)
44
45
46 timezone = db.Column(db.String(50), nullable=True)
47
48
49
50 proven = db.Column(db.Boolean, default=False)
51
52
53 admin = db.Column(db.Boolean, default=False)
54
55
56 proxy = db.Column(db.Boolean, default=False)
57
58
59 api_login = db.Column(db.String(40), nullable=False, default="abc")
60 api_token = db.Column(db.String(40), nullable=False, default="abc")
61 api_token_expiration = db.Column(
62 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
63
64
65 openid_groups = db.Column(JSONEncodedDict)
66
67 @property
69 """
70 Return the short username of the user, e.g. bkabrda
71 """
72
73 return self.username
74
76 """
77 Get permissions of this user for the given copr.
78 Caches the permission during one request,
79 so use this if you access them multiple times
80 """
81
82 if not hasattr(self, "_permissions_for_copr"):
83 self._permissions_for_copr = {}
84 if copr.name not in self._permissions_for_copr:
85 self._permissions_for_copr[copr.name] = (
86 CoprPermission.query
87 .filter_by(user=self)
88 .filter_by(copr=copr)
89 .first()
90 )
91 return self._permissions_for_copr[copr.name]
92
112
113 @property
119
120 @property
123
125 """
126 :type group: Group
127 """
128 if group.fas_name in self.user_teams:
129 return True
130 else:
131 return False
132
151
152 @property
154
155 return ["id", "name"]
156
157 @property
159 """
160 Get number of coprs for this user.
161 """
162
163 return (Copr.query.filter_by(user=self).
164 filter_by(deleted=False).
165 filter_by(group_id=None).
166 count())
167
168 @property
170 """
171 Return url to libravatar image.
172 """
173
174 try:
175 return libravatar_url(email=self.mail, https=True)
176 except IOError:
177 return ""
178
179
180 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
181
182 """
183 Represents a single copr (private repo with builds, mock chroots, etc.).
184 """
185
186 id = db.Column(db.Integer, primary_key=True)
187
188 name = db.Column(db.String(100), nullable=False)
189 homepage = db.Column(db.Text)
190 contact = db.Column(db.Text)
191
192
193 repos = db.Column(db.Text)
194
195 created_on = db.Column(db.Integer)
196
197 description = db.Column(db.Text)
198 instructions = db.Column(db.Text)
199 deleted = db.Column(db.Boolean, default=False)
200 playground = db.Column(db.Boolean, default=False)
201
202
203 auto_createrepo = db.Column(db.Boolean, default=True)
204
205
206 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
207 user = db.relationship("User", backref=db.backref("coprs"))
208 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
209 group = db.relationship("Group", backref=db.backref("groups"))
210 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
211 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
212 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
213
214
215 webhook_secret = db.Column(db.String(100))
216
217
218 build_enable_net = db.Column(db.Boolean, default=True,
219 server_default="1", nullable=False)
220
221 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
222
223
224 latest_indexed_data_update = db.Column(db.Integer)
225
226
227 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
228
229
230 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
231
232
233 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
234
235
236 follow_fedora_branching = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
237
238 __mapper_args__ = {
239 "order_by": created_on.desc()
240 }
241
242 @property
244 """
245 Return True if copr belongs to a group
246 """
247 return self.group_id is not None
248
249 @property
255
256 @property
262
263 @property
265 """
266 Return repos of this copr as a list of strings
267 """
268 return self.repos.split()
269
270 @property
277
278 @property
280 """
281 :rtype: list of CoprChroot
282 """
283 return [c for c in self.copr_chroots if c.is_active]
284
285 @property
287 """
288 Return list of active mock_chroots of this copr
289 """
290
291 return sorted(self.active_chroots, key=lambda ch: ch.name)
292
293 @property
295 """
296 Return list of active mock_chroots of this copr
297 """
298
299 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
300 output = []
301 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
302 output.append((os, [ch[1] for ch in chs]))
303
304 return output
305
306 @property
308 """
309 Return number of builds in this copr
310 """
311
312 return len(self.builds)
313
314 @property
318
319 @disable_createrepo.setter
323
324 @property
335
341
342 @property
345
346 @property
349
350 @property
355
356 @property
362
363 @property
365 return "/".join([self.repo_url, "modules"])
366
367 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
368 result = {}
369 for key in ["id", "name", "description", "instructions"]:
370 result[key] = str(copy.copy(getattr(self, key)))
371 result["owner"] = self.owner_name
372 return result
373
374 @property
379
382
385
386 """
387 Association class for Copr<->Permission relation
388 """
389
390
391
392 copr_builder = db.Column(db.SmallInteger, default=0)
393
394 copr_admin = db.Column(db.SmallInteger, default=0)
395
396
397 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
398 user = db.relationship("User", backref=db.backref("copr_permissions"))
399 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
400 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
401
402
403 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
404 """
405 Represents a single package in a project.
406 """
407 __table_args__ = (
408 db.UniqueConstraint('copr_id', 'name', name='packages_copr_pkgname'),
409 )
410
411 id = db.Column(db.Integer, primary_key=True)
412 name = db.Column(db.String(100), nullable=False)
413
414 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
415
416 source_json = db.Column(db.Text)
417
418 webhook_rebuild = db.Column(db.Boolean, default=False)
419
420 enable_net = db.Column(db.Boolean, default=False,
421 server_default="0", nullable=False)
422
423
424
425
426
427
428
429 old_status = db.Column(db.Integer)
430
431 builds = db.relationship("Build", order_by="Build.id")
432
433
434 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
435 copr = db.relationship("Copr", backref=db.backref("packages"))
436
437 @property
440
441 @property
446
447 @property
450
451 @property
453 """
454 Package's source type (and source_json) is being derived from its first build, which works except
455 for "link" and "upload" cases. Consider these being equivalent to source_type being unset.
456 """
457 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
458
459 @property
464
465 @property
471
477
478 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
479 package_dict = super(Package, self).to_dict()
480 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
481
482 if with_latest_build:
483 build = self.last_build(successful=False)
484 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
485 if with_latest_succeeded_build:
486 build = self.last_build(successful=True)
487 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
488 if with_all_builds:
489 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
490
491 return package_dict
492
495
496
497 -class Build(db.Model, helpers.Serializer):
498 """
499 Representation of one build in one copr
500 """
501 __table_args__ = (db.Index('build_canceled', "canceled"), )
502
503 id = db.Column(db.Integer, primary_key=True)
504
505 pkgs = db.Column(db.Text)
506
507 built_packages = db.Column(db.Text)
508
509 pkg_version = db.Column(db.Text)
510
511 canceled = db.Column(db.Boolean, default=False)
512
513 repos = db.Column(db.Text)
514
515
516 submitted_on = db.Column(db.Integer, nullable=False)
517
518 results = db.Column(db.Text)
519
520 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
521
522 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
523
524 enable_net = db.Column(db.Boolean, default=False,
525 server_default="0", nullable=False)
526
527 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
528
529 source_json = db.Column(db.Text)
530
531 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
532
533 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
534
535 srpm_url = db.Column(db.Text)
536
537
538 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
539 user = db.relationship("User", backref=db.backref("builds"))
540 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
541 copr = db.relationship("Copr", backref=db.backref("builds"))
542 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
543 package = db.relationship("Package")
544
545 chroots = association_proxy("build_chroots", "mock_chroot")
546
547 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
548 batch = db.relationship("Batch", backref=db.backref("builds"))
549
550 @property
553
554 @property
557
558 @property
561
562 @property
563 - def fail_type_text(self):
565
566 @property
568
569
570 return self.build_chroots[0].git_hash is None
571
572 @property
574 if self.repos is None:
575 return list()
576 else:
577 return self.repos.split()
578
579 @property
582
583 @property
585 return "{:08d}".format(self.id)
586
587 @property
595
596 @property
598 if app.config["COPR_DIST_GIT_LOGS_URL"]:
599 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
600 self.import_task_id.replace('/', '_'))
601 return None
602
603 @property
609
610 @property
617
618 @property
623
624 @property
627
628 @property
630 mb_list = [chroot.started_on for chroot in
631 self.build_chroots if chroot.started_on]
632 if len(mb_list) > 0:
633 return min(mb_list)
634 else:
635 return None
636
637 @property
640
641 @property
643 if not self.build_chroots:
644 return None
645 if any(chroot.ended_on is None for chroot in self.build_chroots):
646 return None
647 return max(chroot.ended_on for chroot in self.build_chroots)
648
649 @property
651 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
652
653 @property
655 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
656
657 @property
660
661 @property
670
671 @property
673 return map(lambda chroot: chroot.status, self.build_chroots)
674
676 """
677 Get build chroots with states which present in `states` list
678 If states == None, function returns build_chroots
679 """
680 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
681 if statuses is not None:
682 statuses = set(statuses)
683 else:
684 return self.build_chroots
685
686 return [
687 chroot for chroot, status in chroot_states_map.items()
688 if status in statuses
689 ]
690
691 @property
693 return {b.name: b for b in self.build_chroots}
694
695 @property
702
703 @property
708
709 @property
712
713 @property
724
725 @property
727 """
728 Return text representation of status of this build
729 """
730
731 if self.status is not None:
732 return StatusEnum(self.status)
733
734 return "unknown"
735
736 @property
738 """
739 Find out if this build is cancelable.
740
741 Build is cancelabel only when it's pending (not started)
742 """
743
744 return self.status == StatusEnum("pending") or \
745 self.status == StatusEnum("importing") or \
746 self.status == StatusEnum("running")
747
748 @property
750 """
751 Find out if this build is repeatable.
752
753 Build is repeatable only if it's not pending, starting or running
754 """
755 return self.status not in [StatusEnum("pending"),
756 StatusEnum("starting"),
757 StatusEnum("running"),
758 StatusEnum("forked")]
759
760 @property
762 """
763 Find out if this build is in finished state.
764
765 Build is finished only if all its build_chroots are in finished state.
766 """
767 return all([(chroot.state in ["succeeded", "forked", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
768
769 @property
771 """
772 Find out if this build is persistent.
773
774 This property is inherited from the project.
775 """
776 return self.copr.persistent
777
778 @property
780 """
781 Extract source package name from source name or url
782 todo: obsolete
783 """
784 try:
785 src_rpm_name = self.pkgs.split("/")[-1]
786 except:
787 return None
788 if src_rpm_name.endswith(".src.rpm"):
789 return src_rpm_name[:-8]
790 else:
791 return src_rpm_name
792
793 @property
795 try:
796 return self.package.name
797 except:
798 return None
799
800 - def to_dict(self, options=None, with_chroot_states=False):
813
816 """
817 1:N mapping: branch -> chroots
818 """
819
820
821 name = db.Column(db.String(50), primary_key=True)
822
823
824 -class MockChroot(db.Model, helpers.Serializer):
825
826 """
827 Representation of mock chroot
828 """
829 __table_args__ = (
830 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
831 )
832
833 id = db.Column(db.Integer, primary_key=True)
834
835 os_release = db.Column(db.String(50), nullable=False)
836
837 os_version = db.Column(db.String(50), nullable=False)
838
839 arch = db.Column(db.String(50), nullable=False)
840 is_active = db.Column(db.Boolean, default=True)
841
842
843 distgit_branch_name = db.Column(db.String(50),
844 db.ForeignKey("dist_git_branch.name"),
845 nullable=False)
846
847 distgit_branch = db.relationship("DistGitBranch",
848 backref=db.backref("chroots"))
849
850 @property
852 """
853 Textual representation of name of this chroot
854 """
855 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
856
857 @property
859 """
860 Textual representation of name of this or release
861 """
862 return "{}-{}".format(self.os_release, self.os_version)
863
864 @property
866 """
867 Textual representation of name of this or release
868 """
869 return "{} {}".format(self.os_release, self.os_version)
870
871 @property
873 """
874 Textual representation of the operating system name
875 """
876 return "{0} {1}".format(self.os_release, self.os_version)
877
878 @property
883
884
885 -class CoprChroot(db.Model, helpers.Serializer):
886
887 """
888 Representation of Copr<->MockChroot relation
889 """
890
891 buildroot_pkgs = db.Column(db.Text)
892 repos = db.Column(db.Text, default="", server_default="", nullable=False)
893 mock_chroot_id = db.Column(
894 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
895 mock_chroot = db.relationship(
896 "MockChroot", backref=db.backref("copr_chroots"))
897 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
898 copr = db.relationship("Copr",
899 backref=db.backref(
900 "copr_chroots",
901 single_parent=True,
902 cascade="all,delete,delete-orphan"))
903
904 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
905 comps_name = db.Column(db.String(127), nullable=True)
906
907 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
908 module_md_name = db.Column(db.String(127), nullable=True)
909
911 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
912
914 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
915
916 @property
919
920 @property
922 return self.repos.split()
923
924 @property
928
929 @property
933
934 @property
940
941 @property
947
948 @property
951
952 @property
955
957 options = {"__columns_only__": [
958 "buildroot_pkgs", "repos", "comps_name", "copr_id"
959 ]}
960 d = super(CoprChroot, self).to_dict(options=options)
961 d["mock_chroot"] = self.mock_chroot.name
962 return d
963
966
967 """
968 Representation of Build<->MockChroot relation
969 """
970
971 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
972 primary_key=True)
973 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
974 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
975 primary_key=True)
976 build = db.relationship("Build", backref=db.backref("build_chroots"))
977 git_hash = db.Column(db.String(40))
978 status = db.Column(db.Integer, default=StatusEnum("importing"))
979
980 started_on = db.Column(db.Integer)
981 ended_on = db.Column(db.Integer, index=True)
982
983 last_deferred = db.Column(db.Integer)
984
985 build_requires = db.Column(db.Text)
986
987 @property
989 """
990 Textual representation of name of this chroot
991 """
992
993 return self.mock_chroot.name
994
995 @property
997 """
998 Return text representation of status of this build chroot
999 """
1000
1001 if self.status is not None:
1002 return StatusEnum(self.status)
1003
1004 return "unknown"
1005
1006 @property
1009
1010 @property
1022
1023 @property
1025 return urljoin(app.config["BACKEND_BASE_URL"],
1026 os.path.join("results", self.result_dir, "")
1027 )
1028
1029 @property
1050
1052 return "<BuildChroot: {}>".format(self.to_dict())
1053
1054
1055 -class LegalFlag(db.Model, helpers.Serializer):
1056 id = db.Column(db.Integer, primary_key=True)
1057
1058 raise_message = db.Column(db.Text)
1059
1060 raised_on = db.Column(db.Integer)
1061
1062 resolved_on = db.Column(db.Integer)
1063
1064
1065 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1066
1067 copr = db.relationship(
1068 "Copr", backref=db.backref("legal_flags", cascade="all"))
1069
1070 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1071 reporter = db.relationship("User",
1072 backref=db.backref("legal_flags_raised"),
1073 foreign_keys=[reporter_id],
1074 primaryjoin="LegalFlag.reporter_id==User.id")
1075
1076 resolver_id = db.Column(
1077 db.Integer, db.ForeignKey("user.id"), nullable=True)
1078 resolver = db.relationship("User",
1079 backref=db.backref("legal_flags_resolved"),
1080 foreign_keys=[resolver_id],
1081 primaryjoin="LegalFlag.resolver_id==User.id")
1082
1083
1084 -class Action(db.Model, helpers.Serializer):
1141
1142
1143 -class Krb5Login(db.Model, helpers.Serializer):
1144 """
1145 Represents additional user information for kerberos authentication.
1146 """
1147
1148 __tablename__ = "krb5_login"
1149
1150
1151 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1152
1153
1154 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1155
1156
1157 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1158
1159 user = db.relationship("User", backref=db.backref("krb5_logins"))
1160
1163 """
1164 Generic store for simple statistics.
1165 """
1166
1167 name = db.Column(db.String(127), primary_key=True)
1168 counter_type = db.Column(db.String(30))
1169
1170 counter = db.Column(db.Integer, default=0, server_default="0")
1171
1172
1173 -class Group(db.Model, helpers.Serializer):
1174 """
1175 Represents FAS groups and their aliases in Copr
1176 """
1177 id = db.Column(db.Integer, primary_key=True)
1178 name = db.Column(db.String(127))
1179
1180
1181 fas_name = db.Column(db.String(127))
1182
1183 @property
1185 return u"@{}".format(self.name)
1186
1189
1192
1193
1194 -class Batch(db.Model):
1195 id = db.Column(db.Integer, primary_key=True)
1196
1197
1198 -class Module(db.Model, helpers.Serializer):
1199 id = db.Column(db.Integer, primary_key=True)
1200 name = db.Column(db.String(100), nullable=False)
1201 stream = db.Column(db.String(100), nullable=False)
1202 version = db.Column(db.Integer, nullable=False)
1203 summary = db.Column(db.String(100), nullable=False)
1204 description = db.Column(db.Text)
1205 created_on = db.Column(db.Integer, nullable=True)
1206
1207
1208
1209
1210
1211
1212
1213 yaml_b64 = db.Column(db.Text)
1214
1215
1216 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1217 copr = db.relationship("Copr", backref=db.backref("modules"))
1218
1219 @property
1221 return base64.b64decode(self.yaml_b64)
1222
1223 @property
1225 mmd = modulemd.ModuleMetadata()
1226 mmd.loads(self.yaml)
1227 return mmd
1228
1229 @property
1232
1233 @property
1236
1237 @property
1240
1241 @property
1249
1251
1252
1253
1254 module_dir = "fedora-24-{}+{}-{}-{}".format(arch, self.name, self.stream, self.version)
1255 return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch])
1256