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 if app.config["COPR_DIST_GIT_LOGS_URL"]:
586 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
587 self.import_task_id.replace('/', '_'))
588 return None
589
590 @property
598
599 @property
604
605 @property
608
609 @property
611 mb_list = [chroot.started_on for chroot in
612 self.build_chroots if chroot.started_on]
613 if len(mb_list) > 0:
614 return min(mb_list)
615 else:
616 return None
617
618 @property
621
622 @property
624 if not self.build_chroots:
625 return None
626 if any(chroot.ended_on is None for chroot in self.build_chroots):
627 return None
628 return max(chroot.ended_on for chroot in self.build_chroots)
629
630 @property
632 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
633
634 @property
636 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
637
638 @property
641
642 @property
651
652 @property
654 return map(lambda chroot: chroot.status, self.build_chroots)
655
657 """
658 Get build chroots with states which present in `states` list
659 If states == None, function returns build_chroots
660 """
661 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
662 if statuses is not None:
663 statuses = set(statuses)
664 else:
665 return self.build_chroots
666
667 return [
668 chroot for chroot, status in chroot_states_map.items()
669 if status in statuses
670 ]
671
672 @property
674 return {b.name: b for b in self.build_chroots}
675
676 @property
683
684 @property
689
690 @property
693
694 @property
705
706 @property
708 """
709 Return text representation of status of this build
710 """
711
712 if self.status is not None:
713 return StatusEnum(self.status)
714
715 return "unknown"
716
717 @property
719 """
720 Find out if this build is cancelable.
721
722 Build is cancelabel only when it's pending (not started)
723 """
724
725 return self.status == StatusEnum("pending") or \
726 self.status == StatusEnum("importing") or \
727 self.status == StatusEnum("running")
728
729 @property
731 """
732 Find out if this build is repeatable.
733
734 Build is repeatable only if it's not pending, starting or running
735 """
736 return self.status not in [StatusEnum("pending"),
737 StatusEnum("starting"),
738 StatusEnum("running"),
739 StatusEnum("forked")]
740
741 @property
743 """
744 Find out if this build is in finished state.
745
746 Build is finished only if all its build_chroots are in finished state.
747 """
748 return all([(chroot.state in ["succeeded", "forked", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
749
750 @property
752 """
753 Find out if this build is persistent.
754
755 This property is inherited from the project.
756 """
757 return self.copr.persistent
758
759 @property
761 """
762 Extract source package name from source name or url
763 todo: obsolete
764 """
765 try:
766 src_rpm_name = self.pkgs.split("/")[-1]
767 except:
768 return None
769 if src_rpm_name.endswith(".src.rpm"):
770 return src_rpm_name[:-8]
771 else:
772 return src_rpm_name
773
774 @property
776 try:
777 return self.package.name
778 except:
779 return None
780
781 - def to_dict(self, options=None, with_chroot_states=False):
794
797 """
798 1:N mapping: branch -> chroots
799 """
800
801
802 name = db.Column(db.String(50), primary_key=True)
803
804
805 -class MockChroot(db.Model, helpers.Serializer):
806
807 """
808 Representation of mock chroot
809 """
810 __table_args__ = (
811 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
812 )
813
814 id = db.Column(db.Integer, primary_key=True)
815
816 os_release = db.Column(db.String(50), nullable=False)
817
818 os_version = db.Column(db.String(50), nullable=False)
819
820 arch = db.Column(db.String(50), nullable=False)
821 is_active = db.Column(db.Boolean, default=True)
822
823
824 distgit_branch_name = db.Column(db.String(50),
825 db.ForeignKey("dist_git_branch.name"),
826 nullable=False)
827
828 distgit_branch = db.relationship("DistGitBranch",
829 backref=db.backref("chroots"))
830
831 @property
833 """
834 Textual representation of name of this chroot
835 """
836 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
837
838 @property
840 """
841 Textual representation of name of this or release
842 """
843 return "{}-{}".format(self.os_release, self.os_version)
844
845 @property
847 """
848 Textual representation of name of this or release
849 """
850 return "{} {}".format(self.os_release, self.os_version)
851
852 @property
854 """
855 Textual representation of the operating system name
856 """
857 return "{0} {1}".format(self.os_release, self.os_version)
858
859 @property
864
865
866 -class CoprChroot(db.Model, helpers.Serializer):
867
868 """
869 Representation of Copr<->MockChroot relation
870 """
871
872 buildroot_pkgs = db.Column(db.Text)
873 repos = db.Column(db.Text, default="", server_default="", nullable=False)
874 mock_chroot_id = db.Column(
875 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
876 mock_chroot = db.relationship(
877 "MockChroot", backref=db.backref("copr_chroots"))
878 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
879 copr = db.relationship("Copr",
880 backref=db.backref(
881 "copr_chroots",
882 single_parent=True,
883 cascade="all,delete,delete-orphan"))
884
885 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
886 comps_name = db.Column(db.String(127), nullable=True)
887
888 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
889 module_md_name = db.Column(db.String(127), nullable=True)
890
892 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
893
895 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
896
897 @property
900
901 @property
903 return self.repos.split()
904
905 @property
909
910 @property
914
915 @property
921
922 @property
928
929 @property
932
933 @property
936
938 options = {"__columns_only__": [
939 "buildroot_pkgs", "repos", "comps_name", "copr_id"
940 ]}
941 d = super(CoprChroot, self).to_dict(options=options)
942 d["mock_chroot"] = self.mock_chroot.name
943 return d
944
947
948 """
949 Representation of Build<->MockChroot relation
950 """
951
952 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
953 primary_key=True)
954 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
955 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
956 primary_key=True)
957 build = db.relationship("Build", backref=db.backref("build_chroots"))
958 git_hash = db.Column(db.String(40))
959 status = db.Column(db.Integer, default=StatusEnum("importing"))
960
961 started_on = db.Column(db.Integer)
962 ended_on = db.Column(db.Integer, index=True)
963
964 last_deferred = db.Column(db.Integer)
965
966 build_requires = db.Column(db.Text)
967
968 @property
970 """
971 Textual representation of name of this chroot
972 """
973
974 return self.mock_chroot.name
975
976 @property
978 """
979 Return text representation of status of this build chroot
980 """
981
982 if self.status is not None:
983 return StatusEnum(self.status)
984
985 return "unknown"
986
987 @property
990
991 @property
1003
1004 @property
1006 return urljoin(app.config["BACKEND_BASE_URL"],
1007 os.path.join("results", self.result_dir, "")
1008 )
1009
1010 @property
1031
1033 return "<BuildChroot: {}>".format(self.to_dict())
1034
1035
1036 -class LegalFlag(db.Model, helpers.Serializer):
1037 id = db.Column(db.Integer, primary_key=True)
1038
1039 raise_message = db.Column(db.Text)
1040
1041 raised_on = db.Column(db.Integer)
1042
1043 resolved_on = db.Column(db.Integer)
1044
1045
1046 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1047
1048 copr = db.relationship(
1049 "Copr", backref=db.backref("legal_flags", cascade="all"))
1050
1051 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1052 reporter = db.relationship("User",
1053 backref=db.backref("legal_flags_raised"),
1054 foreign_keys=[reporter_id],
1055 primaryjoin="LegalFlag.reporter_id==User.id")
1056
1057 resolver_id = db.Column(
1058 db.Integer, db.ForeignKey("user.id"), nullable=True)
1059 resolver = db.relationship("User",
1060 backref=db.backref("legal_flags_resolved"),
1061 foreign_keys=[resolver_id],
1062 primaryjoin="LegalFlag.resolver_id==User.id")
1063
1064
1065 -class Action(db.Model, helpers.Serializer):
1122
1123
1124 -class Krb5Login(db.Model, helpers.Serializer):
1125 """
1126 Represents additional user information for kerberos authentication.
1127 """
1128
1129 __tablename__ = "krb5_login"
1130
1131
1132 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1133
1134
1135 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1136
1137
1138 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1139
1140 user = db.relationship("User", backref=db.backref("krb5_logins"))
1141
1144 """
1145 Generic store for simple statistics.
1146 """
1147
1148 name = db.Column(db.String(127), primary_key=True)
1149 counter_type = db.Column(db.String(30))
1150
1151 counter = db.Column(db.Integer, default=0, server_default="0")
1152
1153
1154 -class Group(db.Model, helpers.Serializer):
1155 """
1156 Represents FAS groups and their aliases in Copr
1157 """
1158 id = db.Column(db.Integer, primary_key=True)
1159 name = db.Column(db.String(127))
1160
1161
1162 fas_name = db.Column(db.String(127))
1163
1164 @property
1166 return u"@{}".format(self.name)
1167
1170
1173
1174
1175 -class Batch(db.Model):
1176 id = db.Column(db.Integer, primary_key=True)
1177
1178
1179 -class Module(db.Model, helpers.Serializer):
1180 id = db.Column(db.Integer, primary_key=True)
1181 name = db.Column(db.String(100), nullable=False)
1182 stream = db.Column(db.String(100), nullable=False)
1183 version = db.Column(db.Integer, nullable=False)
1184 summary = db.Column(db.String(100), nullable=False)
1185 description = db.Column(db.Text)
1186 created_on = db.Column(db.Integer, nullable=True)
1187
1188
1189
1190
1191
1192
1193
1194 yaml_b64 = db.Column(db.Text)
1195
1196
1197 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1198 copr = db.relationship("Copr", backref=db.backref("modules"))
1199
1200 @property
1202 return base64.b64decode(self.yaml_b64)
1203
1204 @property
1206 mmd = modulemd.ModuleMetadata()
1207 mmd.loads(self.yaml)
1208 return mmd
1209
1210 @property
1213
1214 @property
1217
1218 @property
1221
1222 @property
1230
1232
1233
1234
1235 module_dir = "fedora-24-{}+{}-{}-{}".format(arch, self.name, self.stream, self.version)
1236 return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch])
1237