1 import copy
2 import datetime
3 import os
4 import json
5 import base64
6 import uuid
7 from fnmatch import fnmatch
8
9 from sqlalchemy import outerjoin
10 from sqlalchemy.ext.associationproxy import association_proxy
11 from sqlalchemy.orm import column_property, validates
12 from sqlalchemy.event import listens_for
13 from six.moves.urllib.parse import urljoin
14 from libravatar import libravatar_url
15 import zlib
16
17 from flask import url_for
18
19 from copr_common.enums import ActionTypeEnum, BackendResultEnum, FailTypeEnum, ModuleStatusEnum, StatusEnum
20 from coprs import constants
21 from coprs import db
22 from coprs import helpers
23 from coprs import app
24
25 import itertools
26 import operator
27 from coprs.helpers import JSONEncodedDict
28
29 import gi
30 gi.require_version('Modulemd', '1.0')
31 from gi.repository import Modulemd
37
62
65 """
66 Records all the private information for a user.
67 """
68
69 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True,
70 nullable=False)
71
72
73 mail = db.Column(db.String(150), nullable=False)
74
75
76 timezone = db.Column(db.String(50), nullable=True)
77
78
79 api_login = db.Column(db.String(40), nullable=False, default="abc")
80 api_token = db.Column(db.String(40), nullable=False, default="abc")
81 api_token_expiration = db.Column(
82 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
83
84
85 -class User(db.Model, helpers.Serializer):
86 __table__ = outerjoin(_UserPublic.__table__, _UserPrivate.__table__)
87 id = column_property(_UserPublic.__table__.c.id, _UserPrivate.__table__.c.user_id)
88
89 @property
91 """
92 Return the short username of the user, e.g. bkabrda
93 """
94
95 return self.username
96
98 """
99 Get permissions of this user for the given copr.
100 Caches the permission during one request,
101 so use this if you access them multiple times
102 """
103
104 if not hasattr(self, "_permissions_for_copr"):
105 self._permissions_for_copr = {}
106 if copr.name not in self._permissions_for_copr:
107 self._permissions_for_copr[copr.name] = (
108 CoprPermission.query
109 .filter_by(user=self)
110 .filter_by(copr=copr)
111 .first()
112 )
113 return self._permissions_for_copr[copr.name]
114
131
132 @property
138
139 @property
142
144 """
145 :type group: Group
146 """
147 if group.fas_name in self.user_teams:
148 return True
149 else:
150 return False
151
170
171 @property
173
174 return ["id", "name"]
175
176 @property
178 """
179 Get number of coprs for this user.
180 """
181
182 return (Copr.query.filter_by(user=self).
183 filter_by(deleted=False).
184 filter_by(group_id=None).
185 count())
186
187 @property
189 """
190 Return url to libravatar image.
191 """
192
193 try:
194 return libravatar_url(email=self.mail, https=True)
195 except IOError:
196 return ""
197
200 """
201 Representation of User or Group <-> Copr relation
202 """
203 id = db.Column(db.Integer, primary_key=True)
204
205 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
206 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True, index=True)
207 group_id = db.Column(db.Integer, db.ForeignKey("group.id"), nullable=True, index=True)
208 position = db.Column(db.Integer, nullable=False)
209
210 copr = db.relationship("Copr")
211 user = db.relationship("User")
212 group = db.relationship("Group")
213
214
215 -class _CoprPublic(db.Model, helpers.Serializer, CoprSearchRelatedData):
216 """
217 Represents public part of a single copr (personal repo with builds, mock
218 chroots, etc.).
219 """
220
221 __tablename__ = "copr"
222 __table_args__ = (
223 db.Index('copr_name_group_id_idx', 'name', 'group_id'),
224 )
225
226 id = db.Column(db.Integer, primary_key=True)
227
228 name = db.Column(db.String(100), nullable=False)
229 homepage = db.Column(db.Text)
230 contact = db.Column(db.Text)
231
232
233 repos = db.Column(db.Text)
234
235 created_on = db.Column(db.Integer)
236
237 description = db.Column(db.Text)
238 instructions = db.Column(db.Text)
239 deleted = db.Column(db.Boolean, default=False)
240 playground = db.Column(db.Boolean, default=False)
241
242
243 auto_createrepo = db.Column(db.Boolean, default=True)
244
245
246 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True)
247 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
248 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
249
250
251 build_enable_net = db.Column(db.Boolean, default=True,
252 server_default="1", nullable=False)
253
254 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
255
256
257 latest_indexed_data_update = db.Column(db.Integer)
258
259
260 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
261
262
263 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
264
265
266 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
267
268
269 follow_fedora_branching = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
270
271
272 scm_repo_url = db.Column(db.Text)
273 scm_api_type = db.Column(db.Text)
274
275
276 delete_after = db.Column(db.DateTime, index=True, nullable=True)
277
278 multilib = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
279 module_hotfixes = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
280
283 """
284 Represents private part of a single copr (personal repo with builds, mock
285 chroots, etc.).
286 """
287
288 __table_args__ = (
289 db.Index('copr_private_webhook_secret', 'webhook_secret'),
290 )
291
292
293 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True,
294 nullable=False, primary_key=True)
295
296
297 webhook_secret = db.Column(db.String(100))
298
299
300 scm_api_auth_json = db.Column(db.Text)
301
302
303 -class Copr(db.Model, helpers.Serializer):
304 """
305 Represents private a single copr (personal repo with builds, mock chroots,
306 etc.).
307 """
308
309
310
311 __table__ = outerjoin(_CoprPublic.__table__, _CoprPrivate.__table__)
312 id = column_property(
313 _CoprPublic.__table__.c.id,
314 _CoprPrivate.__table__.c.copr_id
315 )
316
317
318 user = db.relationship("User", backref=db.backref("coprs"))
319 group = db.relationship("Group", backref=db.backref("groups"))
320 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
321 forked_from = db.relationship("Copr",
322 remote_side=_CoprPublic.id,
323 foreign_keys=[_CoprPublic.forked_from_id],
324 backref=db.backref("all_forks"))
325
326 @property
328 return [fork for fork in self.all_forks if not fork.deleted]
329
330 @property
331 - def main_dir(self):
332 """
333 Return main copr dir for a Copr
334 """
335 return CoprDir.query.filter(CoprDir.copr_id==self.id).filter(CoprDir.main==True).one()
336
337 @property
342
343 @property
345 """
346 Return True if copr belongs to a group
347 """
348 return self.group is not None
349
350 @property
356
357 @property
363
364 @property
366 """
367 Return repos of this copr as a list of strings
368 """
369 return self.repos.split()
370
371 @property
377
378 @property
398
399
400 @property
402 """
403 :rtype: list of CoprChroot
404 """
405 return [c for c in self.copr_chroots if c.is_active]
406
407 @property
409 """
410 Return list of active mock_chroots of this copr
411 """
412 return sorted(self.active_chroots, key=lambda ch: ch.name)
413
414 @property
418
419 @property
421 """
422 Return list of active mock_chroots of this copr
423 """
424 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
425 output = []
426 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
427 output.append((os, [ch[1] for ch in chs]))
428
429 return output
430
431 @property
433 """
434 Return number of builds in this copr
435 """
436 return len(self.builds)
437
438 @property
441
442 @disable_createrepo.setter
445
446 @property
449
450 @property
462
468
469 @property
472
473 @property
476
477 @property
482
483 @property
485 return "-".join([self.owner_name.replace("@", "group_"), self.name])
486
487 @property
489 return "/".join([self.repo_url, "modules"])
490
491 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
492 result = {}
493 for key in ["id", "name", "description", "instructions"]:
494 result[key] = str(copy.copy(getattr(self, key)))
495 result["owner"] = self.owner_name
496 return result
497
498 @property
503
506
507 @property
510
511 @enable_net.setter
514
517
518 @property
520 if self.delete_after is None:
521 return None
522
523 delta = self.delete_after - datetime.datetime.now()
524 return delta.days if delta.days > 0 else 0
525
526 @delete_after_days.setter
535
536 @property
541
542 @property
549
551 """
552 Association class for Copr<->Permission relation
553 """
554
555
556
557 copr_builder = db.Column(db.SmallInteger, default=0)
558
559 copr_admin = db.Column(db.SmallInteger, default=0)
560
561
562 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
563 user = db.relationship("User", backref=db.backref("copr_permissions"))
564 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
565 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
566
568 if name == 'admin':
569 self.copr_admin = value
570 elif name == 'builder':
571 self.copr_builder = value
572 else:
573 raise KeyError("{0} is not a valid copr permission".format(name))
574
581
584 """
585 Represents one of data directories for a copr.
586 """
587 id = db.Column(db.Integer, primary_key=True)
588
589 name = db.Column(db.Text, index=True)
590 main = db.Column(db.Boolean, index=True, default=False, server_default="0", nullable=False)
591
592 ownername = db.Column(db.Text, index=True, nullable=False)
593
594 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True, nullable=False)
595 copr = db.relationship("Copr", backref=db.backref("dirs"))
596
597 __table_args__ = (
598 db.Index('only_one_main_copr_dir', copr_id, main,
599 unique=True, postgresql_where=(main==True)),
600
601 db.UniqueConstraint('ownername', 'name',
602 name='ownername_copr_dir_uniq'),
603 )
604
609
610 @property
613
614 @property
617
618 @property
622
623 @property
629
630
631 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
632 """
633 Represents a single package in a project_dir.
634 """
635
636 __table_args__ = (
637 db.UniqueConstraint('copr_dir_id', 'name', name='packages_copr_dir_pkgname'),
638 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'),
639 )
640
645
646 id = db.Column(db.Integer, primary_key=True)
647 name = db.Column(db.String(100), nullable=False)
648
649 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
650
651 source_json = db.Column(db.Text)
652
653 webhook_rebuild = db.Column(db.Boolean, default=False)
654
655 enable_net = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
656
657
658 max_builds = db.Column(db.Integer, index=True)
659
660 @validates('max_builds')
662 return None if value == 0 else value
663
664 builds = db.relationship("Build", order_by="Build.id")
665
666
667 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True)
668 copr = db.relationship("Copr", backref=db.backref("packages"))
669
670 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)
671 copr_dir = db.relationship("CoprDir", backref=db.backref("packages"))
672
673
674
675 chroot_blacklist_raw = db.Column(db.Text)
676
677 @property
680
681 @property
686
687 @property
690
691 @property
693 """
694 Package's source type (and source_json) is being derived from its first build, which works except
695 for "link" and "upload" cases. Consider these being equivalent to source_type being unset.
696 """
697 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
698
699 @property
704
705 @property
711
717
718 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
719 package_dict = super(Package, self).to_dict()
720 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
721
722 if with_latest_build:
723 build = self.last_build(successful=False)
724 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
725 if with_latest_succeeded_build:
726 build = self.last_build(successful=True)
727 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
728 if with_all_builds:
729 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
730
731 return package_dict
732
735
736
737 @property
739 if not self.chroot_blacklist_raw:
740 return []
741
742 blacklisted = []
743 for pattern in self.chroot_blacklist_raw.split(','):
744 pattern = pattern.strip()
745 if not pattern:
746 continue
747 blacklisted.append(pattern)
748
749 return blacklisted
750
751
752 @staticmethod
754 for pattern in patterns:
755 if fnmatch(chroot.name, pattern):
756 return True
757 return False
758
759
760 @property
761 - def main_pkg(self):
762 if self.copr_dir.main:
763 return self
764
765 main_pkg = Package.query.filter_by(
766 name=self.name,
767 copr_dir_id=self.copr.main_dir.id
768 ).first()
769 return main_pkg
770
771
772 @property
784
785
786 -class Build(db.Model, helpers.Serializer):
787 """
788 Representation of one build in one copr
789 """
790
791 SCM_COMMIT = 'commit'
792 SCM_PULL_REQUEST = 'pull-request'
793
794 __table_args__ = (db.Index('build_canceled', "canceled"),
795 db.Index('build_order', "is_background", "id"),
796 db.Index('build_filter', "source_type", "canceled"),
797 db.Index('build_canceled_is_background_source_status_id_idx', 'canceled', "is_background", "source_status", "id"),
798 )
799
813
814 id = db.Column(db.Integer, primary_key=True)
815
816 pkgs = db.Column(db.Text)
817
818 built_packages = db.Column(db.Text)
819
820 pkg_version = db.Column(db.Text)
821
822 canceled = db.Column(db.Boolean, default=False)
823
824 repos = db.Column(db.Text)
825
826
827 submitted_on = db.Column(db.Integer, nullable=False)
828
829 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
830
831 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
832
833 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
834
835 enable_net = db.Column(db.Boolean, default=False,
836 server_default="0", nullable=False)
837
838 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
839
840 source_json = db.Column(db.Text)
841
842 fail_type = db.Column(db.Integer, default=FailTypeEnum("unset"))
843
844 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
845
846 source_status = db.Column(db.Integer, default=StatusEnum("waiting"))
847 srpm_url = db.Column(db.Text)
848
849
850 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True)
851 user = db.relationship("User", backref=db.backref("builds"))
852 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True)
853 copr = db.relationship("Copr", backref=db.backref("builds"))
854 package_id = db.Column(db.Integer, db.ForeignKey("package.id"), index=True)
855 package = db.relationship("Package")
856
857 chroots = association_proxy("build_chroots", "mock_chroot")
858
859 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
860 batch = db.relationship("Batch", backref=db.backref("builds"))
861
862 module_id = db.Column(db.Integer, db.ForeignKey("module.id"), index=True)
863 module = db.relationship("Module", backref=db.backref("builds"))
864
865 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)
866 copr_dir = db.relationship("CoprDir", backref=db.backref("builds"))
867
868
869 scm_object_id = db.Column(db.Text)
870 scm_object_type = db.Column(db.Text)
871 scm_object_url = db.Column(db.Text)
872
873
874 update_callback = db.Column(db.Text)
875
876
877 submitted_by = db.Column(db.Text)
878
879
880
881
882 resubmitted_from_id = db.Column(db.Integer)
883
884 _cached_status = None
885 _cached_status_set = None
886
887 @property
890
891 @property
894
895 @property
898
899 @property
902
903 @property
906
907 @property
908 - def fail_type_text(self):
909 return FailTypeEnum(self.fail_type)
910
911 @property
913 if self.repos is None:
914 return list()
915 else:
916 return self.repos.split()
917
918 @property
921
922 @property
924 return "{:08d}".format(self.id)
925
931
932 @property
934 if app.config["COPR_DIST_GIT_LOGS_URL"]:
935 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
936 self.task_id.replace('/', '_'))
937 return None
938
939 @property
947
948 @property
953
954 @property
957
958 @property
966
967 @property
970
971 @property
978
979 @property
982
983 @property
986
987 @property
990
991 @property
1000
1001 @property
1004
1006 """
1007 Get build chroots with states which present in `states` list
1008 If states == None, function returns build_chroots
1009 """
1010 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
1011 if statuses is not None:
1012 statuses = set(statuses)
1013 else:
1014 return self.build_chroots
1015
1016 return [
1017 chroot for chroot, status in chroot_states_map.items()
1018 if status in statuses
1019 ]
1020
1021 @property
1023 return {b.name: b for b in self.build_chroots}
1024
1025 @property
1027 """
1028 Return build status.
1029 """
1030 if self.canceled:
1031 return StatusEnum("canceled")
1032
1033 use_src_statuses = ["starting", "pending", "running", "importing", "failed"]
1034 if self.source_status in [StatusEnum(s) for s in use_src_statuses]:
1035 return self.source_status
1036
1037 if not self.chroot_states:
1038
1039
1040
1041
1042
1043
1044
1045 app.logger.error("Build %s has source_status succeeded, but "
1046 "no build_chroots", self.id)
1047 return StatusEnum("waiting")
1048
1049 for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked"]:
1050 if StatusEnum(state) in self.chroot_states:
1051 return StatusEnum(state)
1052
1053 if StatusEnum("waiting") in self.chroot_states:
1054
1055
1056
1057
1058 app.logger.error("Build chroots pending, even though build %s"
1059 " has succeeded source_status", self.id)
1060 return StatusEnum("pending")
1061
1062 return None
1063
1064 @property
1066 """
1067 Return text representation of status of this build.
1068 """
1069 if self.status != None:
1070 return StatusEnum(self.status)
1071 return "unknown"
1072
1073 @property
1075 """
1076 Find out if this build is cancelable.
1077 """
1078 return not self.finished and self.status != StatusEnum("starting")
1079
1080 @property
1082 """
1083 Find out if this build is repeatable.
1084
1085 Build is repeatable only if sources has been imported.
1086 """
1087 return self.source_status == StatusEnum("succeeded")
1088
1089 @property
1091 """
1092 Find out if this build is in finished state.
1093
1094 Build is finished only if all its build_chroots are in finished state or
1095 the build was canceled.
1096 """
1097 if self.canceled:
1098 return True
1099 if not self.build_chroots:
1100 return StatusEnum(self.source_status) in helpers.FINISHED_STATUSES
1101 return all([chroot.finished for chroot in self.build_chroots])
1102
1103 @property
1106
1107 @property
1109 """
1110 Find out if this build is persistent.
1111
1112 This property is inherited from the project.
1113 """
1114 return self.copr.persistent
1115
1116 @property
1118 try:
1119 return self.package.name
1120 except:
1121 return None
1122
1123 - def to_dict(self, options=None, with_chroot_states=False):
1136
1137 @property
1139 """
1140 Return tuple (submitter_string, submitter_link), while the
1141 submitter_link may be empty if we are not able to detect it
1142 wisely.
1143 """
1144 if self.user:
1145 user = self.user.name
1146 return (user, url_for('coprs_ns.coprs_by_user', username=user))
1147
1148 if self.submitted_by:
1149 links = ['http://', 'https://']
1150 if any([self.submitted_by.startswith(x) for x in links]):
1151 return (self.submitted_by, self.submitted_by)
1152
1153 return (self.submitted_by, None)
1154
1155 return (None, None)
1156
1157 @property
1159 """
1160 Return a string unique to project + submitter. At this level copr
1161 backend later applies builder user-VM separation policy (VMs are only
1162 re-used for builds which have the same build.sandbox value)
1163 """
1164 submitter, _ = self.submitter
1165 if not submitter:
1166
1167
1168 submitter = uuid.uuid4()
1169
1170 return '{0}--{1}'.format(self.copr.full_name, submitter)
1171
1172 @property
1175
1176 @property
1179
1182 """
1183 1:N mapping: branch -> chroots
1184 """
1185
1186
1187 name = db.Column(db.String(50), primary_key=True)
1188
1189
1190 -class MockChroot(db.Model, helpers.Serializer):
1191 """
1192 Representation of mock chroot
1193 """
1194
1195 __table_args__ = (
1196 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
1197 )
1198
1199 id = db.Column(db.Integer, primary_key=True)
1200
1201 os_release = db.Column(db.String(50), nullable=False)
1202
1203 os_version = db.Column(db.String(50), nullable=False)
1204
1205 arch = db.Column(db.String(50), nullable=False)
1206 is_active = db.Column(db.Boolean, default=True)
1207
1208
1209 distgit_branch_name = db.Column(db.String(50),
1210 db.ForeignKey("dist_git_branch.name"),
1211 nullable=False)
1212
1213 distgit_branch = db.relationship("DistGitBranch",
1214 backref=db.backref("chroots"))
1215
1216
1217
1218 final_prunerepo_done = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
1219
1220 comment = db.Column(db.Text, nullable=True)
1221
1222 multilib_pairs = {
1223 'x86_64': 'i386',
1224 }
1225
1226 @classmethod
1235
1236 @property
1238 """
1239 Textual representation of name of this chroot
1240 """
1241 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
1242
1243 @property
1245 """
1246 Textual representation of name of this or release
1247 """
1248 return "{}-{}".format(self.os_release, self.os_version)
1249
1250 @property
1252 """
1253 Textual representation of the operating system name
1254 """
1255 return "{0} {1}".format(self.os_release, self.os_version)
1256
1257 @property
1262
1263
1264 -class CoprChroot(db.Model, helpers.Serializer):
1265 """
1266 Representation of Copr<->MockChroot relation
1267 """
1268
1269 buildroot_pkgs = db.Column(db.Text)
1270 repos = db.Column(db.Text, default="", server_default="", nullable=False)
1271 mock_chroot_id = db.Column(
1272 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
1273 mock_chroot = db.relationship(
1274 "MockChroot", backref=db.backref("copr_chroots"))
1275 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
1276 copr = db.relationship("Copr",
1277 backref=db.backref(
1278 "copr_chroots",
1279 single_parent=True,
1280 cascade="all,delete,delete-orphan"))
1281
1282 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
1283 comps_name = db.Column(db.String(127), nullable=True)
1284
1285 module_toggle = db.Column(db.Text, nullable=True)
1286
1287 with_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1288 without_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1289
1290
1291
1292 delete_after = db.Column(db.DateTime, index=True)
1293 delete_notify = db.Column(db.DateTime, index=True)
1294
1296 if isinstance(comps_xml, str):
1297 data = comps_xml.encode("utf-8")
1298 else:
1299 data = comps_xml
1300 self.comps_zlib = zlib.compress(data)
1301
1302 @property
1305
1306 @property
1308 return (self.repos or "").split()
1309
1310 @property
1314
1315 @property
1321
1322 @property
1325
1326 @property
1329
1330 @property
1332 if not self.delete_after:
1333 return None
1334 now = datetime.datetime.now()
1335 days = (self.delete_after - now).days
1336 return days if days > 0 else 0
1337
1338 @property
1340 if not self.module_toggle:
1341 return []
1342 module_enable = []
1343 for m in self.module_toggle.split(','):
1344 if m[0] != "!":
1345 module_enable.append(m)
1346 return module_enable
1347
1349 options = {"__columns_only__": [
1350 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts"
1351 ]}
1352 d = super(CoprChroot, self).to_dict(options=options)
1353 d["mock_chroot"] = self.mock_chroot.name
1354 return d
1355
1358 """
1359 Representation of Build<->MockChroot relation
1360 """
1361
1362 __table_args__ = (db.Index('build_chroot_status_started_on_idx', "status", "started_on"),)
1363
1364 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
1365 primary_key=True)
1366 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
1367 build_id = db.Column(db.Integer, db.ForeignKey("build.id", ondelete="CASCADE"),
1368 primary_key=True)
1369 build = db.relationship("Build", backref=db.backref("build_chroots", cascade="all, delete-orphan",
1370 passive_deletes=True))
1371 git_hash = db.Column(db.String(40))
1372 status = db.Column(db.Integer, default=StatusEnum("waiting"))
1373
1374 started_on = db.Column(db.Integer, index=True)
1375 ended_on = db.Column(db.Integer, index=True)
1376
1377
1378 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
1379
1380 build_requires = db.Column(db.Text)
1381
1382 @property
1384 """
1385 Textual representation of name of this chroot
1386 """
1387 return self.mock_chroot.name
1388
1389 @property
1391 """
1392 Return text representation of status of this build chroot
1393 """
1394 if self.status is not None:
1395 return StatusEnum(self.status)
1396 return "unknown"
1397
1398 @property
1401
1402 @property
1405
1406 @property
1420
1421 @property
1427
1428 @property
1430 if not self.build.package:
1431 return None
1432
1433 if not (self.finished or self.state == "running"):
1434 return None
1435
1436 if not self.result_dir_url:
1437 return None
1438
1439 return os.path.join(self.result_dir_url,
1440 "builder-live.log" if self.state == 'running' else "builder-live.log.gz")
1441
1442
1443 -class LegalFlag(db.Model, helpers.Serializer):
1444 id = db.Column(db.Integer, primary_key=True)
1445
1446 raise_message = db.Column(db.Text)
1447
1448 raised_on = db.Column(db.Integer)
1449
1450 resolved_on = db.Column(db.Integer)
1451
1452
1453 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1454
1455 copr = db.relationship(
1456 "Copr", backref=db.backref("legal_flags", cascade="all"))
1457
1458 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1459 reporter = db.relationship("User",
1460 backref=db.backref("legal_flags_raised"),
1461 foreign_keys=[reporter_id],
1462 primaryjoin="LegalFlag.reporter_id==User.id")
1463
1464 resolver_id = db.Column(
1465 db.Integer, db.ForeignKey("user.id"), nullable=True)
1466 resolver = db.relationship("User",
1467 backref=db.backref("legal_flags_resolved"),
1468 foreign_keys=[resolver_id],
1469 primaryjoin="LegalFlag.resolver_id==User.id")
1470
1471
1472 -class Action(db.Model, helpers.Serializer):
1473 """
1474 Representation of a custom action that needs
1475 backends cooperation/admin attention/...
1476 """
1477
1478 id = db.Column(db.Integer, primary_key=True)
1479
1480 action_type = db.Column(db.Integer, nullable=False)
1481
1482 object_type = db.Column(db.String(20))
1483
1484 object_id = db.Column(db.Integer)
1485
1486 old_value = db.Column(db.String(255))
1487 new_value = db.Column(db.String(255))
1488
1489 data = db.Column(db.Text)
1490
1491 result = db.Column(
1492 db.Integer, default=BackendResultEnum("waiting"))
1493
1494 message = db.Column(db.Text)
1495
1496 created_on = db.Column(db.Integer, index=True)
1497
1498 ended_on = db.Column(db.Integer, index=True)
1499
1502
1511
1524
1525
1526 -class Krb5Login(db.Model, helpers.Serializer):
1527 """
1528 Represents additional user information for kerberos authentication.
1529 """
1530
1531 __tablename__ = "krb5_login"
1532
1533
1534 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1535
1536
1537 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1538
1539
1540 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1541
1542 user = db.relationship("User", backref=db.backref("krb5_logins"))
1543
1546 """
1547 Generic store for simple statistics.
1548 """
1549
1550 name = db.Column(db.String(127), primary_key=True)
1551 counter_type = db.Column(db.String(30))
1552
1553 counter = db.Column(db.Integer, default=0, server_default="0")
1554
1555
1556 -class Group(db.Model, helpers.Serializer):
1557
1558 """
1559 Represents FAS groups and their aliases in Copr
1560 """
1561
1562 id = db.Column(db.Integer, primary_key=True)
1563 name = db.Column(db.String(127))
1564
1565
1566 fas_name = db.Column(db.String(127))
1567
1568 @property
1570 return u"@{}".format(self.name)
1571
1574
1577
1578
1579 -class Batch(db.Model):
1580 id = db.Column(db.Integer, primary_key=True)
1581 blocked_by_id = db.Column(db.Integer, db.ForeignKey("batch.id"), nullable=True)
1582 blocked_by = db.relationship("Batch", remote_side=[id])
1583
1584 @property
1587
1588
1589 -class Module(db.Model, helpers.Serializer):
1590 id = db.Column(db.Integer, primary_key=True)
1591 name = db.Column(db.String(100), nullable=False)
1592 stream = db.Column(db.String(100), nullable=False)
1593 version = db.Column(db.BigInteger, nullable=False)
1594 summary = db.Column(db.String(100), nullable=False)
1595 description = db.Column(db.Text)
1596 created_on = db.Column(db.Integer, nullable=True)
1597
1598
1599
1600
1601
1602
1603
1604 yaml_b64 = db.Column(db.Text)
1605
1606
1607 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1608 copr = db.relationship("Copr", backref=db.backref("modules"))
1609
1610 __table_args__ = (
1611 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"),
1612 )
1613
1614 @property
1616 return base64.b64decode(self.yaml_b64)
1617
1618 @property
1620 mmd = Modulemd.ModuleStream()
1621 mmd.import_from_string(self.yaml.decode("utf-8"))
1622 return mmd
1623
1624 @property
1627
1628 @property
1631
1632 @property
1635
1636 @property
1638 """
1639 Return numeric representation of status of this build
1640 """
1641 if self.action:
1642 return { BackendResultEnum("success"): ModuleStatusEnum("succeeded"),
1643 BackendResultEnum("failure"): ModuleStatusEnum("failed"),
1644 BackendResultEnum("waiting"): ModuleStatusEnum("waiting"),
1645 }[self.action.result]
1646 build_statuses = [b.status for b in self.builds]
1647 for state in ["canceled", "running", "starting", "pending", "failed", "succeeded"]:
1648 if ModuleStatusEnum(state) in build_statuses:
1649 return ModuleStatusEnum(state)
1650
1651 @property
1653 """
1654 Return text representation of status of this build
1655 """
1656 return ModuleStatusEnum(self.status)
1657
1658 @property
1661
1662 @property
1665
1666 @property
1668 return {k: v.get_rpms().get() for k, v in self.modulemd.get_profiles().items()}
1669
1676
1683
1686 """ Dist-git instances, e.g. Fedora/CentOS/RHEL/ """
1687
1688
1689 id = db.Column(db.Integer, primary_key=True)
1690
1691
1692 name = db.Column(db.String(50), nullable=False, unique=True)
1693
1694
1695 clone_url = db.Column(db.String(100), nullable=False)
1696
1697
1698 clone_package_uri = db.Column(db.String(100), nullable=False)
1699
1700
1701 priority = db.Column(db.Integer, default=100, nullable=False)
1702
1706
1707
1708 @listens_for(DistGitInstance.__table__, 'after_create')
1716