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 six.moves.urllib.parse import urljoin
13 from libravatar import libravatar_url
14 import zlib
15
16 from flask import url_for
17
18 from copr_common.enums import ActionTypeEnum, BackendResultEnum, FailTypeEnum, ModuleStatusEnum, StatusEnum
19 from coprs import constants
20 from coprs import db
21 from coprs import helpers
22 from coprs import app
23
24 import itertools
25 import operator
26 from coprs.helpers import JSONEncodedDict
27
28 import gi
29 gi.require_version('Modulemd', '1.0')
30 from gi.repository import Modulemd
36
61
64 """
65 Records all the private information for a user.
66 """
67
68 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True,
69 nullable=False)
70
71
72 mail = db.Column(db.String(150), nullable=False)
73
74
75 timezone = db.Column(db.String(50), nullable=True)
76
77
78 api_login = db.Column(db.String(40), nullable=False, default="abc")
79 api_token = db.Column(db.String(40), nullable=False, default="abc")
80 api_token_expiration = db.Column(
81 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
82
83
84 -class User(db.Model, helpers.Serializer):
85 __table__ = outerjoin(_UserPublic.__table__, _UserPrivate.__table__)
86 id = column_property(_UserPublic.__table__.c.id, _UserPrivate.__table__.c.user_id)
87
88 @property
90 """
91 Return the short username of the user, e.g. bkabrda
92 """
93
94 return self.username
95
97 """
98 Get permissions of this user for the given copr.
99 Caches the permission during one request,
100 so use this if you access them multiple times
101 """
102
103 if not hasattr(self, "_permissions_for_copr"):
104 self._permissions_for_copr = {}
105 if copr.name not in self._permissions_for_copr:
106 self._permissions_for_copr[copr.name] = (
107 CoprPermission.query
108 .filter_by(user=self)
109 .filter_by(copr=copr)
110 .first()
111 )
112 return self._permissions_for_copr[copr.name]
113
130
131 @property
137
138 @property
141
143 """
144 :type group: Group
145 """
146 if group.fas_name in self.user_teams:
147 return True
148 else:
149 return False
150
169
170 @property
172
173 return ["id", "name"]
174
175 @property
177 """
178 Get number of coprs for this user.
179 """
180
181 return (Copr.query.filter_by(user=self).
182 filter_by(deleted=False).
183 filter_by(group_id=None).
184 count())
185
186 @property
188 """
189 Return url to libravatar image.
190 """
191
192 try:
193 return libravatar_url(email=self.mail, https=True)
194 except IOError:
195 return ""
196
199 """
200 Representation of User or Group <-> Copr relation
201 """
202 id = db.Column(db.Integer, primary_key=True)
203
204 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
205 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True, index=True)
206 group_id = db.Column(db.Integer, db.ForeignKey("group.id"), nullable=True, index=True)
207 position = db.Column(db.Integer, nullable=False)
208
209 copr = db.relationship("Copr")
210 user = db.relationship("User")
211 group = db.relationship("Group")
212
213
214 -class _CoprPublic(db.Model, helpers.Serializer, CoprSearchRelatedData):
215 """
216 Represents public part of a single copr (personal repo with builds, mock
217 chroots, etc.).
218 """
219
220 __tablename__ = "copr"
221 __table_args__ = (
222 db.Index('copr_name_group_id_idx', 'name', 'group_id'),
223 )
224
225 id = db.Column(db.Integer, primary_key=True)
226
227 name = db.Column(db.String(100), nullable=False)
228 homepage = db.Column(db.Text)
229 contact = db.Column(db.Text)
230
231
232 repos = db.Column(db.Text)
233
234 created_on = db.Column(db.Integer)
235
236 description = db.Column(db.Text)
237 instructions = db.Column(db.Text)
238 deleted = db.Column(db.Boolean, default=False)
239 playground = db.Column(db.Boolean, default=False)
240
241
242 auto_createrepo = db.Column(db.Boolean, default=True)
243
244
245 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True)
246 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
247 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
248
249
250 build_enable_net = db.Column(db.Boolean, default=True,
251 server_default="1", nullable=False)
252
253 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
254
255
256 latest_indexed_data_update = db.Column(db.Integer)
257
258
259 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
260
261
262 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
263
264
265 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
266
267
268 follow_fedora_branching = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
269
270
271 scm_repo_url = db.Column(db.Text)
272 scm_api_type = db.Column(db.Text)
273
274
275 delete_after = db.Column(db.DateTime, index=True, nullable=True)
276
277 multilib = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
278 module_hotfixes = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
279
282 """
283 Represents private part of a single copr (personal repo with builds, mock
284 chroots, etc.).
285 """
286
287 __table_args__ = (
288 db.Index('copr_private_webhook_secret', 'webhook_secret'),
289 )
290
291
292 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True,
293 nullable=False, primary_key=True)
294
295
296 webhook_secret = db.Column(db.String(100))
297
298
299 scm_api_auth_json = db.Column(db.Text)
300
301
302 -class Copr(db.Model, helpers.Serializer):
303 """
304 Represents private a single copr (personal repo with builds, mock chroots,
305 etc.).
306 """
307
308
309
310 __table__ = outerjoin(_CoprPublic.__table__, _CoprPrivate.__table__)
311 id = column_property(
312 _CoprPublic.__table__.c.id,
313 _CoprPrivate.__table__.c.copr_id
314 )
315
316
317 user = db.relationship("User", backref=db.backref("coprs"))
318 group = db.relationship("Group", backref=db.backref("groups"))
319 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
320 forked_from = db.relationship("Copr",
321 remote_side=_CoprPublic.id,
322 foreign_keys=[_CoprPublic.forked_from_id],
323 backref=db.backref("all_forks"))
324
325 @property
327 return [fork for fork in self.all_forks if not fork.deleted]
328
329 @property
330 - def main_dir(self):
331 """
332 Return main copr dir for a Copr
333 """
334 return CoprDir.query.filter(CoprDir.copr_id==self.id).filter(CoprDir.main==True).one()
335
336 @property
341
342 @property
344 """
345 Return True if copr belongs to a group
346 """
347 return self.group is not None
348
349 @property
355
356 @property
362
363 @property
365 """
366 Return repos of this copr as a list of strings
367 """
368 return self.repos.split()
369
370 @property
376
377 @property
397
398
399 @property
401 """
402 :rtype: list of CoprChroot
403 """
404 return [c for c in self.copr_chroots if c.is_active]
405
406 @property
408 """
409 Return list of active mock_chroots of this copr
410 """
411 return sorted(self.active_chroots, key=lambda ch: ch.name)
412
413 @property
417
418 @property
420 """
421 Return list of active mock_chroots of this copr
422 """
423 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
424 output = []
425 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
426 output.append((os, [ch[1] for ch in chs]))
427
428 return output
429
430 @property
432 """
433 Return number of builds in this copr
434 """
435 return len(self.builds)
436
437 @property
440
441 @disable_createrepo.setter
444
445 @property
448
449 @property
461
467
468 @property
471
472 @property
475
476 @property
481
482 @property
484 return "-".join([self.owner_name.replace("@", "group_"), self.name])
485
486 @property
488 return "/".join([self.repo_url, "modules"])
489
490 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
491 result = {}
492 for key in ["id", "name", "description", "instructions"]:
493 result[key] = str(copy.copy(getattr(self, key)))
494 result["owner"] = self.owner_name
495 return result
496
497 @property
502
505
506 @property
509
510 @enable_net.setter
513
516
517 @property
519 if self.delete_after is None:
520 return None
521
522 delta = self.delete_after - datetime.datetime.now()
523 return delta.days if delta.days > 0 else 0
524
525 @delete_after_days.setter
534
535 @property
540
541 @property
548
550 """
551 Association class for Copr<->Permission relation
552 """
553
554
555
556 copr_builder = db.Column(db.SmallInteger, default=0)
557
558 copr_admin = db.Column(db.SmallInteger, default=0)
559
560
561 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
562 user = db.relationship("User", backref=db.backref("copr_permissions"))
563 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
564 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
565
567 if name == 'admin':
568 self.copr_admin = value
569 elif name == 'builder':
570 self.copr_builder = value
571 else:
572 raise KeyError("{0} is not a valid copr permission".format(name))
573
580
583 """
584 Represents one of data directories for a copr.
585 """
586 id = db.Column(db.Integer, primary_key=True)
587
588 name = db.Column(db.Text, index=True)
589 main = db.Column(db.Boolean, index=True, default=False, server_default="0", nullable=False)
590
591 ownername = db.Column(db.Text, index=True, nullable=False)
592
593 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True, nullable=False)
594 copr = db.relationship("Copr", backref=db.backref("dirs"))
595
596 __table_args__ = (
597 db.Index('only_one_main_copr_dir', copr_id, main,
598 unique=True, postgresql_where=(main==True)),
599
600 db.UniqueConstraint('ownername', 'name',
601 name='ownername_copr_dir_uniq'),
602 )
603
608
609 @property
612
613 @property
616
617 @property
621
622 @property
628
629
630 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
631 """
632 Represents a single package in a project_dir.
633 """
634
635 __table_args__ = (
636 db.UniqueConstraint('copr_dir_id', 'name', name='packages_copr_dir_pkgname'),
637 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'),
638 )
639
644
645 id = db.Column(db.Integer, primary_key=True)
646 name = db.Column(db.String(100), nullable=False)
647
648 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
649
650 source_json = db.Column(db.Text)
651
652 webhook_rebuild = db.Column(db.Boolean, default=False)
653
654 enable_net = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
655
656
657 max_builds = db.Column(db.Integer, index=True)
658
659 @validates('max_builds')
661 return None if value == 0 else value
662
663 builds = db.relationship("Build", order_by="Build.id")
664
665
666 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True)
667 copr = db.relationship("Copr", backref=db.backref("packages"))
668
669 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)
670 copr_dir = db.relationship("CoprDir", backref=db.backref("packages"))
671
672
673
674 chroot_blacklist_raw = db.Column(db.Text)
675
676 @property
679
680 @property
685
686 @property
689
690 @property
692 """
693 Package's source type (and source_json) is being derived from its first build, which works except
694 for "link" and "upload" cases. Consider these being equivalent to source_type being unset.
695 """
696 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
697
698 @property
703
704 @property
710
716
717 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
718 package_dict = super(Package, self).to_dict()
719 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
720
721 if with_latest_build:
722 build = self.last_build(successful=False)
723 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
724 if with_latest_succeeded_build:
725 build = self.last_build(successful=True)
726 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
727 if with_all_builds:
728 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
729
730 return package_dict
731
734
735
736 @property
738 if not self.chroot_blacklist_raw:
739 return []
740
741 blacklisted = []
742 for pattern in self.chroot_blacklist_raw.split(','):
743 pattern = pattern.strip()
744 if not pattern:
745 continue
746 blacklisted.append(pattern)
747
748 return blacklisted
749
750
751 @staticmethod
753 for pattern in patterns:
754 if fnmatch(chroot.name, pattern):
755 return True
756 return False
757
758
759 @property
760 - def main_pkg(self):
761 if self.copr_dir.main:
762 return self
763
764 main_pkg = Package.query.filter_by(
765 name=self.name,
766 copr_dir_id=self.copr.main_dir.id
767 ).first()
768 return main_pkg
769
770
771 @property
783
784
785 -class Build(db.Model, helpers.Serializer):
786 """
787 Representation of one build in one copr
788 """
789
790 SCM_COMMIT = 'commit'
791 SCM_PULL_REQUEST = 'pull-request'
792
793 __table_args__ = (db.Index('build_canceled', "canceled"),
794 db.Index('build_order', "is_background", "id"),
795 db.Index('build_filter', "source_type", "canceled"),
796 db.Index('build_canceled_is_background_source_status_id_idx', 'canceled', "is_background", "source_status", "id"),
797 )
798
812
813 id = db.Column(db.Integer, primary_key=True)
814
815 pkgs = db.Column(db.Text)
816
817 built_packages = db.Column(db.Text)
818
819 pkg_version = db.Column(db.Text)
820
821 canceled = db.Column(db.Boolean, default=False)
822
823 repos = db.Column(db.Text)
824
825
826 submitted_on = db.Column(db.Integer, nullable=False)
827
828 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
829
830 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
831
832 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
833
834 enable_net = db.Column(db.Boolean, default=False,
835 server_default="0", nullable=False)
836
837 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
838
839 source_json = db.Column(db.Text)
840
841 fail_type = db.Column(db.Integer, default=FailTypeEnum("unset"))
842
843 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
844
845 source_status = db.Column(db.Integer, default=StatusEnum("waiting"))
846 srpm_url = db.Column(db.Text)
847
848
849 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True)
850 user = db.relationship("User", backref=db.backref("builds"))
851 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True)
852 copr = db.relationship("Copr", backref=db.backref("builds"))
853 package_id = db.Column(db.Integer, db.ForeignKey("package.id"), index=True)
854 package = db.relationship("Package")
855
856 chroots = association_proxy("build_chroots", "mock_chroot")
857
858 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
859 batch = db.relationship("Batch", backref=db.backref("builds"))
860
861 module_id = db.Column(db.Integer, db.ForeignKey("module.id"), index=True)
862 module = db.relationship("Module", backref=db.backref("builds"))
863
864 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)
865 copr_dir = db.relationship("CoprDir", backref=db.backref("builds"))
866
867
868 scm_object_id = db.Column(db.Text)
869 scm_object_type = db.Column(db.Text)
870 scm_object_url = db.Column(db.Text)
871
872
873 update_callback = db.Column(db.Text)
874
875
876 submitted_by = db.Column(db.Text)
877
878
879
880
881 resubmitted_from_id = db.Column(db.Integer)
882
883 _cached_status = None
884 _cached_status_set = None
885
886 @property
889
890 @property
893
894 @property
897
898 @property
901
902 @property
905
906 @property
907 - def fail_type_text(self):
908 return FailTypeEnum(self.fail_type)
909
910 @property
912 if self.repos is None:
913 return list()
914 else:
915 return self.repos.split()
916
917 @property
920
921 @property
923 return "{:08d}".format(self.id)
924
930
931 @property
933 if app.config["COPR_DIST_GIT_LOGS_URL"]:
934 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
935 self.task_id.replace('/', '_'))
936 return None
937
938 @property
946
947 @property
952
953 @property
956
957 @property
965
966 @property
969
970 @property
977
978 @property
981
982 @property
985
986 @property
989
990 @property
999
1000 @property
1003
1005 """
1006 Get build chroots with states which present in `states` list
1007 If states == None, function returns build_chroots
1008 """
1009 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
1010 if statuses is not None:
1011 statuses = set(statuses)
1012 else:
1013 return self.build_chroots
1014
1015 return [
1016 chroot for chroot, status in chroot_states_map.items()
1017 if status in statuses
1018 ]
1019
1020 @property
1022 return {b.name: b for b in self.build_chroots}
1023
1024 @property
1026 """
1027 Return build status.
1028 """
1029 if self.canceled:
1030 return StatusEnum("canceled")
1031
1032 use_src_statuses = ["starting", "pending", "running", "importing", "failed"]
1033 if self.source_status in [StatusEnum(s) for s in use_src_statuses]:
1034 return self.source_status
1035
1036 if not self.chroot_states:
1037
1038
1039
1040
1041
1042
1043
1044 app.logger.error("Build %s has source_status succeeded, but "
1045 "no build_chroots", self.id)
1046 return StatusEnum("waiting")
1047
1048 for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked"]:
1049 if StatusEnum(state) in self.chroot_states:
1050 return StatusEnum(state)
1051
1052 if StatusEnum("waiting") in self.chroot_states:
1053
1054
1055
1056
1057 app.logger.error("Build chroots pending, even though build %s"
1058 " has succeeded source_status", self.id)
1059 return StatusEnum("pending")
1060
1061 return None
1062
1063 @property
1065 """
1066 Return text representation of status of this build.
1067 """
1068 if self.status != None:
1069 return StatusEnum(self.status)
1070 return "unknown"
1071
1072 @property
1074 """
1075 Find out if this build is cancelable.
1076 """
1077 return not self.finished and self.status != StatusEnum("starting")
1078
1079 @property
1081 """
1082 Find out if this build is repeatable.
1083
1084 Build is repeatable only if sources has been imported.
1085 """
1086 return self.source_status == StatusEnum("succeeded")
1087
1088 @property
1090 """
1091 Find out if this build is in finished state.
1092
1093 Build is finished only if all its build_chroots are in finished state or
1094 the build was canceled.
1095 """
1096 if self.canceled:
1097 return True
1098 if not self.build_chroots:
1099 return StatusEnum(self.source_status) in helpers.FINISHED_STATUSES
1100 return all([chroot.finished for chroot in self.build_chroots])
1101
1102 @property
1105
1106 @property
1108 """
1109 Find out if this build is persistent.
1110
1111 This property is inherited from the project.
1112 """
1113 return self.copr.persistent
1114
1115 @property
1117 try:
1118 return self.package.name
1119 except:
1120 return None
1121
1122 - def to_dict(self, options=None, with_chroot_states=False):
1135
1136 @property
1138 """
1139 Return tuple (submitter_string, submitter_link), while the
1140 submitter_link may be empty if we are not able to detect it
1141 wisely.
1142 """
1143 if self.user:
1144 user = self.user.name
1145 return (user, url_for('coprs_ns.coprs_by_user', username=user))
1146
1147 if self.submitted_by:
1148 links = ['http://', 'https://']
1149 if any([self.submitted_by.startswith(x) for x in links]):
1150 return (self.submitted_by, self.submitted_by)
1151
1152 return (self.submitted_by, None)
1153
1154 return (None, None)
1155
1156 @property
1158 """
1159 Return a string unique to project + submitter. At this level copr
1160 backend later applies builder user-VM separation policy (VMs are only
1161 re-used for builds which have the same build.sandbox value)
1162 """
1163 submitter, _ = self.submitter
1164 if not submitter:
1165
1166
1167 submitter = uuid.uuid4()
1168
1169 return '{0}--{1}'.format(self.copr.full_name, submitter)
1170
1171 @property
1174
1175 @property
1178
1181 """
1182 1:N mapping: branch -> chroots
1183 """
1184
1185
1186 name = db.Column(db.String(50), primary_key=True)
1187
1188
1189 -class MockChroot(db.Model, helpers.Serializer):
1190 """
1191 Representation of mock chroot
1192 """
1193
1194 __table_args__ = (
1195 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
1196 )
1197
1198 id = db.Column(db.Integer, primary_key=True)
1199
1200 os_release = db.Column(db.String(50), nullable=False)
1201
1202 os_version = db.Column(db.String(50), nullable=False)
1203
1204 arch = db.Column(db.String(50), nullable=False)
1205 is_active = db.Column(db.Boolean, default=True)
1206
1207
1208 distgit_branch_name = db.Column(db.String(50),
1209 db.ForeignKey("dist_git_branch.name"),
1210 nullable=False)
1211
1212 distgit_branch = db.relationship("DistGitBranch",
1213 backref=db.backref("chroots"))
1214
1215
1216
1217 final_prunerepo_done = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
1218
1219 comment = db.Column(db.Text, nullable=True)
1220
1221 multilib_pairs = {
1222 'x86_64': 'i386',
1223 }
1224
1225 @classmethod
1234
1235 @property
1237 """
1238 Textual representation of name of this chroot
1239 """
1240 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
1241
1242 @property
1244 """
1245 Textual representation of name of this or release
1246 """
1247 return "{}-{}".format(self.os_release, self.os_version)
1248
1249 @property
1251 """
1252 Textual representation of the operating system name
1253 """
1254 return "{0} {1}".format(self.os_release, self.os_version)
1255
1256 @property
1261
1262
1263 -class CoprChroot(db.Model, helpers.Serializer):
1264 """
1265 Representation of Copr<->MockChroot relation
1266 """
1267
1268 buildroot_pkgs = db.Column(db.Text)
1269 repos = db.Column(db.Text, default="", server_default="", nullable=False)
1270 mock_chroot_id = db.Column(
1271 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
1272 mock_chroot = db.relationship(
1273 "MockChroot", backref=db.backref("copr_chroots"))
1274 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
1275 copr = db.relationship("Copr",
1276 backref=db.backref(
1277 "copr_chroots",
1278 single_parent=True,
1279 cascade="all,delete,delete-orphan"))
1280
1281 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
1282 comps_name = db.Column(db.String(127), nullable=True)
1283
1284 with_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1285 without_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1286
1287
1288
1289 delete_after = db.Column(db.DateTime, index=True)
1290 delete_notify = db.Column(db.DateTime, index=True)
1291
1293 if isinstance(comps_xml, str):
1294 data = comps_xml.encode("utf-8")
1295 else:
1296 data = comps_xml
1297 self.comps_zlib = zlib.compress(data)
1298
1299 @property
1302
1303 @property
1305 return (self.repos or "").split()
1306
1307 @property
1311
1312 @property
1318
1319 @property
1322
1323 @property
1326
1327 @property
1329 if not self.delete_after:
1330 return None
1331 now = datetime.datetime.now()
1332 days = (self.delete_after - now).days
1333 return days if days > 0 else 0
1334
1336 options = {"__columns_only__": [
1337 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts"
1338 ]}
1339 d = super(CoprChroot, self).to_dict(options=options)
1340 d["mock_chroot"] = self.mock_chroot.name
1341 return d
1342
1345 """
1346 Representation of Build<->MockChroot relation
1347 """
1348
1349 __table_args__ = (db.Index('build_chroot_status_started_on_idx', "status", "started_on"),)
1350
1351 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
1352 primary_key=True)
1353 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
1354 build_id = db.Column(db.Integer, db.ForeignKey("build.id", ondelete="CASCADE"),
1355 primary_key=True)
1356 build = db.relationship("Build", backref=db.backref("build_chroots", cascade="all, delete-orphan",
1357 passive_deletes=True))
1358 git_hash = db.Column(db.String(40))
1359 status = db.Column(db.Integer, default=StatusEnum("waiting"))
1360
1361 started_on = db.Column(db.Integer, index=True)
1362 ended_on = db.Column(db.Integer, index=True)
1363
1364
1365 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
1366
1367 build_requires = db.Column(db.Text)
1368
1369 @property
1371 """
1372 Textual representation of name of this chroot
1373 """
1374 return self.mock_chroot.name
1375
1376 @property
1378 """
1379 Return text representation of status of this build chroot
1380 """
1381 if self.status is not None:
1382 return StatusEnum(self.status)
1383 return "unknown"
1384
1385 @property
1388
1389 @property
1392
1393 @property
1407
1408 @property
1414
1415 @property
1417 if not self.build.package:
1418 return None
1419
1420 if not (self.finished or self.state == "running"):
1421 return None
1422
1423 if not self.result_dir_url:
1424 return None
1425
1426 return os.path.join(self.result_dir_url,
1427 "builder-live.log" if self.state == 'running' else "builder-live.log.gz")
1428
1429
1430 -class LegalFlag(db.Model, helpers.Serializer):
1431 id = db.Column(db.Integer, primary_key=True)
1432
1433 raise_message = db.Column(db.Text)
1434
1435 raised_on = db.Column(db.Integer)
1436
1437 resolved_on = db.Column(db.Integer)
1438
1439
1440 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1441
1442 copr = db.relationship(
1443 "Copr", backref=db.backref("legal_flags", cascade="all"))
1444
1445 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1446 reporter = db.relationship("User",
1447 backref=db.backref("legal_flags_raised"),
1448 foreign_keys=[reporter_id],
1449 primaryjoin="LegalFlag.reporter_id==User.id")
1450
1451 resolver_id = db.Column(
1452 db.Integer, db.ForeignKey("user.id"), nullable=True)
1453 resolver = db.relationship("User",
1454 backref=db.backref("legal_flags_resolved"),
1455 foreign_keys=[resolver_id],
1456 primaryjoin="LegalFlag.resolver_id==User.id")
1457
1458
1459 -class Action(db.Model, helpers.Serializer):
1460 """
1461 Representation of a custom action that needs
1462 backends cooperation/admin attention/...
1463 """
1464
1465 id = db.Column(db.Integer, primary_key=True)
1466
1467 action_type = db.Column(db.Integer, nullable=False)
1468
1469 object_type = db.Column(db.String(20))
1470
1471 object_id = db.Column(db.Integer)
1472
1473 old_value = db.Column(db.String(255))
1474 new_value = db.Column(db.String(255))
1475
1476 data = db.Column(db.Text)
1477
1478 result = db.Column(
1479 db.Integer, default=BackendResultEnum("waiting"))
1480
1481 message = db.Column(db.Text)
1482
1483 created_on = db.Column(db.Integer, index=True)
1484
1485 ended_on = db.Column(db.Integer, index=True)
1486
1489
1498
1511
1512
1513 -class Krb5Login(db.Model, helpers.Serializer):
1514 """
1515 Represents additional user information for kerberos authentication.
1516 """
1517
1518 __tablename__ = "krb5_login"
1519
1520
1521 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1522
1523
1524 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1525
1526
1527 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1528
1529 user = db.relationship("User", backref=db.backref("krb5_logins"))
1530
1533 """
1534 Generic store for simple statistics.
1535 """
1536
1537 name = db.Column(db.String(127), primary_key=True)
1538 counter_type = db.Column(db.String(30))
1539
1540 counter = db.Column(db.Integer, default=0, server_default="0")
1541
1542
1543 -class Group(db.Model, helpers.Serializer):
1544
1545 """
1546 Represents FAS groups and their aliases in Copr
1547 """
1548
1549 id = db.Column(db.Integer, primary_key=True)
1550 name = db.Column(db.String(127))
1551
1552
1553 fas_name = db.Column(db.String(127))
1554
1555 @property
1557 return u"@{}".format(self.name)
1558
1561
1564
1565
1566 -class Batch(db.Model):
1567 id = db.Column(db.Integer, primary_key=True)
1568 blocked_by_id = db.Column(db.Integer, db.ForeignKey("batch.id"), nullable=True)
1569 blocked_by = db.relationship("Batch", remote_side=[id])
1570
1571 @property
1574
1575
1576 -class Module(db.Model, helpers.Serializer):
1577 id = db.Column(db.Integer, primary_key=True)
1578 name = db.Column(db.String(100), nullable=False)
1579 stream = db.Column(db.String(100), nullable=False)
1580 version = db.Column(db.BigInteger, nullable=False)
1581 summary = db.Column(db.String(100), nullable=False)
1582 description = db.Column(db.Text)
1583 created_on = db.Column(db.Integer, nullable=True)
1584
1585
1586
1587
1588
1589
1590
1591 yaml_b64 = db.Column(db.Text)
1592
1593
1594 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1595 copr = db.relationship("Copr", backref=db.backref("modules"))
1596
1597 __table_args__ = (
1598 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"),
1599 )
1600
1601 @property
1603 return base64.b64decode(self.yaml_b64)
1604
1605 @property
1607 mmd = Modulemd.ModuleStream()
1608 mmd.import_from_string(self.yaml.decode("utf-8"))
1609 return mmd
1610
1611 @property
1614
1615 @property
1618
1619 @property
1622
1623 @property
1625 """
1626 Return numeric representation of status of this build
1627 """
1628 if self.action:
1629 return { BackendResultEnum("success"): ModuleStatusEnum("succeeded"),
1630 BackendResultEnum("failure"): ModuleStatusEnum("failed"),
1631 BackendResultEnum("waiting"): ModuleStatusEnum("waiting"),
1632 }[self.action.result]
1633 build_statuses = [b.status for b in self.builds]
1634 for state in ["canceled", "running", "starting", "pending", "failed", "succeeded"]:
1635 if ModuleStatusEnum(state) in build_statuses:
1636 return ModuleStatusEnum(state)
1637
1638 @property
1640 """
1641 Return text representation of status of this build
1642 """
1643 return ModuleStatusEnum(self.status)
1644
1645 @property
1648
1649 @property
1652
1653 @property
1655 return {k: v.get_rpms().get() for k, v in self.modulemd.get_profiles().items()}
1656
1663
1670