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, index=True, 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
913
914 @property
919
920 @property
923
924 @property
932
933 @property
936
937 @property
944
945 @property
948
949 @property
952
953 @property
956
957 @property
966
967 @property
970
972 """
973 Get build chroots with states which present in `states` list
974 If states == None, function returns build_chroots
975 """
976 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
977 if statuses is not None:
978 statuses = set(statuses)
979 else:
980 return self.build_chroots
981
982 return [
983 chroot for chroot, status in chroot_states_map.items()
984 if status in statuses
985 ]
986
987 @property
989 return {b.name: b for b in self.build_chroots}
990
991 @property
993 """
994 Return build status.
995 """
996 if self.canceled:
997 return StatusEnum("canceled")
998
999 use_src_statuses = ["starting", "pending", "running", "importing", "failed"]
1000 if self.source_status in [StatusEnum(s) for s in use_src_statuses]:
1001 return self.source_status
1002
1003 if not self.chroot_states:
1004
1005
1006
1007
1008
1009
1010
1011 app.logger.error("Build %s has source_status succeeded, but "
1012 "no build_chroots", self.id)
1013 return StatusEnum("waiting")
1014
1015 for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked"]:
1016 if StatusEnum(state) in self.chroot_states:
1017 return StatusEnum(state)
1018
1019 if StatusEnum("waiting") in self.chroot_states:
1020
1021
1022
1023
1024 app.logger.error("Build chroots pending, even though build %s"
1025 " has succeeded source_status", self.id)
1026 return StatusEnum("pending")
1027
1028 return None
1029
1030 @property
1032 """
1033 Return text representation of status of this build.
1034 """
1035 if self.status != None:
1036 return StatusEnum(self.status)
1037 return "unknown"
1038
1039 @property
1041 """
1042 Find out if this build is cancelable.
1043 """
1044 return not self.finished and self.status != StatusEnum("starting")
1045
1046 @property
1048 """
1049 Find out if this build is repeatable.
1050
1051 Build is repeatable only if sources has been imported.
1052 """
1053 return self.source_status == StatusEnum("succeeded")
1054
1055 @property
1057 """
1058 Find out if this build is in finished state.
1059
1060 Build is finished only if all its build_chroots are in finished state or
1061 the build was canceled.
1062 """
1063 if self.canceled:
1064 return True
1065 if not self.build_chroots:
1066 return StatusEnum(self.source_status) in helpers.FINISHED_STATUSES
1067 return all([chroot.finished for chroot in self.build_chroots])
1068
1069 @property
1072
1073 @property
1075 """
1076 Find out if this build is persistent.
1077
1078 This property is inherited from the project.
1079 """
1080 return self.copr.persistent
1081
1082 @property
1084 try:
1085 return self.package.name
1086 except:
1087 return None
1088
1089 - def to_dict(self, options=None, with_chroot_states=False):
1102
1103 @property
1105 """
1106 Return tuple (submitter_string, submitter_link), while the
1107 submitter_link may be empty if we are not able to detect it
1108 wisely.
1109 """
1110 if self.user:
1111 user = self.user.name
1112 return (user, url_for('coprs_ns.coprs_by_user', username=user))
1113
1114 if self.submitted_by:
1115 links = ['http://', 'https://']
1116 if any([self.submitted_by.startswith(x) for x in links]):
1117 return (self.submitted_by, self.submitted_by)
1118
1119 return (self.submitted_by, None)
1120
1121 return (None, None)
1122
1123 @property
1125 """
1126 Return a string unique to project + submitter. At this level copr
1127 backend later applies builder user-VM separation policy (VMs are only
1128 re-used for builds which have the same build.sandbox value)
1129 """
1130 submitter, _ = self.submitter
1131 if not submitter:
1132
1133
1134 submitter = uuid.uuid4()
1135
1136 return '{0}--{1}'.format(self.copr.full_name, submitter)
1137
1140 """
1141 1:N mapping: branch -> chroots
1142 """
1143
1144
1145 name = db.Column(db.String(50), primary_key=True)
1146
1147
1148 -class MockChroot(db.Model, helpers.Serializer):
1149 """
1150 Representation of mock chroot
1151 """
1152
1153 __table_args__ = (
1154 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
1155 )
1156
1157 id = db.Column(db.Integer, primary_key=True)
1158
1159 os_release = db.Column(db.String(50), nullable=False)
1160
1161 os_version = db.Column(db.String(50), nullable=False)
1162
1163 arch = db.Column(db.String(50), nullable=False)
1164 is_active = db.Column(db.Boolean, default=True)
1165
1166
1167 distgit_branch_name = db.Column(db.String(50),
1168 db.ForeignKey("dist_git_branch.name"),
1169 nullable=False)
1170
1171 distgit_branch = db.relationship("DistGitBranch",
1172 backref=db.backref("chroots"))
1173
1174
1175
1176 final_prunerepo_done = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
1177
1178 @classmethod
1187
1188 @property
1190 """
1191 Textual representation of name of this chroot
1192 """
1193 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
1194
1195 @property
1197 """
1198 Textual representation of name of this or release
1199 """
1200 return "{}-{}".format(self.os_release, self.os_version)
1201
1202 @property
1204 """
1205 Textual representation of the operating system name
1206 """
1207 return "{0} {1}".format(self.os_release, self.os_version)
1208
1209 @property
1214
1215
1216 -class CoprChroot(db.Model, helpers.Serializer):
1217 """
1218 Representation of Copr<->MockChroot relation
1219 """
1220
1221 buildroot_pkgs = db.Column(db.Text)
1222 repos = db.Column(db.Text, default="", server_default="", nullable=False)
1223 mock_chroot_id = db.Column(
1224 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
1225 mock_chroot = db.relationship(
1226 "MockChroot", backref=db.backref("copr_chroots"))
1227 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
1228 copr = db.relationship("Copr",
1229 backref=db.backref(
1230 "copr_chroots",
1231 single_parent=True,
1232 cascade="all,delete,delete-orphan"))
1233
1234 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
1235 comps_name = db.Column(db.String(127), nullable=True)
1236
1237 with_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1238 without_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1239
1240
1241
1242 delete_after = db.Column(db.DateTime, index=True)
1243 delete_notify = db.Column(db.DateTime, index=True)
1244
1246 if isinstance(comps_xml, str):
1247 data = comps_xml.encode("utf-8")
1248 else:
1249 data = comps_xml
1250 self.comps_zlib = zlib.compress(data)
1251
1252 @property
1255
1256 @property
1258 return (self.repos or "").split()
1259
1260 @property
1264
1265 @property
1271
1272 @property
1275
1276 @property
1279
1280 @property
1282 if not self.delete_after:
1283 return None
1284 now = datetime.datetime.now()
1285 days = (self.delete_after - now).days
1286 return days if days > 0 else 0
1287
1289 options = {"__columns_only__": [
1290 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts"
1291 ]}
1292 d = super(CoprChroot, self).to_dict(options=options)
1293 d["mock_chroot"] = self.mock_chroot.name
1294 return d
1295
1298 """
1299 Representation of Build<->MockChroot relation
1300 """
1301
1302 __table_args__ = (db.Index('build_chroot_status_started_on_idx', "status", "started_on"),)
1303
1304 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
1305 primary_key=True)
1306 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
1307 build_id = db.Column(db.Integer, db.ForeignKey("build.id", ondelete="CASCADE"),
1308 primary_key=True)
1309 build = db.relationship("Build", backref=db.backref("build_chroots", cascade="all, delete-orphan",
1310 passive_deletes=True))
1311 git_hash = db.Column(db.String(40))
1312 status = db.Column(db.Integer, default=StatusEnum("waiting"))
1313
1314 started_on = db.Column(db.Integer, index=True)
1315 ended_on = db.Column(db.Integer, index=True)
1316
1317
1318 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
1319
1320 build_requires = db.Column(db.Text)
1321
1322 @property
1324 """
1325 Textual representation of name of this chroot
1326 """
1327 return self.mock_chroot.name
1328
1329 @property
1331 """
1332 Return text representation of status of this build chroot
1333 """
1334 if self.status is not None:
1335 return StatusEnum(self.status)
1336 return "unknown"
1337
1338 @property
1341
1342 @property
1345
1346 @property
1360
1361 @property
1367
1368
1369 -class LegalFlag(db.Model, helpers.Serializer):
1370 id = db.Column(db.Integer, primary_key=True)
1371
1372 raise_message = db.Column(db.Text)
1373
1374 raised_on = db.Column(db.Integer)
1375
1376 resolved_on = db.Column(db.Integer)
1377
1378
1379 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1380
1381 copr = db.relationship(
1382 "Copr", backref=db.backref("legal_flags", cascade="all"))
1383
1384 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1385 reporter = db.relationship("User",
1386 backref=db.backref("legal_flags_raised"),
1387 foreign_keys=[reporter_id],
1388 primaryjoin="LegalFlag.reporter_id==User.id")
1389
1390 resolver_id = db.Column(
1391 db.Integer, db.ForeignKey("user.id"), nullable=True)
1392 resolver = db.relationship("User",
1393 backref=db.backref("legal_flags_resolved"),
1394 foreign_keys=[resolver_id],
1395 primaryjoin="LegalFlag.resolver_id==User.id")
1396
1397
1398 -class Action(db.Model, helpers.Serializer):
1399 """
1400 Representation of a custom action that needs
1401 backends cooperation/admin attention/...
1402 """
1403
1404 id = db.Column(db.Integer, primary_key=True)
1405
1406 action_type = db.Column(db.Integer, nullable=False)
1407
1408 object_type = db.Column(db.String(20))
1409
1410 object_id = db.Column(db.Integer)
1411
1412 old_value = db.Column(db.String(255))
1413 new_value = db.Column(db.String(255))
1414
1415 data = db.Column(db.Text)
1416
1417 result = db.Column(
1418 db.Integer, default=BackendResultEnum("waiting"))
1419
1420 message = db.Column(db.Text)
1421
1422 created_on = db.Column(db.Integer)
1423
1424 ended_on = db.Column(db.Integer)
1425
1428
1437
1450
1451
1452 -class Krb5Login(db.Model, helpers.Serializer):
1453 """
1454 Represents additional user information for kerberos authentication.
1455 """
1456
1457 __tablename__ = "krb5_login"
1458
1459
1460 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1461
1462
1463 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1464
1465
1466 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1467
1468 user = db.relationship("User", backref=db.backref("krb5_logins"))
1469
1472 """
1473 Generic store for simple statistics.
1474 """
1475
1476 name = db.Column(db.String(127), primary_key=True)
1477 counter_type = db.Column(db.String(30))
1478
1479 counter = db.Column(db.Integer, default=0, server_default="0")
1480
1481
1482 -class Group(db.Model, helpers.Serializer):
1483
1484 """
1485 Represents FAS groups and their aliases in Copr
1486 """
1487
1488 id = db.Column(db.Integer, primary_key=True)
1489 name = db.Column(db.String(127))
1490
1491
1492 fas_name = db.Column(db.String(127))
1493
1494 @property
1496 return u"@{}".format(self.name)
1497
1500
1503
1504
1505 -class Batch(db.Model):
1506 id = db.Column(db.Integer, primary_key=True)
1507 blocked_by_id = db.Column(db.Integer, db.ForeignKey("batch.id"), nullable=True)
1508 blocked_by = db.relationship("Batch", remote_side=[id])
1509
1510 @property
1513
1514
1515 -class Module(db.Model, helpers.Serializer):
1516 id = db.Column(db.Integer, primary_key=True)
1517 name = db.Column(db.String(100), nullable=False)
1518 stream = db.Column(db.String(100), nullable=False)
1519 version = db.Column(db.BigInteger, nullable=False)
1520 summary = db.Column(db.String(100), nullable=False)
1521 description = db.Column(db.Text)
1522 created_on = db.Column(db.Integer, nullable=True)
1523
1524
1525
1526
1527
1528
1529
1530 yaml_b64 = db.Column(db.Text)
1531
1532
1533 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1534 copr = db.relationship("Copr", backref=db.backref("modules"))
1535
1536 __table_args__ = (
1537 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"),
1538 )
1539
1540 @property
1542 return base64.b64decode(self.yaml_b64)
1543
1544 @property
1546 mmd = Modulemd.ModuleStream()
1547 mmd.import_from_string(self.yaml.decode("utf-8"))
1548 return mmd
1549
1550 @property
1553
1554 @property
1557
1558 @property
1561
1562 @property
1564 """
1565 Return numeric representation of status of this build
1566 """
1567 if self.action:
1568 return { BackendResultEnum("success"): ModuleStatusEnum("succeeded"),
1569 BackendResultEnum("failure"): ModuleStatusEnum("failed"),
1570 BackendResultEnum("waiting"): ModuleStatusEnum("waiting"),
1571 }[self.action.result]
1572 build_statuses = [b.status for b in self.builds]
1573 for state in ["canceled", "running", "starting", "pending", "failed", "succeeded"]:
1574 if ModuleStatusEnum(state) in build_statuses:
1575 return ModuleStatusEnum(state)
1576
1577 @property
1579 """
1580 Return text representation of status of this build
1581 """
1582 return ModuleStatusEnum(self.status)
1583
1584 @property
1587
1588 @property
1591
1592 @property
1594 return {k: v.get_rpms().get() for k, v in self.modulemd.get_profiles().items()}
1595
1602