1 import copy
2 import datetime
3 import json
4 import os
5 import flask
6
7 from sqlalchemy.ext.associationproxy import association_proxy
8 from libravatar import libravatar_url
9 import zlib
10
11 from coprs import constants
12 from coprs import db
13 from coprs import helpers
14 from coprs import app
15
16 import itertools
17 import operator
18 from coprs.helpers import BuildSourceEnum, StatusEnum, ActionTypeEnum, JSONEncodedDict
24
25
26 -class User(db.Model, helpers.Serializer):
27
28 """
29 Represents user of the copr frontend
30 """
31
32
33 id = db.Column(db.Integer, primary_key=True)
34
35
36 username = db.Column(db.String(100), nullable=False, unique=True)
37
38
39 mail = db.Column(db.String(150), nullable=False)
40
41
42 timezone = db.Column(db.String(50), nullable=True)
43
44
45
46 proven = db.Column(db.Boolean, default=False)
47
48
49 admin = db.Column(db.Boolean, default=False)
50
51
52 api_login = db.Column(db.String(40), nullable=False, default="abc")
53 api_token = db.Column(db.String(40), nullable=False, default="abc")
54 api_token_expiration = db.Column(
55 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
56
57
58 openid_groups = db.Column(JSONEncodedDict)
59
60 @property
62 """
63 Return the short username of the user, e.g. bkabrda
64 """
65
66 return self.username
67
69 """
70 Get permissions of this user for the given copr.
71 Caches the permission during one request,
72 so use this if you access them multiple times
73 """
74
75 if not hasattr(self, "_permissions_for_copr"):
76 self._permissions_for_copr = {}
77 if copr.name not in self._permissions_for_copr:
78 self._permissions_for_copr[copr.name] = (
79 CoprPermission.query
80 .filter_by(user=self)
81 .filter_by(copr=copr)
82 .first()
83 )
84 return self._permissions_for_copr[copr.name]
85
105
106 @property
112
113 @property
116
118 """
119 :type group: Group
120 """
121 if group.fas_name in self.user_teams:
122 return True
123 else:
124 return False
125
144
145 @property
147
148 return ["id", "name"]
149
150 @property
152 """
153 Get number of coprs for this user.
154 """
155
156 return (Copr.query.filter_by(user=self).
157 filter_by(deleted=False).
158 filter_by(group_id=None).
159 count())
160
161 @property
163 """
164 Return url to libravatar image.
165 """
166
167 try:
168 return libravatar_url(email=self.mail, https=True)
169 except IOError:
170 return ""
171
172
173 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
174
175 """
176 Represents a single copr (private repo with builds, mock chroots, etc.).
177 """
178
179 id = db.Column(db.Integer, primary_key=True)
180
181 name = db.Column(db.String(100), nullable=False)
182 homepage = db.Column(db.Text)
183 contact = db.Column(db.Text)
184
185
186 repos = db.Column(db.Text)
187
188 created_on = db.Column(db.Integer)
189
190 description = db.Column(db.Text)
191 instructions = db.Column(db.Text)
192 deleted = db.Column(db.Boolean, default=False)
193 playground = db.Column(db.Boolean, default=False)
194
195
196 auto_createrepo = db.Column(db.Boolean, default=True)
197
198
199 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
200 user = db.relationship("User", backref=db.backref("coprs"))
201 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
202 group = db.relationship("Group", backref=db.backref("groups"))
203 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
204 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
205 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
206
207
208 webhook_secret = db.Column(db.String(100))
209
210
211 build_enable_net = db.Column(db.Boolean, default=True,
212 server_default="1", nullable=False)
213
214 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
215
216
217 latest_indexed_data_update = db.Column(db.Integer)
218
219
220 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
221
222 __mapper_args__ = {
223 "order_by": created_on.desc()
224 }
225
226 @property
228 """
229 Return True if copr belongs to a group
230 """
231 return self.group_id is not None
232
233 @property
239
240 @property
246
247 @property
249 """
250 Return repos of this copr as a list of strings
251 """
252 return self.repos.split()
253
254 @property
261
262 @property
264 """
265 :rtype: list of CoprChroot
266 """
267 return [c for c in self.copr_chroots if c.is_active]
268
269 @property
271 """
272 Return list of active mock_chroots of this copr
273 """
274
275 return sorted(self.active_chroots, key=lambda ch: ch.name)
276
277 @property
279 """
280 Return list of active mock_chroots of this copr
281 """
282
283 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
284 output = []
285 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
286 output.append((os, [ch[1] for ch in chs]))
287
288 return output
289
290 @property
292 """
293 Return number of builds in this copr
294 """
295
296 return len(self.builds)
297
298 @property
302
303 @disable_createrepo.setter
307
308 @property
318
324
325 @property
328
329 @property
332
333 @property
338
339 @property
341 return "/".join([self.repo_url, "modules"])
342
343 @property
349
350 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
351 result = {}
352 for key in ["id", "name", "description", "instructions"]:
353 result[key] = str(copy.copy(getattr(self, key)))
354 result["owner"] = self.owner_name
355 return result
356
357 @property
362
365
368
369 """
370 Association class for Copr<->Permission relation
371 """
372
373
374
375 copr_builder = db.Column(db.SmallInteger, default=0)
376
377 copr_admin = db.Column(db.SmallInteger, default=0)
378
379
380 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
381 user = db.relationship("User", backref=db.backref("copr_permissions"))
382 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
383 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
384
385
386 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
387 """
388 Represents a single package in a project.
389 """
390 id = db.Column(db.Integer, primary_key=True)
391 name = db.Column(db.String(100), nullable=False)
392
393 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
394
395 source_json = db.Column(db.Text)
396
397 webhook_rebuild = db.Column(db.Boolean, default=False)
398
399 enable_net = db.Column(db.Boolean, default=False,
400 server_default="0", nullable=False)
401
402
403
404
405
406
407
408 old_status = db.Column(db.Integer)
409
410 builds = db.relationship("Build", order_by="Build.id")
411
412
413 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
414 copr = db.relationship("Copr", backref=db.backref("packages"))
415
416 @property
419
420 @property
425
426 @property
429
430 @property
432 """
433 Package's source type (and source_json) is being derived from its first build, which works except
434 for "srpm_link" and "srpm_upload" cases. Consider these being equivalent to source_type being unset.
435 """
436 return self.source_type and self.source_type_text != "srpm_link" and self.source_type_text != "srpm_upload"
437
438 @property
443
449
450 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
451 package_dict = super(Package, self).to_dict()
452 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
453
454 if with_latest_build:
455 build = self.last_build(successful=False)
456 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
457 if with_latest_succeeded_build:
458 build = self.last_build(successful=True)
459 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
460 if with_all_builds:
461 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
462
463 return package_dict
464
467
468
469 -class Build(db.Model, helpers.Serializer):
470
471 """
472 Representation of one build in one copr
473 """
474 __table_args__ = (db.Index('build_canceled', "canceled"), )
475
476 id = db.Column(db.Integer, primary_key=True)
477
478 pkgs = db.Column(db.Text)
479
480 built_packages = db.Column(db.Text)
481
482 pkg_version = db.Column(db.Text)
483
484 canceled = db.Column(db.Boolean, default=False)
485
486 repos = db.Column(db.Text)
487
488
489 submitted_on = db.Column(db.Integer, nullable=False)
490
491 results = db.Column(db.Text)
492
493 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
494
495 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
496
497 enable_net = db.Column(db.Boolean, default=False,
498 server_default="0", nullable=False)
499
500 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
501
502 source_json = db.Column(db.Text)
503
504 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
505
506 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
507
508
509 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
510 user = db.relationship("User", backref=db.backref("builds"))
511 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
512 copr = db.relationship("Copr", backref=db.backref("builds"))
513 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
514 package = db.relationship("Package")
515
516 chroots = association_proxy("build_chroots", "mock_chroot")
517
518 @property
521
522 @property
525
526 @property
529
530 @property
531 - def fail_type_text(self):
533
534 @property
536
537
538 return self.build_chroots[0].git_hash is None
539
540 @property
542 if self.repos is None:
543 return list()
544 else:
545 return self.repos.split()
546
547 @property
555
556 @property
561
562 @property
565
566 @property
568 mb_list = [chroot.started_on for chroot in
569 self.build_chroots if chroot.started_on]
570 if len(mb_list) > 0:
571 return min(mb_list)
572 else:
573 return None
574
575 @property
578
579 @property
581 if not self.build_chroots:
582 return None
583 if any(chroot.ended_on is None for chroot in self.build_chroots):
584 return None
585 return max(chroot.ended_on for chroot in self.build_chroots)
586
587 @property
589 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
590
591 @property
593 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
594
595 @property
598
599 @property
608
609 @property
611 return map(lambda chroot: chroot.status, self.build_chroots)
612
614 """
615 Get build chroots with states which present in `states` list
616 If states == None, function returns build_chroots
617 """
618 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
619 if statuses is not None:
620 statuses = set(statuses)
621 else:
622 return self.build_chroots
623
624 return [
625 chroot for chroot, status in chroot_states_map.items()
626 if status in statuses
627 ]
628
629 @property
631 return {b.name: b for b in self.build_chroots}
632
633 @property
640
641 @property
646
647 @property
650
651 @property
662
663 @property
665 """
666 Return text representation of status of this build
667 """
668
669 if self.status is not None:
670 return StatusEnum(self.status)
671
672 return "unknown"
673
674 @property
676 """
677 Find out if this build is cancelable.
678
679 Build is cancelabel only when it's pending (not started)
680 """
681
682 return self.status == StatusEnum("pending") or \
683 self.status == StatusEnum("importing")
684
685 @property
687 """
688 Find out if this build is repeatable.
689
690 Build is repeatable only if it's not pending, starting or running
691 """
692 return self.status not in [StatusEnum("pending"),
693 StatusEnum("starting"),
694 StatusEnum("running"), ]
695
696 @property
698 """
699 Find out if this build is in finished state.
700
701 Build is finished only if all its build_chroots are in finished state.
702 """
703 return all([(chroot.state in ["succeeded", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
704
705 @property
707 """
708 Find out if this build is persistent.
709
710 This property is inherited from the project.
711 """
712 return self.copr.persistent
713
714 @property
716 """
717 Extract source package name from source name or url
718 todo: obsolete
719 """
720 try:
721 src_rpm_name = self.pkgs.split("/")[-1]
722 except:
723 return None
724 if src_rpm_name.endswith(".src.rpm"):
725 return src_rpm_name[:-8]
726 else:
727 return src_rpm_name
728
729 @property
731 try:
732 return self.package.name
733 except:
734 return None
735
736 - def to_dict(self, options=None, with_chroot_states=False):
749
750
751 -class MockChroot(db.Model, helpers.Serializer):
752
753 """
754 Representation of mock chroot
755 """
756
757 id = db.Column(db.Integer, primary_key=True)
758
759 os_release = db.Column(db.String(50), nullable=False)
760
761 os_version = db.Column(db.String(50), nullable=False)
762
763 arch = db.Column(db.String(50), nullable=False)
764 is_active = db.Column(db.Boolean, default=True)
765
766 @property
768 """
769 Textual representation of name of this chroot
770 """
771 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
772
773 @property
775 """
776 Textual representation of name of this or release
777 """
778 return "{}-{}".format(self.os_release, self.os_version)
779
780 @property
782 """
783 Textual representation of name of this or release
784 """
785 return "{} {}".format(self.os_release, self.os_version)
786
787 @property
789 """
790 Textual representation of the operating system name
791 """
792 return "{0} {1}".format(self.os_release, self.os_version)
793
794 @property
799
800
801 -class CoprChroot(db.Model, helpers.Serializer):
802
803 """
804 Representation of Copr<->MockChroot relation
805 """
806
807 buildroot_pkgs = db.Column(db.Text)
808 mock_chroot_id = db.Column(
809 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
810 mock_chroot = db.relationship(
811 "MockChroot", backref=db.backref("copr_chroots"))
812 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
813 copr = db.relationship("Copr",
814 backref=db.backref(
815 "copr_chroots",
816 single_parent=True,
817 cascade="all,delete,delete-orphan"))
818
819 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
820 comps_name = db.Column(db.String(127), nullable=True)
821
822 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
823 module_md_name = db.Column(db.String(127), nullable=True)
824
826 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
827
829 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
830
831 @property
834
835 @property
839
840 @property
844
845 @property
851
852 @property
858
859 @property
862
863 @property
866
869
870 """
871 Representation of Build<->MockChroot relation
872 """
873
874 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
875 primary_key=True)
876 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
877 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
878 primary_key=True)
879 build = db.relationship("Build", backref=db.backref("build_chroots"))
880 git_hash = db.Column(db.String(40))
881 status = db.Column(db.Integer, default=StatusEnum("importing"))
882
883 started_on = db.Column(db.Integer)
884 ended_on = db.Column(db.Integer)
885
886 last_deferred = db.Column(db.Integer)
887
888 @property
890 """
891 Textual representation of name of this chroot
892 """
893
894 return self.mock_chroot.name
895
896 @property
898 """
899 Return text representation of status of this build chroot
900 """
901
902 if self.status is not None:
903 return StatusEnum(self.status)
904
905 return "unknown"
906
907 @property
910
911 @property
914
915 @property
922
923 @property
929
930 @property
935
936 @property
957
959 return "<BuildChroot: {}>".format(self.to_dict())
960
961
962 -class LegalFlag(db.Model, helpers.Serializer):
963 id = db.Column(db.Integer, primary_key=True)
964
965 raise_message = db.Column(db.Text)
966
967 raised_on = db.Column(db.Integer)
968
969 resolved_on = db.Column(db.Integer)
970
971
972 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
973
974 copr = db.relationship(
975 "Copr", backref=db.backref("legal_flags", cascade="all"))
976
977 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
978 reporter = db.relationship("User",
979 backref=db.backref("legal_flags_raised"),
980 foreign_keys=[reporter_id],
981 primaryjoin="LegalFlag.reporter_id==User.id")
982
983 resolver_id = db.Column(
984 db.Integer, db.ForeignKey("user.id"), nullable=True)
985 resolver = db.relationship("User",
986 backref=db.backref("legal_flags_resolved"),
987 foreign_keys=[resolver_id],
988 primaryjoin="LegalFlag.resolver_id==User.id")
989
990
991 -class Action(db.Model, helpers.Serializer):
1035
1036
1037 -class Krb5Login(db.Model, helpers.Serializer):
1038 """
1039 Represents additional user information for kerberos authentication.
1040 """
1041
1042 __tablename__ = "krb5_login"
1043
1044
1045 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1046
1047
1048 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1049
1050
1051 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1052
1053 user = db.relationship("User", backref=db.backref("krb5_logins"))
1054
1057 """
1058 Generic store for simple statistics.
1059 """
1060
1061 name = db.Column(db.String(127), primary_key=True)
1062 counter_type = db.Column(db.String(30))
1063
1064 counter = db.Column(db.Integer, default=0, server_default="0")
1065
1066
1067 -class Group(db.Model, helpers.Serializer):
1068 """
1069 Represents FAS groups and their aliases in Copr
1070 """
1071 id = db.Column(db.Integer, primary_key=True)
1072 name = db.Column(db.String(127))
1073
1074
1075 fas_name = db.Column(db.String(127))
1076
1077 @property
1079 return u"@{}".format(self.name)
1080
1083
1086