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 @property
886
887 @property
890
891 @property
894
895 @property
898
899 @property
902
903 @property
904 - def fail_type_text(self):
905 return FailTypeEnum(self.fail_type)
906
907 @property
909 if self.repos is None:
910 return list()
911 else:
912 return self.repos.split()
913
914 @property
917
918 @property
920 return "{:08d}".format(self.id)
921
927
928 @property
930 if app.config["COPR_DIST_GIT_LOGS_URL"]:
931 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
932 self.task_id.replace('/', '_'))
933 return None
934
935 @property
943
944 @property
949
950 @property
953
954 @property
962
963 @property
966
967 @property
974
975 @property
978
979 @property
982
983 @property
986
987 @property
996
997 @property
1000
1002 """
1003 Get build chroots with states which present in `states` list
1004 If states == None, function returns build_chroots
1005 """
1006 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
1007 if statuses is not None:
1008 statuses = set(statuses)
1009 else:
1010 return self.build_chroots
1011
1012 return [
1013 chroot for chroot, status in chroot_states_map.items()
1014 if status in statuses
1015 ]
1016
1017 @property
1019 return {b.name: b for b in self.build_chroots}
1020
1021 @property
1023 """
1024 Return build status.
1025 """
1026 if self.canceled:
1027 return StatusEnum("canceled")
1028
1029 use_src_statuses = ["starting", "pending", "running", "importing", "failed"]
1030 if self.source_status in [StatusEnum(s) for s in use_src_statuses]:
1031 return self.source_status
1032
1033 if not self.chroot_states:
1034
1035
1036
1037
1038
1039
1040
1041 app.logger.error("Build %s has source_status succeeded, but "
1042 "no build_chroots", self.id)
1043 return StatusEnum("waiting")
1044
1045 for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked"]:
1046 if StatusEnum(state) in self.chroot_states:
1047 return StatusEnum(state)
1048
1049 if StatusEnum("waiting") in self.chroot_states:
1050
1051
1052
1053
1054 app.logger.error("Build chroots pending, even though build %s"
1055 " has succeeded source_status", self.id)
1056 return StatusEnum("pending")
1057
1058 return None
1059
1060 @property
1062 """
1063 Return text representation of status of this build.
1064 """
1065 if self.status != None:
1066 return StatusEnum(self.status)
1067 return "unknown"
1068
1069 @property
1071 """
1072 Find out if this build is cancelable.
1073 """
1074 return not self.finished and self.status != StatusEnum("starting")
1075
1076 @property
1078 """
1079 Find out if this build is repeatable.
1080
1081 Build is repeatable only if sources has been imported.
1082 """
1083 return self.source_status == StatusEnum("succeeded")
1084
1085 @property
1087 """
1088 Find out if this build is in finished state.
1089
1090 Build is finished only if all its build_chroots are in finished state or
1091 the build was canceled.
1092 """
1093 if self.canceled:
1094 return True
1095 if not self.build_chroots:
1096 return StatusEnum(self.source_status) in helpers.FINISHED_STATUSES
1097 return all([chroot.finished for chroot in self.build_chroots])
1098
1099 @property
1102
1103 @property
1105 """
1106 Find out if this build is persistent.
1107
1108 This property is inherited from the project.
1109 """
1110 return self.copr.persistent
1111
1112 @property
1114 try:
1115 return self.package.name
1116 except:
1117 return None
1118
1119 - def to_dict(self, options=None, with_chroot_states=False):
1132
1133 @property
1135 """
1136 Return tuple (submitter_string, submitter_link), while the
1137 submitter_link may be empty if we are not able to detect it
1138 wisely.
1139 """
1140 if self.user:
1141 user = self.user.name
1142 return (user, url_for('coprs_ns.coprs_by_user', username=user))
1143
1144 if self.submitted_by:
1145 links = ['http://', 'https://']
1146 if any([self.submitted_by.startswith(x) for x in links]):
1147 return (self.submitted_by, self.submitted_by)
1148
1149 return (self.submitted_by, None)
1150
1151 return (None, None)
1152
1153 @property
1155 """
1156 Return a string unique to project + submitter. At this level copr
1157 backend later applies builder user-VM separation policy (VMs are only
1158 re-used for builds which have the same build.sandbox value)
1159 """
1160 submitter, _ = self.submitter
1161 if not submitter:
1162
1163
1164 submitter = uuid.uuid4()
1165
1166 return '{0}--{1}'.format(self.copr.full_name, submitter)
1167
1168 @property
1171
1172 @property
1175
1178 """
1179 1:N mapping: branch -> chroots
1180 """
1181
1182
1183 name = db.Column(db.String(50), primary_key=True)
1184
1185
1186 -class MockChroot(db.Model, helpers.Serializer):
1187 """
1188 Representation of mock chroot
1189 """
1190
1191 __table_args__ = (
1192 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
1193 )
1194
1195 id = db.Column(db.Integer, primary_key=True)
1196
1197 os_release = db.Column(db.String(50), nullable=False)
1198
1199 os_version = db.Column(db.String(50), nullable=False)
1200
1201 arch = db.Column(db.String(50), nullable=False)
1202 is_active = db.Column(db.Boolean, default=True)
1203
1204
1205 distgit_branch_name = db.Column(db.String(50),
1206 db.ForeignKey("dist_git_branch.name"),
1207 nullable=False)
1208
1209 distgit_branch = db.relationship("DistGitBranch",
1210 backref=db.backref("chroots"))
1211
1212
1213
1214 final_prunerepo_done = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
1215
1216 comment = db.Column(db.Text, nullable=True)
1217
1218 multilib_pairs = {
1219 'x86_64': 'i386',
1220 }
1221
1222 @classmethod
1231
1232 @property
1234 """
1235 Textual representation of name of this chroot
1236 """
1237 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
1238
1239 @property
1241 """
1242 Textual representation of name of this or release
1243 """
1244 return "{}-{}".format(self.os_release, self.os_version)
1245
1246 @property
1248 """
1249 Textual representation of the operating system name
1250 """
1251 return "{0} {1}".format(self.os_release, self.os_version)
1252
1253 @property
1258
1259
1260 -class CoprChroot(db.Model, helpers.Serializer):
1261 """
1262 Representation of Copr<->MockChroot relation
1263 """
1264
1265 buildroot_pkgs = db.Column(db.Text)
1266 repos = db.Column(db.Text, default="", server_default="", nullable=False)
1267 mock_chroot_id = db.Column(
1268 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
1269 mock_chroot = db.relationship(
1270 "MockChroot", backref=db.backref("copr_chroots"))
1271 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
1272 copr = db.relationship("Copr",
1273 backref=db.backref(
1274 "copr_chroots",
1275 single_parent=True,
1276 cascade="all,delete,delete-orphan"))
1277
1278 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
1279 comps_name = db.Column(db.String(127), nullable=True)
1280
1281 with_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1282 without_opts = db.Column(db.Text, default="", server_default="", nullable=False)
1283
1284
1285
1286 delete_after = db.Column(db.DateTime, index=True)
1287 delete_notify = db.Column(db.DateTime, index=True)
1288
1290 if isinstance(comps_xml, str):
1291 data = comps_xml.encode("utf-8")
1292 else:
1293 data = comps_xml
1294 self.comps_zlib = zlib.compress(data)
1295
1296 @property
1299
1300 @property
1302 return (self.repos or "").split()
1303
1304 @property
1308
1309 @property
1315
1316 @property
1319
1320 @property
1323
1324 @property
1326 if not self.delete_after:
1327 return None
1328 now = datetime.datetime.now()
1329 days = (self.delete_after - now).days
1330 return days if days > 0 else 0
1331
1333 options = {"__columns_only__": [
1334 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts"
1335 ]}
1336 d = super(CoprChroot, self).to_dict(options=options)
1337 d["mock_chroot"] = self.mock_chroot.name
1338 return d
1339
1342 """
1343 Representation of Build<->MockChroot relation
1344 """
1345
1346 __table_args__ = (db.Index('build_chroot_status_started_on_idx', "status", "started_on"),)
1347
1348 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
1349 primary_key=True)
1350 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
1351 build_id = db.Column(db.Integer, db.ForeignKey("build.id", ondelete="CASCADE"),
1352 primary_key=True)
1353 build = db.relationship("Build", backref=db.backref("build_chroots", cascade="all, delete-orphan",
1354 passive_deletes=True))
1355 git_hash = db.Column(db.String(40))
1356 status = db.Column(db.Integer, default=StatusEnum("waiting"))
1357
1358 started_on = db.Column(db.Integer, index=True)
1359 ended_on = db.Column(db.Integer, index=True)
1360
1361
1362 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
1363
1364 build_requires = db.Column(db.Text)
1365
1366 @property
1368 """
1369 Textual representation of name of this chroot
1370 """
1371 return self.mock_chroot.name
1372
1373 @property
1375 """
1376 Return text representation of status of this build chroot
1377 """
1378 if self.status is not None:
1379 return StatusEnum(self.status)
1380 return "unknown"
1381
1382 @property
1385
1386 @property
1389
1390 @property
1404
1405 @property
1411
1412 @property
1414 if not self.build.package:
1415 return None
1416
1417 if not (self.finished or self.state == "running"):
1418 return None
1419
1420 if not self.result_dir_url:
1421 return None
1422
1423 return os.path.join(self.result_dir_url,
1424 "builder-live.log" if self.state == 'running' else "builder-live.log.gz")
1425
1426
1427 -class LegalFlag(db.Model, helpers.Serializer):
1428 id = db.Column(db.Integer, primary_key=True)
1429
1430 raise_message = db.Column(db.Text)
1431
1432 raised_on = db.Column(db.Integer)
1433
1434 resolved_on = db.Column(db.Integer)
1435
1436
1437 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1438
1439 copr = db.relationship(
1440 "Copr", backref=db.backref("legal_flags", cascade="all"))
1441
1442 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1443 reporter = db.relationship("User",
1444 backref=db.backref("legal_flags_raised"),
1445 foreign_keys=[reporter_id],
1446 primaryjoin="LegalFlag.reporter_id==User.id")
1447
1448 resolver_id = db.Column(
1449 db.Integer, db.ForeignKey("user.id"), nullable=True)
1450 resolver = db.relationship("User",
1451 backref=db.backref("legal_flags_resolved"),
1452 foreign_keys=[resolver_id],
1453 primaryjoin="LegalFlag.resolver_id==User.id")
1454
1455
1456 -class Action(db.Model, helpers.Serializer):
1457 """
1458 Representation of a custom action that needs
1459 backends cooperation/admin attention/...
1460 """
1461
1462 id = db.Column(db.Integer, primary_key=True)
1463
1464 action_type = db.Column(db.Integer, nullable=False)
1465
1466 object_type = db.Column(db.String(20))
1467
1468 object_id = db.Column(db.Integer)
1469
1470 old_value = db.Column(db.String(255))
1471 new_value = db.Column(db.String(255))
1472
1473 data = db.Column(db.Text)
1474
1475 result = db.Column(
1476 db.Integer, default=BackendResultEnum("waiting"))
1477
1478 message = db.Column(db.Text)
1479
1480 created_on = db.Column(db.Integer)
1481
1482 ended_on = db.Column(db.Integer)
1483
1486
1495
1508
1509
1510 -class Krb5Login(db.Model, helpers.Serializer):
1511 """
1512 Represents additional user information for kerberos authentication.
1513 """
1514
1515 __tablename__ = "krb5_login"
1516
1517
1518 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1519
1520
1521 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1522
1523
1524 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1525
1526 user = db.relationship("User", backref=db.backref("krb5_logins"))
1527
1530 """
1531 Generic store for simple statistics.
1532 """
1533
1534 name = db.Column(db.String(127), primary_key=True)
1535 counter_type = db.Column(db.String(30))
1536
1537 counter = db.Column(db.Integer, default=0, server_default="0")
1538
1539
1540 -class Group(db.Model, helpers.Serializer):
1541
1542 """
1543 Represents FAS groups and their aliases in Copr
1544 """
1545
1546 id = db.Column(db.Integer, primary_key=True)
1547 name = db.Column(db.String(127))
1548
1549
1550 fas_name = db.Column(db.String(127))
1551
1552 @property
1554 return u"@{}".format(self.name)
1555
1558
1561
1562
1563 -class Batch(db.Model):
1564 id = db.Column(db.Integer, primary_key=True)
1565 blocked_by_id = db.Column(db.Integer, db.ForeignKey("batch.id"), nullable=True)
1566 blocked_by = db.relationship("Batch", remote_side=[id])
1567
1568 @property
1571
1572
1573 -class Module(db.Model, helpers.Serializer):
1574 id = db.Column(db.Integer, primary_key=True)
1575 name = db.Column(db.String(100), nullable=False)
1576 stream = db.Column(db.String(100), nullable=False)
1577 version = db.Column(db.BigInteger, nullable=False)
1578 summary = db.Column(db.String(100), nullable=False)
1579 description = db.Column(db.Text)
1580 created_on = db.Column(db.Integer, nullable=True)
1581
1582
1583
1584
1585
1586
1587
1588 yaml_b64 = db.Column(db.Text)
1589
1590
1591 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1592 copr = db.relationship("Copr", backref=db.backref("modules"))
1593
1594 __table_args__ = (
1595 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"),
1596 )
1597
1598 @property
1600 return base64.b64decode(self.yaml_b64)
1601
1602 @property
1604 mmd = Modulemd.ModuleStream()
1605 mmd.import_from_string(self.yaml.decode("utf-8"))
1606 return mmd
1607
1608 @property
1611
1612 @property
1615
1616 @property
1619
1620 @property
1622 """
1623 Return numeric representation of status of this build
1624 """
1625 if self.action:
1626 return { BackendResultEnum("success"): ModuleStatusEnum("succeeded"),
1627 BackendResultEnum("failure"): ModuleStatusEnum("failed"),
1628 BackendResultEnum("waiting"): ModuleStatusEnum("waiting"),
1629 }[self.action.result]
1630 build_statuses = [b.status for b in self.builds]
1631 for state in ["canceled", "running", "starting", "pending", "failed", "succeeded"]:
1632 if ModuleStatusEnum(state) in build_statuses:
1633 return ModuleStatusEnum(state)
1634
1635 @property
1637 """
1638 Return text representation of status of this build
1639 """
1640 return ModuleStatusEnum(self.status)
1641
1642 @property
1645
1646 @property
1649
1650 @property
1652 return {k: v.get_rpms().get() for k, v in self.modulemd.get_profiles().items()}
1653
1660