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
232 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
233
234 __mapper_args__ = {
235 "order_by": created_on.desc()
236 }
237
238 @property
240 """
241 Return True if copr belongs to a group
242 """
243 return self.group_id is not None
244
245 @property
251
252 @property
258
259 @property
261 """
262 Return repos of this copr as a list of strings
263 """
264 return self.repos.split()
265
266 @property
273
274 @property
276 """
277 :rtype: list of CoprChroot
278 """
279 return [c for c in self.copr_chroots if c.is_active]
280
281 @property
283 """
284 Return list of active mock_chroots of this copr
285 """
286
287 return sorted(self.active_chroots, key=lambda ch: ch.name)
288
289 @property
291 """
292 Return list of active mock_chroots of this copr
293 """
294
295 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
296 output = []
297 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
298 output.append((os, [ch[1] for ch in chs]))
299
300 return output
301
302 @property
304 """
305 Return number of builds in this copr
306 """
307
308 return len(self.builds)
309
310 @property
314
315 @disable_createrepo.setter
319
320 @property
331
337
338 @property
341
342 @property
345
346 @property
351
352 @property
358
359 @property
361 return "/".join([self.repo_url, "modules"])
362
363 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
364 result = {}
365 for key in ["id", "name", "description", "instructions"]:
366 result[key] = str(copy.copy(getattr(self, key)))
367 result["owner"] = self.owner_name
368 return result
369
370 @property
375
378
381
382 """
383 Association class for Copr<->Permission relation
384 """
385
386
387
388 copr_builder = db.Column(db.SmallInteger, default=0)
389
390 copr_admin = db.Column(db.SmallInteger, default=0)
391
392
393 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
394 user = db.relationship("User", backref=db.backref("copr_permissions"))
395 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
396 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
397
398
399 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
400 """
401 Represents a single package in a project.
402 """
403 __table_args__ = (
404 db.UniqueConstraint('copr_id', 'name', name='packages_copr_pkgname'),
405 )
406
407 id = db.Column(db.Integer, primary_key=True)
408 name = db.Column(db.String(100), nullable=False)
409
410 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
411
412 source_json = db.Column(db.Text)
413
414 webhook_rebuild = db.Column(db.Boolean, default=False)
415
416 enable_net = db.Column(db.Boolean, default=False,
417 server_default="0", nullable=False)
418
419
420
421
422
423
424
425 old_status = db.Column(db.Integer)
426
427 builds = db.relationship("Build", order_by="Build.id")
428
429
430 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
431 copr = db.relationship("Copr", backref=db.backref("packages"))
432
433 @property
436
437 @property
442
443 @property
446
447 @property
449 """
450 Package's source type (and source_json) is being derived from its first build, which works except
451 for "srpm_link" and "srpm_upload" cases. Consider these being equivalent to source_type being unset.
452 """
453 return self.source_type and self.source_type_text != "srpm_link" and self.source_type_text != "srpm_upload"
454
455 @property
460
466
467 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
468 package_dict = super(Package, self).to_dict()
469 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
470
471 if with_latest_build:
472 build = self.last_build(successful=False)
473 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
474 if with_latest_succeeded_build:
475 build = self.last_build(successful=True)
476 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
477 if with_all_builds:
478 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
479
480 return package_dict
481
484
485
486 -class Build(db.Model, helpers.Serializer):
487
488 """
489 Representation of one build in one copr
490 """
491 __table_args__ = (db.Index('build_canceled', "canceled"), )
492
493 id = db.Column(db.Integer, primary_key=True)
494
495 pkgs = db.Column(db.Text)
496
497 built_packages = db.Column(db.Text)
498
499 pkg_version = db.Column(db.Text)
500
501 canceled = db.Column(db.Boolean, default=False)
502
503 repos = db.Column(db.Text)
504
505
506 submitted_on = db.Column(db.Integer, nullable=False)
507
508 results = db.Column(db.Text)
509
510 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
511
512 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
513
514 enable_net = db.Column(db.Boolean, default=False,
515 server_default="0", nullable=False)
516
517 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
518
519 source_json = db.Column(db.Text)
520
521 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
522
523 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
524
525
526 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
527 user = db.relationship("User", backref=db.backref("builds"))
528 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
529 copr = db.relationship("Copr", backref=db.backref("builds"))
530 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
531 package = db.relationship("Package")
532
533 chroots = association_proxy("build_chroots", "mock_chroot")
534
535 @property
538
539 @property
542
543 @property
546
547 @property
548 - def fail_type_text(self):
550
551 @property
553
554
555 return self.build_chroots[0].git_hash is None
556
557 @property
559 if self.repos is None:
560 return list()
561 else:
562 return self.repos.split()
563
564 @property
567
568 @property
570 if app.config["COPR_DIST_GIT_LOGS_URL"]:
571 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
572 self.import_task_id.replace('/', '_'))
573 return None
574
575 @property
583
584 @property
589
590 @property
593
594 @property
596 mb_list = [chroot.started_on for chroot in
597 self.build_chroots if chroot.started_on]
598 if len(mb_list) > 0:
599 return min(mb_list)
600 else:
601 return None
602
603 @property
606
607 @property
609 if not self.build_chroots:
610 return None
611 if any(chroot.ended_on is None for chroot in self.build_chroots):
612 return None
613 return max(chroot.ended_on for chroot in self.build_chroots)
614
615 @property
617 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
618
619 @property
621 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
622
623 @property
626
627 @property
636
637 @property
639 return map(lambda chroot: chroot.status, self.build_chroots)
640
642 """
643 Get build chroots with states which present in `states` list
644 If states == None, function returns build_chroots
645 """
646 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
647 if statuses is not None:
648 statuses = set(statuses)
649 else:
650 return self.build_chroots
651
652 return [
653 chroot for chroot, status in chroot_states_map.items()
654 if status in statuses
655 ]
656
657 @property
659 return {b.name: b for b in self.build_chroots}
660
661 @property
668
669 @property
674
675 @property
678
679 @property
690
691 @property
693 """
694 Return text representation of status of this build
695 """
696
697 if self.status is not None:
698 return StatusEnum(self.status)
699
700 return "unknown"
701
702 @property
704 """
705 Find out if this build is cancelable.
706
707 Build is cancelabel only when it's pending (not started)
708 """
709
710 return self.status == StatusEnum("pending") or \
711 self.status == StatusEnum("importing") or \
712 self.status == StatusEnum("running")
713
714 @property
716 """
717 Find out if this build is repeatable.
718
719 Build is repeatable only if it's not pending, starting or running
720 """
721 return self.status not in [StatusEnum("pending"),
722 StatusEnum("starting"),
723 StatusEnum("running"),
724 StatusEnum("forked")]
725
726 @property
728 """
729 Find out if this build is in finished state.
730
731 Build is finished only if all its build_chroots are in finished state.
732 """
733 return all([(chroot.state in ["succeeded", "forked", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
734
735 @property
737 """
738 Find out if this build is persistent.
739
740 This property is inherited from the project.
741 """
742 return self.copr.persistent
743
744 @property
746 """
747 Extract source package name from source name or url
748 todo: obsolete
749 """
750 try:
751 src_rpm_name = self.pkgs.split("/")[-1]
752 except:
753 return None
754 if src_rpm_name.endswith(".src.rpm"):
755 return src_rpm_name[:-8]
756 else:
757 return src_rpm_name
758
759 @property
761 try:
762 return self.package.name
763 except:
764 return None
765
766 - def to_dict(self, options=None, with_chroot_states=False):
779
782 """
783 1:N mapping: branch -> chroots
784 """
785
786
787 name = db.Column(db.String(50), primary_key=True)
788
789
790 -class MockChroot(db.Model, helpers.Serializer):
791
792 """
793 Representation of mock chroot
794 """
795 __table_args__ = (
796 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
797 )
798
799 id = db.Column(db.Integer, primary_key=True)
800
801 os_release = db.Column(db.String(50), nullable=False)
802
803 os_version = db.Column(db.String(50), nullable=False)
804
805 arch = db.Column(db.String(50), nullable=False)
806 is_active = db.Column(db.Boolean, default=True)
807
808
809 distgit_branch_name = db.Column(db.String(50),
810 db.ForeignKey("dist_git_branch.name"),
811 nullable=False)
812
813 distgit_branch = db.relationship("DistGitBranch",
814 backref=db.backref("chroots"))
815
816 @property
818 """
819 Textual representation of name of this chroot
820 """
821 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
822
823 @property
825 """
826 Textual representation of name of this or release
827 """
828 return "{}-{}".format(self.os_release, self.os_version)
829
830 @property
832 """
833 Textual representation of name of this or release
834 """
835 return "{} {}".format(self.os_release, self.os_version)
836
837 @property
839 """
840 Textual representation of the operating system name
841 """
842 return "{0} {1}".format(self.os_release, self.os_version)
843
844 @property
849
850
851 -class CoprChroot(db.Model, helpers.Serializer):
852
853 """
854 Representation of Copr<->MockChroot relation
855 """
856
857 buildroot_pkgs = db.Column(db.Text)
858 repos = db.Column(db.Text, default="", server_default="", nullable=False)
859 mock_chroot_id = db.Column(
860 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
861 mock_chroot = db.relationship(
862 "MockChroot", backref=db.backref("copr_chroots"))
863 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
864 copr = db.relationship("Copr",
865 backref=db.backref(
866 "copr_chroots",
867 single_parent=True,
868 cascade="all,delete,delete-orphan"))
869
870 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
871 comps_name = db.Column(db.String(127), nullable=True)
872
873 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
874 module_md_name = db.Column(db.String(127), nullable=True)
875
877 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
878
880 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
881
882 @property
885
886 @property
888 return self.repos.split()
889
890 @property
894
895 @property
899
900 @property
906
907 @property
913
914 @property
917
918 @property
921
923 options = {"__columns_only__": [
924 "buildroot_pkgs", "repos", "comps_name", "copr_id"
925 ]}
926 d = super(CoprChroot, self).to_dict(options=options)
927 d["mock_chroot"] = self.mock_chroot.name
928 return d
929
932
933 """
934 Representation of Build<->MockChroot relation
935 """
936
937 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
938 primary_key=True)
939 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
940 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
941 primary_key=True)
942 build = db.relationship("Build", backref=db.backref("build_chroots"))
943 git_hash = db.Column(db.String(40))
944 status = db.Column(db.Integer, default=StatusEnum("importing"))
945
946 started_on = db.Column(db.Integer)
947 ended_on = db.Column(db.Integer, index=True)
948
949 last_deferred = db.Column(db.Integer)
950
951 @property
953 """
954 Textual representation of name of this chroot
955 """
956
957 return self.mock_chroot.name
958
959 @property
961 """
962 Return text representation of status of this build chroot
963 """
964
965 if self.status is not None:
966 return StatusEnum(self.status)
967
968 return "unknown"
969
970 @property
973
974 @property
986
987 @property
992
993 @property
1014
1016 return "<BuildChroot: {}>".format(self.to_dict())
1017
1018
1019 -class LegalFlag(db.Model, helpers.Serializer):
1020 id = db.Column(db.Integer, primary_key=True)
1021
1022 raise_message = db.Column(db.Text)
1023
1024 raised_on = db.Column(db.Integer)
1025
1026 resolved_on = db.Column(db.Integer)
1027
1028
1029 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1030
1031 copr = db.relationship(
1032 "Copr", backref=db.backref("legal_flags", cascade="all"))
1033
1034 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1035 reporter = db.relationship("User",
1036 backref=db.backref("legal_flags_raised"),
1037 foreign_keys=[reporter_id],
1038 primaryjoin="LegalFlag.reporter_id==User.id")
1039
1040 resolver_id = db.Column(
1041 db.Integer, db.ForeignKey("user.id"), nullable=True)
1042 resolver = db.relationship("User",
1043 backref=db.backref("legal_flags_resolved"),
1044 foreign_keys=[resolver_id],
1045 primaryjoin="LegalFlag.resolver_id==User.id")
1046
1047
1048 -class Action(db.Model, helpers.Serializer):
1105
1106
1107 -class Krb5Login(db.Model, helpers.Serializer):
1108 """
1109 Represents additional user information for kerberos authentication.
1110 """
1111
1112 __tablename__ = "krb5_login"
1113
1114
1115 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1116
1117
1118 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1119
1120
1121 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1122
1123 user = db.relationship("User", backref=db.backref("krb5_logins"))
1124
1127 """
1128 Generic store for simple statistics.
1129 """
1130
1131 name = db.Column(db.String(127), primary_key=True)
1132 counter_type = db.Column(db.String(30))
1133
1134 counter = db.Column(db.Integer, default=0, server_default="0")
1135
1136
1137 -class Group(db.Model, helpers.Serializer):
1138 """
1139 Represents FAS groups and their aliases in Copr
1140 """
1141 id = db.Column(db.Integer, primary_key=True)
1142 name = db.Column(db.String(127))
1143
1144
1145 fas_name = db.Column(db.String(127))
1146
1147 @property
1149 return u"@{}".format(self.name)
1150
1153
1156
1157
1158 -class Module(db.Model, helpers.Serializer):
1159 id = db.Column(db.Integer, primary_key=True)
1160 name = db.Column(db.String(100), nullable=False)
1161 stream = db.Column(db.String(100), nullable=False)
1162 version = db.Column(db.Integer, nullable=False)
1163 summary = db.Column(db.String(100), nullable=False)
1164 description = db.Column(db.Text)
1165 created_on = db.Column(db.Integer, nullable=True)
1166
1167
1168
1169
1170
1171
1172
1173 yaml_b64 = db.Column(db.Text)
1174
1175
1176 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1177 copr = db.relationship("Copr", backref=db.backref("modules"))
1178
1179 @property
1181 return base64.b64decode(self.yaml_b64)
1182
1183 @property
1185 mmd = modulemd.ModuleMetadata()
1186 mmd.loads(self.yaml)
1187 return mmd
1188
1189 @property
1192
1193 @property
1196
1197 @property
1200
1201 @property
1209
1211
1212
1213
1214 module_dir = "fedora-24-{}+{}-{}-{}".format(arch, self.name, self.stream, self.version)
1215 return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch])
1216