1 import copy
2 import datetime
3 import json
4 import os
5 import flask
6 import json
7 import base64
8 import modulemd
9
10 from sqlalchemy.ext.associationproxy import association_proxy
11 from six.moves.urllib.parse import urljoin
12 from libravatar import libravatar_url
13 import zlib
14
15 from coprs import constants
16 from coprs import db
17 from coprs import helpers
18 from coprs import app
19
20 import itertools
21 import operator
22 from coprs.helpers import BuildSourceEnum, StatusEnum, ActionTypeEnum, JSONEncodedDict
28
29
30 -class User(db.Model, helpers.Serializer):
31
32 """
33 Represents user of the copr frontend
34 """
35
36
37 id = db.Column(db.Integer, primary_key=True)
38
39
40 username = db.Column(db.String(100), nullable=False, unique=True)
41
42
43 mail = db.Column(db.String(150), nullable=False)
44
45
46 timezone = db.Column(db.String(50), nullable=True)
47
48
49
50 proven = db.Column(db.Boolean, default=False)
51
52
53 admin = db.Column(db.Boolean, default=False)
54
55
56 proxy = db.Column(db.Boolean, default=False)
57
58
59 api_login = db.Column(db.String(40), nullable=False, default="abc")
60 api_token = db.Column(db.String(40), nullable=False, default="abc")
61 api_token_expiration = db.Column(
62 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
63
64
65 openid_groups = db.Column(JSONEncodedDict)
66
67 @property
69 """
70 Return the short username of the user, e.g. bkabrda
71 """
72
73 return self.username
74
76 """
77 Get permissions of this user for the given copr.
78 Caches the permission during one request,
79 so use this if you access them multiple times
80 """
81
82 if not hasattr(self, "_permissions_for_copr"):
83 self._permissions_for_copr = {}
84 if copr.name not in self._permissions_for_copr:
85 self._permissions_for_copr[copr.name] = (
86 CoprPermission.query
87 .filter_by(user=self)
88 .filter_by(copr=copr)
89 .first()
90 )
91 return self._permissions_for_copr[copr.name]
92
112
113 @property
119
120 @property
123
125 """
126 :type group: Group
127 """
128 if group.fas_name in self.user_teams:
129 return True
130 else:
131 return False
132
151
152 @property
154
155 return ["id", "name"]
156
157 @property
159 """
160 Get number of coprs for this user.
161 """
162
163 return (Copr.query.filter_by(user=self).
164 filter_by(deleted=False).
165 filter_by(group_id=None).
166 count())
167
168 @property
170 """
171 Return url to libravatar image.
172 """
173
174 try:
175 return libravatar_url(email=self.mail, https=True)
176 except IOError:
177 return ""
178
179
180 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
181
182 """
183 Represents a single copr (private repo with builds, mock chroots, etc.).
184 """
185
186 __table_args__ = (
187 db.Index('copr_webhook_secret', 'webhook_secret'),
188 )
189
190 id = db.Column(db.Integer, primary_key=True)
191
192 name = db.Column(db.String(100), nullable=False)
193 homepage = db.Column(db.Text)
194 contact = db.Column(db.Text)
195
196
197 repos = db.Column(db.Text)
198
199 created_on = db.Column(db.Integer)
200
201 description = db.Column(db.Text)
202 instructions = db.Column(db.Text)
203 deleted = db.Column(db.Boolean, default=False)
204 playground = db.Column(db.Boolean, default=False)
205
206
207 auto_createrepo = db.Column(db.Boolean, default=True)
208
209
210 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
211 user = db.relationship("User", backref=db.backref("coprs"))
212 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
213 group = db.relationship("Group", backref=db.backref("groups"))
214 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
215 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
216 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
217
218
219 webhook_secret = db.Column(db.String(100))
220
221
222 build_enable_net = db.Column(db.Boolean, default=True,
223 server_default="1", nullable=False)
224
225 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
226
227
228 latest_indexed_data_update = db.Column(db.Integer)
229
230
231 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
232
233
234 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
235
236
237 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
238
239
240 follow_fedora_branching = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
241
242 __mapper_args__ = {
243 "order_by": created_on.desc()
244 }
245
246 @property
248 """
249 Return True if copr belongs to a group
250 """
251 return self.group_id is not None
252
253 @property
259
260 @property
266
267 @property
269 """
270 Return repos of this copr as a list of strings
271 """
272 return self.repos.split()
273
274 @property
281
282 @property
284 """
285 :rtype: list of CoprChroot
286 """
287 return [c for c in self.copr_chroots if c.is_active]
288
289 @property
291 """
292 Return list of active mock_chroots of this copr
293 """
294
295 return sorted(self.active_chroots, key=lambda ch: ch.name)
296
297 @property
299 """
300 Return list of active mock_chroots of this copr
301 """
302
303 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
304 output = []
305 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
306 output.append((os, [ch[1] for ch in chs]))
307
308 return output
309
310 @property
312 """
313 Return number of builds in this copr
314 """
315
316 return len(self.builds)
317
318 @property
322
323 @disable_createrepo.setter
327
328 @property
339
345
346 @property
349
350 @property
353
354 @property
359
360 @property
366
367 @property
369 return "/".join([self.repo_url, "modules"])
370
371 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
372 result = {}
373 for key in ["id", "name", "description", "instructions"]:
374 result[key] = str(copy.copy(getattr(self, key)))
375 result["owner"] = self.owner_name
376 return result
377
378 @property
383
386
389
390 """
391 Association class for Copr<->Permission relation
392 """
393
394
395
396 copr_builder = db.Column(db.SmallInteger, default=0)
397
398 copr_admin = db.Column(db.SmallInteger, default=0)
399
400
401 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
402 user = db.relationship("User", backref=db.backref("copr_permissions"))
403 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
404 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
405
406
407 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
408 """
409 Represents a single package in a project.
410 """
411 __table_args__ = (
412 db.UniqueConstraint('copr_id', 'name', name='packages_copr_pkgname'),
413 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'),
414 )
415
416 id = db.Column(db.Integer, primary_key=True)
417 name = db.Column(db.String(100), nullable=False)
418
419 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
420
421 source_json = db.Column(db.Text)
422
423 webhook_rebuild = db.Column(db.Boolean, default=False)
424
425 enable_net = db.Column(db.Boolean, default=False,
426 server_default="0", nullable=False)
427
428
429
430
431
432
433
434 old_status = db.Column(db.Integer)
435
436 builds = db.relationship("Build", order_by="Build.id")
437
438
439 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
440 copr = db.relationship("Copr", backref=db.backref("packages"))
441
442 @property
445
446 @property
451
452 @property
455
456 @property
458 """
459 Package's source type (and source_json) is being derived from its first build, which works except
460 for "link" and "upload" cases. Consider these being equivalent to source_type being unset.
461 """
462 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
463
464 @property
469
470 @property
476
482
483 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
484 package_dict = super(Package, self).to_dict()
485 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
486
487 if with_latest_build:
488 build = self.last_build(successful=False)
489 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
490 if with_latest_succeeded_build:
491 build = self.last_build(successful=True)
492 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
493 if with_all_builds:
494 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
495
496 return package_dict
497
500
501
502 -class Build(db.Model, helpers.Serializer):
503 """
504 Representation of one build in one copr
505 """
506 __table_args__ = (db.Index('build_canceled', "canceled"), )
507
508 id = db.Column(db.Integer, primary_key=True)
509
510 pkgs = db.Column(db.Text)
511
512 built_packages = db.Column(db.Text)
513
514 pkg_version = db.Column(db.Text)
515
516 canceled = db.Column(db.Boolean, default=False)
517
518 repos = db.Column(db.Text)
519
520
521 submitted_on = db.Column(db.Integer, nullable=False)
522
523 results = db.Column(db.Text)
524
525 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
526
527 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
528
529 enable_net = db.Column(db.Boolean, default=False,
530 server_default="0", nullable=False)
531
532 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
533
534 source_json = db.Column(db.Text)
535
536 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
537
538 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
539
540 srpm_url = db.Column(db.Text)
541
542
543 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
544 user = db.relationship("User", backref=db.backref("builds"))
545 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
546 copr = db.relationship("Copr", backref=db.backref("builds"))
547 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
548 package = db.relationship("Package")
549
550 chroots = association_proxy("build_chroots", "mock_chroot")
551
552 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
553 batch = db.relationship("Batch", backref=db.backref("builds"))
554
555 @property
558
559 @property
562
563 @property
566
567 @property
568 - def fail_type_text(self):
570
571 @property
573
574
575 return self.build_chroots[0].git_hash is None
576
577 @property
579 if self.repos is None:
580 return list()
581 else:
582 return self.repos.split()
583
584 @property
587
588 @property
590 return "{:08d}".format(self.id)
591
592 @property
600
601 @property
603 if app.config["COPR_DIST_GIT_LOGS_URL"]:
604 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
605 self.import_task_id.replace('/', '_'))
606 return None
607
608 @property
614
615 @property
622
623 @property
628
629 @property
632
633 @property
635 mb_list = [chroot.started_on for chroot in
636 self.build_chroots if chroot.started_on]
637 if len(mb_list) > 0:
638 return min(mb_list)
639 else:
640 return None
641
642 @property
645
646 @property
648 if not self.build_chroots:
649 return None
650 if any(chroot.ended_on is None for chroot in self.build_chroots):
651 return None
652 return max(chroot.ended_on for chroot in self.build_chroots)
653
654 @property
656 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
657
658 @property
660 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
661
662 @property
665
666 @property
675
676 @property
678 return map(lambda chroot: chroot.status, self.build_chroots)
679
681 """
682 Get build chroots with states which present in `states` list
683 If states == None, function returns build_chroots
684 """
685 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
686 if statuses is not None:
687 statuses = set(statuses)
688 else:
689 return self.build_chroots
690
691 return [
692 chroot for chroot, status in chroot_states_map.items()
693 if status in statuses
694 ]
695
696 @property
698 return {b.name: b for b in self.build_chroots}
699
700 @property
707
708 @property
713
714 @property
717
718 @property
729
730 @property
732 """
733 Return text representation of status of this build
734 """
735
736 if self.status is not None:
737 return StatusEnum(self.status)
738
739 return "unknown"
740
741 @property
743 """
744 Find out if this build is cancelable.
745
746 Build is cancelabel only when it's pending (not started)
747 """
748
749 return self.status == StatusEnum("pending") or \
750 self.status == StatusEnum("importing") or \
751 self.status == StatusEnum("running")
752
753 @property
755 """
756 Find out if this build is repeatable.
757
758 Build is repeatable only if it's not pending, starting or running
759 """
760 return self.status not in [StatusEnum("pending"),
761 StatusEnum("starting"),
762 StatusEnum("running"),
763 StatusEnum("forked")]
764
765 @property
767 """
768 Find out if this build is in finished state.
769
770 Build is finished only if all its build_chroots are in finished state.
771 """
772 return all([(chroot.state in ["succeeded", "forked", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
773
774 @property
776 """
777 Find out if this build is persistent.
778
779 This property is inherited from the project.
780 """
781 return self.copr.persistent
782
783 @property
785 """
786 Extract source package name from source name or url
787 todo: obsolete
788 """
789 try:
790 src_rpm_name = self.pkgs.split("/")[-1]
791 except:
792 return None
793 if src_rpm_name.endswith(".src.rpm"):
794 return src_rpm_name[:-8]
795 else:
796 return src_rpm_name
797
798 @property
800 try:
801 return self.package.name
802 except:
803 return None
804
805 - def to_dict(self, options=None, with_chroot_states=False):
818
821 """
822 1:N mapping: branch -> chroots
823 """
824
825
826 name = db.Column(db.String(50), primary_key=True)
827
828
829 -class MockChroot(db.Model, helpers.Serializer):
830
831 """
832 Representation of mock chroot
833 """
834 __table_args__ = (
835 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
836 )
837
838 id = db.Column(db.Integer, primary_key=True)
839
840 os_release = db.Column(db.String(50), nullable=False)
841
842 os_version = db.Column(db.String(50), nullable=False)
843
844 arch = db.Column(db.String(50), nullable=False)
845 is_active = db.Column(db.Boolean, default=True)
846
847
848 distgit_branch_name = db.Column(db.String(50),
849 db.ForeignKey("dist_git_branch.name"),
850 nullable=False)
851
852 distgit_branch = db.relationship("DistGitBranch",
853 backref=db.backref("chroots"))
854
855 @property
857 """
858 Textual representation of name of this chroot
859 """
860 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
861
862 @property
864 """
865 Textual representation of name of this or release
866 """
867 return "{}-{}".format(self.os_release, self.os_version)
868
869 @property
871 """
872 Textual representation of name of this or release
873 """
874 return "{} {}".format(self.os_release, self.os_version)
875
876 @property
878 """
879 Textual representation of the operating system name
880 """
881 return "{0} {1}".format(self.os_release, self.os_version)
882
883 @property
888
889
890 -class CoprChroot(db.Model, helpers.Serializer):
891
892 """
893 Representation of Copr<->MockChroot relation
894 """
895
896 buildroot_pkgs = db.Column(db.Text)
897 repos = db.Column(db.Text, default="", server_default="", nullable=False)
898 mock_chroot_id = db.Column(
899 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
900 mock_chroot = db.relationship(
901 "MockChroot", backref=db.backref("copr_chroots"))
902 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
903 copr = db.relationship("Copr",
904 backref=db.backref(
905 "copr_chroots",
906 single_parent=True,
907 cascade="all,delete,delete-orphan"))
908
909 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
910 comps_name = db.Column(db.String(127), nullable=True)
911
912 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
913 module_md_name = db.Column(db.String(127), nullable=True)
914
916 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
917
919 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
920
921 @property
924
925 @property
927 return self.repos.split()
928
929 @property
933
934 @property
938
939 @property
945
946 @property
952
953 @property
956
957 @property
960
962 options = {"__columns_only__": [
963 "buildroot_pkgs", "repos", "comps_name", "copr_id"
964 ]}
965 d = super(CoprChroot, self).to_dict(options=options)
966 d["mock_chroot"] = self.mock_chroot.name
967 return d
968
971
972 """
973 Representation of Build<->MockChroot relation
974 """
975
976 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
977 primary_key=True)
978 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
979 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
980 primary_key=True)
981 build = db.relationship("Build", backref=db.backref("build_chroots"))
982 git_hash = db.Column(db.String(40))
983 status = db.Column(db.Integer, default=StatusEnum("importing"))
984
985 started_on = db.Column(db.Integer)
986 ended_on = db.Column(db.Integer, index=True)
987
988 last_deferred = db.Column(db.Integer)
989
990 build_requires = db.Column(db.Text)
991
992 @property
994 """
995 Textual representation of name of this chroot
996 """
997
998 return self.mock_chroot.name
999
1000 @property
1002 """
1003 Return text representation of status of this build chroot
1004 """
1005
1006 if self.status is not None:
1007 return StatusEnum(self.status)
1008
1009 return "unknown"
1010
1011 @property
1014
1015 @property
1027
1028 @property
1030 return urljoin(app.config["BACKEND_BASE_URL"],
1031 os.path.join("results", self.result_dir, "")
1032 )
1033
1034 @property
1055
1057 return "<BuildChroot: {}>".format(self.to_dict())
1058
1059
1060 -class LegalFlag(db.Model, helpers.Serializer):
1061 id = db.Column(db.Integer, primary_key=True)
1062
1063 raise_message = db.Column(db.Text)
1064
1065 raised_on = db.Column(db.Integer)
1066
1067 resolved_on = db.Column(db.Integer)
1068
1069
1070 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1071
1072 copr = db.relationship(
1073 "Copr", backref=db.backref("legal_flags", cascade="all"))
1074
1075 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1076 reporter = db.relationship("User",
1077 backref=db.backref("legal_flags_raised"),
1078 foreign_keys=[reporter_id],
1079 primaryjoin="LegalFlag.reporter_id==User.id")
1080
1081 resolver_id = db.Column(
1082 db.Integer, db.ForeignKey("user.id"), nullable=True)
1083 resolver = db.relationship("User",
1084 backref=db.backref("legal_flags_resolved"),
1085 foreign_keys=[resolver_id],
1086 primaryjoin="LegalFlag.resolver_id==User.id")
1087
1088
1089 -class Action(db.Model, helpers.Serializer):
1146
1147
1148 -class Krb5Login(db.Model, helpers.Serializer):
1149 """
1150 Represents additional user information for kerberos authentication.
1151 """
1152
1153 __tablename__ = "krb5_login"
1154
1155
1156 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1157
1158
1159 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1160
1161
1162 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1163
1164 user = db.relationship("User", backref=db.backref("krb5_logins"))
1165
1168 """
1169 Generic store for simple statistics.
1170 """
1171
1172 name = db.Column(db.String(127), primary_key=True)
1173 counter_type = db.Column(db.String(30))
1174
1175 counter = db.Column(db.Integer, default=0, server_default="0")
1176
1177
1178 -class Group(db.Model, helpers.Serializer):
1179 """
1180 Represents FAS groups and their aliases in Copr
1181 """
1182 id = db.Column(db.Integer, primary_key=True)
1183 name = db.Column(db.String(127))
1184
1185
1186 fas_name = db.Column(db.String(127))
1187
1188 @property
1190 return u"@{}".format(self.name)
1191
1194
1197
1198
1199 -class Batch(db.Model):
1200 id = db.Column(db.Integer, primary_key=True)
1201
1202
1203 -class Module(db.Model, helpers.Serializer):
1204 id = db.Column(db.Integer, primary_key=True)
1205 name = db.Column(db.String(100), nullable=False)
1206 stream = db.Column(db.String(100), nullable=False)
1207 version = db.Column(db.Integer, nullable=False)
1208 summary = db.Column(db.String(100), nullable=False)
1209 description = db.Column(db.Text)
1210 created_on = db.Column(db.Integer, nullable=True)
1211
1212
1213
1214
1215
1216
1217
1218 yaml_b64 = db.Column(db.Text)
1219
1220
1221 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1222 copr = db.relationship("Copr", backref=db.backref("modules"))
1223
1224 @property
1226 return base64.b64decode(self.yaml_b64)
1227
1228 @property
1230 mmd = modulemd.ModuleMetadata()
1231 mmd.loads(self.yaml)
1232 return mmd
1233
1234 @property
1237
1238 @property
1241
1242 @property
1245
1246 @property
1254
1256
1257
1258
1259 module_dir = "fedora-24-{}+{}-{}-{}".format(arch, self.name, self.stream, self.version)
1260 return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch])
1261