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
279 """
280 Represents private part of a single copr (personal repo with builds, mock
281 chroots, etc.).
282 """
283
284 __table_args__ = (
285 db.Index('copr_private_webhook_secret', 'webhook_secret'),
286 )
287
288
289 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True,
290 nullable=False, primary_key=True)
291
292
293 webhook_secret = db.Column(db.String(100))
294
295
296 scm_api_auth_json = db.Column(db.Text)
297
298
299 -class Copr(db.Model, helpers.Serializer):
300 """
301 Represents private a single copr (personal repo with builds, mock chroots,
302 etc.).
303 """
304
305
306
307 __table__ = outerjoin(_CoprPublic.__table__, _CoprPrivate.__table__)
308 id = column_property(
309 _CoprPublic.__table__.c.id,
310 _CoprPrivate.__table__.c.copr_id
311 )
312
313
314 user = db.relationship("User", backref=db.backref("coprs"))
315 group = db.relationship("Group", backref=db.backref("groups"))
316 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
317 forked_from = db.relationship("Copr",
318 remote_side=_CoprPublic.id,
319 foreign_keys=[_CoprPublic.forked_from_id],
320 backref=db.backref("all_forks"))
321
322 @property
324 return [fork for fork in self.all_forks if not fork.deleted]
325
326 @property
327 - def main_dir(self):
328 """
329 Return main copr dir for a Copr
330 """
331 return CoprDir.query.filter(CoprDir.copr_id==self.id).filter(CoprDir.main==True).one()
332
333 @property
338
339 @property
341 """
342 Return True if copr belongs to a group
343 """
344 return self.group is not None
345
346 @property
352
353 @property
359
360 @property
362 """
363 Return repos of this copr as a list of strings
364 """
365 return self.repos.split()
366
367 @property
373
374 @property
376 """
377 :rtype: list of CoprChroot
378 """
379 return [c for c in self.copr_chroots if c.is_active]
380
381 @property
383 """
384 Return list of active mock_chroots of this copr
385 """
386 return sorted(self.active_chroots, key=lambda ch: ch.name)
387
388 @property
392
393 @property
395 """
396 Return list of active mock_chroots of this copr
397 """
398 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
399 output = []
400 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
401 output.append((os, [ch[1] for ch in chs]))
402
403 return output
404
405 @property
407 """
408 Return number of builds in this copr
409 """
410 return len(self.builds)
411
412 @property
415
416 @disable_createrepo.setter
419
420 @property
423
424 @property
436
442
443 @property
446
447 @property
450
451 @property
456
457 @property
459 return "-".join([self.owner_name.replace("@", "group_"), self.name])
460
461 @property
463 return "/".join([self.repo_url, "modules"])
464
465 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
466 result = {}
467 for key in ["id", "name", "description", "instructions"]:
468 result[key] = str(copy.copy(getattr(self, key)))
469 result["owner"] = self.owner_name
470 return result
471
472 @property
477
480
481 @property
484
485 @enable_net.setter
488
491
492 @property
494 if self.delete_after is None:
495 return None
496
497 delta = self.delete_after - datetime.datetime.now()
498 return delta.days if delta.days > 0 else 0
499
500 @delete_after_days.setter
509
510 @property
515
516 @property
523
525 """
526 Association class for Copr<->Permission relation
527 """
528
529
530
531 copr_builder = db.Column(db.SmallInteger, default=0)
532
533 copr_admin = db.Column(db.SmallInteger, default=0)
534
535
536 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
537 user = db.relationship("User", backref=db.backref("copr_permissions"))
538 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
539 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
540
542 if name == 'admin':
543 self.copr_admin = value
544 elif name == 'builder':
545 self.copr_builder = value
546 else:
547 raise KeyError("{0} is not a valid copr permission".format(name))
548
555
558 """
559 Represents one of data directories for a copr.
560 """
561 id = db.Column(db.Integer, primary_key=True)
562
563 name = db.Column(db.Text, index=True)
564 main = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
565
566 ownername = db.Column(db.Text, index=True, nullable=False)
567
568 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True, nullable=False)
569 copr = db.relationship("Copr", backref=db.backref("dirs"))
570
571 __table_args__ = (
572 db.Index('only_one_main_copr_dir', copr_id, main,
573 unique=True, postgresql_where=(main==True)),
574
575 db.UniqueConstraint('ownername', 'name',
576 name='ownername_copr_dir_uniq'),
577 )
578
583
584 @property
587
588 @property
591
592 @property
596
597 @property
603
604
605 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
606 """
607 Represents a single package in a project_dir.
608 """
609
610 __table_args__ = (
611 db.UniqueConstraint('copr_dir_id', 'name', name='packages_copr_dir_pkgname'),
612 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'),
613 )
614
619
620 id = db.Column(db.Integer, primary_key=True)
621 name = db.Column(db.String(100), nullable=False)
622
623 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
624
625 source_json = db.Column(db.Text)
626
627 webhook_rebuild = db.Column(db.Boolean, default=False)
628
629 enable_net = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
630
631
632 max_builds = db.Column(db.Integer, index=True)
633
634 @validates('max_builds')
636 return None if value == 0 else value
637
638 builds = db.relationship("Build", order_by="Build.id")
639
640
641 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True)
642 copr = db.relationship("Copr", backref=db.backref("packages"))
643
644 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)
645 copr_dir = db.relationship("CoprDir", backref=db.backref("packages"))
646
647
648
649 chroot_blacklist_raw = db.Column(db.Text)
650
651 @property
654
655 @property
660
661 @property
664
665 @property
667 """
668 Package's source type (and source_json) is being derived from its first build, which works except
669 for "link" and "upload" cases. Consider these being equivalent to source_type being unset.
670 """
671 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
672
673 @property
678
679 @property
685
691
692 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
693 package_dict = super(Package, self).to_dict()
694 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
695
696 if with_latest_build:
697 build = self.last_build(successful=False)
698 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
699 if with_latest_succeeded_build:
700 build = self.last_build(successful=True)
701 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
702 if with_all_builds:
703 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
704
705 return package_dict
706
709
710
711 @property
713 if not self.chroot_blacklist_raw:
714 return []
715
716 blacklisted = []
717 for pattern in self.chroot_blacklist_raw.split(','):
718 pattern = pattern.strip()
719 if not pattern:
720 continue
721 blacklisted.append(pattern)
722
723 return blacklisted
724
725
726 @staticmethod
728 for pattern in patterns:
729 if fnmatch(chroot.name, pattern):
730 return True
731 return False
732
733
734 @property
735 - def main_pkg(self):
736 if self.copr_dir.main:
737 return self
738
739 main_pkg = Package.query.filter_by(
740 name=self.name,
741 copr_dir_id=self.copr.main_dir.id
742 ).first()
743 return main_pkg
744
745
746 @property
758
759
760 -class Build(db.Model, helpers.Serializer):
761 """
762 Representation of one build in one copr
763 """
764
765 SCM_COMMIT = 'commit'
766 SCM_PULL_REQUEST = 'pull-request'
767
768 __table_args__ = (db.Index('build_canceled', "canceled"),
769 db.Index('build_order', "is_background", "id"),
770 db.Index('build_filter', "source_type", "canceled"),
771 db.Index('build_canceled_is_background_source_status_id_idx', 'canceled', "is_background", "source_status", "id"),
772 )
773
787
788 id = db.Column(db.Integer, primary_key=True)
789
790 pkgs = db.Column(db.Text)
791
792 built_packages = db.Column(db.Text)
793
794 pkg_version = db.Column(db.Text)
795
796 canceled = db.Column(db.Boolean, default=False)
797
798 repos = db.Column(db.Text)
799
800
801 submitted_on = db.Column(db.Integer, nullable=False)
802
803 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
804
805 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
806
807 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
808
809 enable_net = db.Column(db.Boolean, default=False,
810 server_default="0", nullable=False)
811
812 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
813
814 source_json = db.Column(db.Text)
815
816 fail_type = db.Column(db.Integer, default=FailTypeEnum("unset"))
817
818 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
819
820 source_status = db.Column(db.Integer, default=StatusEnum("waiting"))
821 srpm_url = db.Column(db.Text)
822
823
824 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True)
825 user = db.relationship("User", backref=db.backref("builds"))
826 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True)
827 copr = db.relationship("Copr", backref=db.backref("builds"))
828 package_id = db.Column(db.Integer, db.ForeignKey("package.id"), index=True)
829 package = db.relationship("Package")
830
831 chroots = association_proxy("build_chroots", "mock_chroot")
832
833 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
834 batch = db.relationship("Batch", backref=db.backref("builds"))
835
836 module_id = db.Column(db.Integer, db.ForeignKey("module.id"), index=True)
837 module = db.relationship("Module", backref=db.backref("builds"))
838
839 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)
840 copr_dir = db.relationship("CoprDir", backref=db.backref("builds"))
841
842
843 scm_object_id = db.Column(db.Text)
844 scm_object_type = db.Column(db.Text)
845 scm_object_url = db.Column(db.Text)
846
847
848 update_callback = db.Column(db.Text)
849
850
851 submitted_by = db.Column(db.Text)
852
853 @property
856
857 @property
860
861 @property
864
865 @property
868
869 @property
872
873 @property
874 - def fail_type_text(self):
875 return FailTypeEnum(self.fail_type)
876
877 @property
879 if self.repos is None:
880 return list()
881 else:
882 return self.repos.split()
883
884 @property
887
888 @property
890 return "{:08d}".format(self.id)
891
897
898 @property
900 if app.config["COPR_DIST_GIT_LOGS_URL"]:
901 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
902 self.task_id.replace('/', '_'))
903 return None
904
905 @property
911
912 @property
917
918 @property
921
922 @property
930
931 @property
934
935 @property
942
943 @property
946
947 @property
950
951 @property
954
955 @property
964
965 @property
968
970 """
971 Get build chroots with states which present in `states` list
972 If states == None, function returns build_chroots
973 """
974 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
975 if statuses is not None:
976 statuses = set(statuses)
977 else:
978 return self.build_chroots
979
980 return [
981 chroot for chroot, status in chroot_states_map.items()
982 if status in statuses
983 ]
984
985 @property
987 return {b.name: b for b in self.build_chroots}
988
989 @property
991 """
992 Return build status.
993 """
994 if self.canceled:
995 return StatusEnum("canceled")
996
997 use_src_statuses = ["starting", "pending", "running", "importing", "failed"]
998 if self.source_status in [StatusEnum(s) for s in use_src_statuses]:
999 return self.source_status
1000
1001 if not self.chroot_states:
1002
1003
1004
1005
1006
1007
1008
1009 app.logger.error("Build %s has source_status succeeded, but "
1010 "no build_chroots", self.id)
1011 return StatusEnum("waiting")
1012
1013 for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked"]:
1014 if StatusEnum(state) in self.chroot_states:
1015 return StatusEnum(state)
1016
1017 if StatusEnum("waiting") in self.chroot_states:
1018
1019
1020
1021
1022 app.logger.error("Build chroots pending, even though build %s"
1023 " has succeeded source_status", self.id)
1024 return StatusEnum("pending")
1025
1026 return None
1027
1028 @property
1030 """
1031 Return text representation of status of this build.
1032 """
1033 if self.status != None:
1034 return StatusEnum(self.status)
1035 return "unknown"
1036
1037 @property
1039 """
1040 Find out if this build is cancelable.
1041 """
1042 return not self.finished and self.status != StatusEnum("starting")
1043
1044 @property
1046 """
1047 Find out if this build is repeatable.
1048
1049 Build is repeatable only if sources has been imported.
1050 """
1051 return self.source_status == StatusEnum("succeeded")
1052
1053 @property
1055 """
1056 Find out if this build is in finished state.
1057
1058 Build is finished only if all its build_chroots are in finished state or
1059 the build was canceled.
1060 """
1061 if self.canceled:
1062 return True
1063 if not self.build_chroots:
1064 return StatusEnum(self.source_status) in helpers.FINISHED_STATUSES
1065 return all([chroot.finished for chroot in self.build_chroots])
1066
1067 @property
1070
1071 @property
1073 """
1074 Find out if this build is persistent.
1075
1076 This property is inherited from the project.
1077 """
1078 return self.copr.persistent
1079
1080 @property
1082 try:
1083 return self.package.name
1084 except:
1085 return None
1086
1087 - def to_dict(self, options=None, with_chroot_states=False):
1100
1101 @property
1103 """
1104 Return tuple (submitter_string, submitter_link), while the
1105 submitter_link may be empty if we are not able to detect it
1106 wisely.
1107 """
1108 if self.user:
1109 user = self.user.name
1110 return (user, url_for('coprs_ns.coprs_by_user', username=user))
1111
1112 if self.submitted_by:
1113 links = ['http://', 'https://']
1114 if any([self.submitted_by.startswith(x) for x in links]):
1115 return (self.submitted_by, self.submitted_by)
1116
1117 return (self.submitted_by, None)
1118
1119 return (None, None)
1120
1121 @property
1123 """
1124 Return a string unique to project + submitter. At this level copr
1125 backend later applies builder user-VM separation policy (VMs are only
1126 re-used for builds which have the same build.sandbox value)
1127 """
1128 submitter, _ = self.submitter
1129 if not submitter:
1130
1131
1132 submitter = uuid.uuid4()
1133
1134 return '{0}--{1}'.format(self.copr.full_name, submitter)
1135
1138 """
1139 1:N mapping: branch -> chroots
1140 """
1141
1142
1143 name = db.Column(db.String(50), primary_key=True)
1144
1145
1146 -class MockChroot(db.Model, helpers.Serializer):
1147 """
1148 Representation of mock chroot
1149 """
1150
1151 __table_args__ = (
1152 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
1153 )
1154
1155 id = db.Column(db.Integer, primary_key=True)
1156
1157 os_release = db.Column(db.String(50), nullable=False)
1158
1159 os_version = db.Column(db.String(50), nullable=False)
1160
1161 arch = db.Column(db.String(50), nullable=False)
1162 is_active = db.Column(db.Boolean, default=True)
1163
1164
1165 distgit_branch_name = db.Column(db.String(50),
1166 db.ForeignKey("dist_git_branch.name"),
1167 nullable=False)
1168
1169 distgit_branch = db.relationship("DistGitBranch",
1170 backref=db.backref("chroots"))
1171
1172
1173
1174 final_prunerepo_done = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
1175
1176 @classmethod
1185
1186 @property
1188 """
1189 Textual representation of name of this chroot
1190 """
1191 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
1192
1193 @property
1195 """
1196 Textual representation of name of this or release
1197 """
1198 return "{}-{}".format(self.os_release, self.os_version)
1199
1200 @property
1202 """
1203 Textual representation of the operating system name
1204 """
1205 return "{0} {1}".format(self.os_release, self.os_version)
1206
1207 @property
1212
1213
1214 -class CoprChroot(db.Model, helpers.Serializer):
1215 """
1216 Representation of Copr<->MockChroot relation
1217 """
1218
1219 buildroot_pkgs = db.Column(db.Text)
1220 repos = db.Column(db.Text, default="", server_default="", nullable=False)
1221 mock_chroot_id = db.Column(
1222 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
1223 mock_chroot = db.relationship(
1224 "MockChroot", backref=db.backref("copr_chroots"))
1225 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
1226 copr = db.relationship("Copr",
1227 backref=db.backref(
1228 "copr_chroots",
1229 single_parent=True,
1230 cascade="all,delete,delete-orphan"))
1231
1232 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
1233 comps_name = db.Column(db.String(127), nullable=True)
1234
1235 with_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1236 without_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1237
1238
1239
1240 delete_after = db.Column(db.DateTime, index=True)
1241 delete_notify = db.Column(db.DateTime, index=True)
1242
1244 if isinstance(comps_xml, str):
1245 data = comps_xml.encode("utf-8")
1246 else:
1247 data = comps_xml
1248 self.comps_zlib = zlib.compress(data)
1249
1250 @property
1253
1254 @property
1256 return (self.repos or "").split()
1257
1258 @property
1262
1263 @property
1269
1270 @property
1273
1274 @property
1277
1278 @property
1280 if not self.delete_after:
1281 return None
1282 now = datetime.datetime.now()
1283 days = (self.delete_after - now).days
1284 return days if days > 0 else 0
1285
1287 options = {"__columns_only__": [
1288 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts"
1289 ]}
1290 d = super(CoprChroot, self).to_dict(options=options)
1291 d["mock_chroot"] = self.mock_chroot.name
1292 return d
1293
1296 """
1297 Representation of Build<->MockChroot relation
1298 """
1299
1300 __table_args__ = (db.Index('build_chroot_status_started_on_idx', "status", "started_on"),)
1301
1302 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
1303 primary_key=True)
1304 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
1305 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
1306 primary_key=True)
1307 build = db.relationship("Build", backref=db.backref("build_chroots"))
1308 git_hash = db.Column(db.String(40))
1309 status = db.Column(db.Integer, default=StatusEnum("waiting"))
1310
1311 started_on = db.Column(db.Integer, index=True)
1312 ended_on = db.Column(db.Integer, index=True)
1313
1314
1315 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
1316
1317 build_requires = db.Column(db.Text)
1318
1319 @property
1321 """
1322 Textual representation of name of this chroot
1323 """
1324 return self.mock_chroot.name
1325
1326 @property
1328 """
1329 Return text representation of status of this build chroot
1330 """
1331 if self.status is not None:
1332 return StatusEnum(self.status)
1333 return "unknown"
1334
1335 @property
1338
1339 @property
1342
1343 @property
1357
1358 @property
1364
1365
1366 -class LegalFlag(db.Model, helpers.Serializer):
1367 id = db.Column(db.Integer, primary_key=True)
1368
1369 raise_message = db.Column(db.Text)
1370
1371 raised_on = db.Column(db.Integer)
1372
1373 resolved_on = db.Column(db.Integer)
1374
1375
1376 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1377
1378 copr = db.relationship(
1379 "Copr", backref=db.backref("legal_flags", cascade="all"))
1380
1381 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1382 reporter = db.relationship("User",
1383 backref=db.backref("legal_flags_raised"),
1384 foreign_keys=[reporter_id],
1385 primaryjoin="LegalFlag.reporter_id==User.id")
1386
1387 resolver_id = db.Column(
1388 db.Integer, db.ForeignKey("user.id"), nullable=True)
1389 resolver = db.relationship("User",
1390 backref=db.backref("legal_flags_resolved"),
1391 foreign_keys=[resolver_id],
1392 primaryjoin="LegalFlag.resolver_id==User.id")
1393
1394
1395 -class Action(db.Model, helpers.Serializer):
1396 """
1397 Representation of a custom action that needs
1398 backends cooperation/admin attention/...
1399 """
1400
1401 id = db.Column(db.Integer, primary_key=True)
1402
1403 action_type = db.Column(db.Integer, nullable=False)
1404
1405 object_type = db.Column(db.String(20))
1406
1407 object_id = db.Column(db.Integer)
1408
1409 old_value = db.Column(db.String(255))
1410 new_value = db.Column(db.String(255))
1411
1412 data = db.Column(db.Text)
1413
1414 result = db.Column(
1415 db.Integer, default=BackendResultEnum("waiting"))
1416
1417 message = db.Column(db.Text)
1418
1419 created_on = db.Column(db.Integer)
1420
1421 ended_on = db.Column(db.Integer)
1422
1425
1434
1447
1448
1449 -class Krb5Login(db.Model, helpers.Serializer):
1450 """
1451 Represents additional user information for kerberos authentication.
1452 """
1453
1454 __tablename__ = "krb5_login"
1455
1456
1457 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1458
1459
1460 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1461
1462
1463 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1464
1465 user = db.relationship("User", backref=db.backref("krb5_logins"))
1466
1469 """
1470 Generic store for simple statistics.
1471 """
1472
1473 name = db.Column(db.String(127), primary_key=True)
1474 counter_type = db.Column(db.String(30))
1475
1476 counter = db.Column(db.Integer, default=0, server_default="0")
1477
1478
1479 -class Group(db.Model, helpers.Serializer):
1480
1481 """
1482 Represents FAS groups and their aliases in Copr
1483 """
1484
1485 id = db.Column(db.Integer, primary_key=True)
1486 name = db.Column(db.String(127))
1487
1488
1489 fas_name = db.Column(db.String(127))
1490
1491 @property
1493 return u"@{}".format(self.name)
1494
1497
1500
1501
1502 -class Batch(db.Model):
1503 id = db.Column(db.Integer, primary_key=True)
1504 blocked_by_id = db.Column(db.Integer, db.ForeignKey("batch.id"), nullable=True)
1505 blocked_by = db.relationship("Batch", remote_side=[id])
1506
1507 @property
1510
1511
1512 -class Module(db.Model, helpers.Serializer):
1513 id = db.Column(db.Integer, primary_key=True)
1514 name = db.Column(db.String(100), nullable=False)
1515 stream = db.Column(db.String(100), nullable=False)
1516 version = db.Column(db.BigInteger, nullable=False)
1517 summary = db.Column(db.String(100), nullable=False)
1518 description = db.Column(db.Text)
1519 created_on = db.Column(db.Integer, nullable=True)
1520
1521
1522
1523
1524
1525
1526
1527 yaml_b64 = db.Column(db.Text)
1528
1529
1530 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1531 copr = db.relationship("Copr", backref=db.backref("modules"))
1532
1533 __table_args__ = (
1534 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"),
1535 )
1536
1537 @property
1539 return base64.b64decode(self.yaml_b64)
1540
1541 @property
1543 mmd = Modulemd.ModuleStream()
1544 mmd.import_from_string(self.yaml.decode("utf-8"))
1545 return mmd
1546
1547 @property
1550
1551 @property
1554
1555 @property
1558
1559 @property
1561 """
1562 Return numeric representation of status of this build
1563 """
1564 if any(b for b in self.builds if b.status == StatusEnum("failed")):
1565 return ModuleStatusEnum("failed")
1566 return self.action.result if self.action else ModuleStatusEnum("pending")
1567
1568 @property
1570 """
1571 Return text representation of status of this build
1572 """
1573 return ModuleStatusEnum(self.status)
1574
1575 @property
1578
1579 @property
1582
1583 @property
1585 return {k: v.get_rpms().get() for k, v in self.modulemd.get_profiles().items()}
1586
1593