1 import copy
2 import datetime
3 import os
4 import flask
5 import json
6 import base64
7 import modulemd
8
9 from sqlalchemy.ext.associationproxy import association_proxy
10 from six.moves.urllib.parse import urljoin
11 from libravatar import libravatar_url
12 import zlib
13
14 from coprs import constants
15 from coprs import db
16 from coprs import helpers
17 from coprs import app
18
19 import itertools
20 import operator
21 from coprs.helpers import BuildSourceEnum, StatusEnum, ActionTypeEnum, JSONEncodedDict
27
28
29 -class User(db.Model, helpers.Serializer):
30
31 """
32 Represents user of the copr frontend
33 """
34
35
36 id = db.Column(db.Integer, primary_key=True)
37
38
39 username = db.Column(db.String(100), nullable=False, unique=True)
40
41
42 mail = db.Column(db.String(150), nullable=False)
43
44
45 timezone = db.Column(db.String(50), nullable=True)
46
47
48
49 proven = db.Column(db.Boolean, default=False)
50
51
52 admin = db.Column(db.Boolean, default=False)
53
54
55 proxy = db.Column(db.Boolean, default=False)
56
57
58 api_login = db.Column(db.String(40), nullable=False, default="abc")
59 api_token = db.Column(db.String(40), nullable=False, default="abc")
60 api_token_expiration = db.Column(
61 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
62
63
64 openid_groups = db.Column(JSONEncodedDict)
65
66 @property
68 """
69 Return the short username of the user, e.g. bkabrda
70 """
71
72 return self.username
73
75 """
76 Get permissions of this user for the given copr.
77 Caches the permission during one request,
78 so use this if you access them multiple times
79 """
80
81 if not hasattr(self, "_permissions_for_copr"):
82 self._permissions_for_copr = {}
83 if copr.name not in self._permissions_for_copr:
84 self._permissions_for_copr[copr.name] = (
85 CoprPermission.query
86 .filter_by(user=self)
87 .filter_by(copr=copr)
88 .first()
89 )
90 return self._permissions_for_copr[copr.name]
91
111
112 @property
118
119 @property
122
124 """
125 :type group: Group
126 """
127 if group.fas_name in self.user_teams:
128 return True
129 else:
130 return False
131
150
151 @property
153
154 return ["id", "name"]
155
156 @property
158 """
159 Get number of coprs for this user.
160 """
161
162 return (Copr.query.filter_by(user=self).
163 filter_by(deleted=False).
164 filter_by(group_id=None).
165 count())
166
167 @property
169 """
170 Return url to libravatar image.
171 """
172
173 try:
174 return libravatar_url(email=self.mail, https=True)
175 except IOError:
176 return ""
177
178
179 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
180
181 """
182 Represents a single copr (private repo with builds, mock chroots, etc.).
183 """
184
185 __table_args__ = (
186 db.Index('copr_webhook_secret', 'webhook_secret'),
187 )
188
189 id = db.Column(db.Integer, primary_key=True)
190
191 name = db.Column(db.String(100), nullable=False)
192 homepage = db.Column(db.Text)
193 contact = db.Column(db.Text)
194
195
196 repos = db.Column(db.Text)
197
198 created_on = db.Column(db.Integer)
199
200 description = db.Column(db.Text)
201 instructions = db.Column(db.Text)
202 deleted = db.Column(db.Boolean, default=False)
203 playground = db.Column(db.Boolean, default=False)
204
205
206 auto_createrepo = db.Column(db.Boolean, default=True)
207
208
209 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
210 user = db.relationship("User", backref=db.backref("coprs"))
211 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
212 group = db.relationship("Group", backref=db.backref("groups"))
213 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
214 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
215 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
216
217
218 webhook_secret = db.Column(db.String(100))
219
220
221 build_enable_net = db.Column(db.Boolean, default=True,
222 server_default="1", nullable=False)
223
224 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
225
226
227 latest_indexed_data_update = db.Column(db.Integer)
228
229
230 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
231
232
233 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
234
235
236 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
237
238
239 follow_fedora_branching = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
240
241 __mapper_args__ = {
242 "order_by": created_on.desc()
243 }
244
245 @property
247 """
248 Return True if copr belongs to a group
249 """
250 return self.group_id is not None
251
252 @property
258
259 @property
265
266 @property
268 """
269 Return repos of this copr as a list of strings
270 """
271 return self.repos.split()
272
273 @property
279
280 @property
282 """
283 :rtype: list of CoprChroot
284 """
285 return [c for c in self.copr_chroots if c.is_active]
286
287 @property
289 """
290 Return list of active mock_chroots of this copr
291 """
292
293 return sorted(self.active_chroots, key=lambda ch: ch.name)
294
295 @property
297 """
298 Return list of active mock_chroots of this copr
299 """
300
301 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
302 output = []
303 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
304 output.append((os, [ch[1] for ch in chs]))
305
306 return output
307
308 @property
310 """
311 Return number of builds in this copr
312 """
313
314 return len(self.builds)
315
316 @property
320
321 @disable_createrepo.setter
325
326 @property
338
344
345 @property
348
349 @property
352
353 @property
358
359 @property
365
366 @property
368 return "/".join([self.repo_url, "modules"])
369
370 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
371 result = {}
372 for key in ["id", "name", "description", "instructions"]:
373 result[key] = str(copy.copy(getattr(self, key)))
374 result["owner"] = self.owner_name
375 return result
376
377 @property
382
385
388
389 """
390 Association class for Copr<->Permission relation
391 """
392
393
394
395 copr_builder = db.Column(db.SmallInteger, default=0)
396
397 copr_admin = db.Column(db.SmallInteger, default=0)
398
399
400 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
401 user = db.relationship("User", backref=db.backref("copr_permissions"))
402 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
403 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
404
405
406 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
407 """
408 Represents a single package in a project.
409 """
410 __table_args__ = (
411 db.UniqueConstraint('copr_id', 'name', name='packages_copr_pkgname'),
412 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'),
413 )
414
415 id = db.Column(db.Integer, primary_key=True)
416 name = db.Column(db.String(100), nullable=False)
417
418 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
419
420 source_json = db.Column(db.Text)
421
422 webhook_rebuild = db.Column(db.Boolean, default=False)
423
424 enable_net = db.Column(db.Boolean, default=False,
425 server_default="0", nullable=False)
426
427
428
429
430
431
432
433 old_status = db.Column(db.Integer)
434
435 builds = db.relationship("Build", order_by="Build.id")
436
437
438 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
439 copr = db.relationship("Copr", backref=db.backref("packages"))
440
441 @property
444
445 @property
450
451 @property
454
455 @property
457 """
458 Package's source type (and source_json) is being derived from its first build, which works except
459 for "link" and "upload" cases. Consider these being equivalent to source_type being unset.
460 """
461 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
462
463 @property
468
469 @property
475
481
482 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
483 package_dict = super(Package, self).to_dict()
484 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
485
486 if with_latest_build:
487 build = self.last_build(successful=False)
488 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
489 if with_latest_succeeded_build:
490 build = self.last_build(successful=True)
491 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
492 if with_all_builds:
493 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
494
495 return package_dict
496
499
500
501 -class Build(db.Model, helpers.Serializer):
502 """
503 Representation of one build in one copr
504 """
505 __table_args__ = (db.Index('build_canceled', "canceled"),
506 db.Index('build_order', "is_background", "id"),
507 db.Index('build_filter', "source_type", "canceled"))
508
519
520 id = db.Column(db.Integer, primary_key=True)
521
522 pkgs = db.Column(db.Text)
523
524 built_packages = db.Column(db.Text)
525
526 pkg_version = db.Column(db.Text)
527
528 canceled = db.Column(db.Boolean, default=False)
529
530 repos = db.Column(db.Text)
531
532
533 submitted_on = db.Column(db.Integer, nullable=False)
534
535 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
536
537 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
538
539 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
540
541 enable_net = db.Column(db.Boolean, default=False,
542 server_default="0", nullable=False)
543
544 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
545
546 source_json = db.Column(db.Text)
547
548 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
549
550 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
551
552 source_status = db.Column(db.Integer, default=StatusEnum("waiting"))
553 srpm_url = db.Column(db.Text)
554
555
556 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
557 user = db.relationship("User", backref=db.backref("builds"))
558 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
559 copr = db.relationship("Copr", backref=db.backref("builds"))
560 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
561 package = db.relationship("Package")
562
563 chroots = association_proxy("build_chroots", "mock_chroot")
564
565 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
566 batch = db.relationship("Batch", backref=db.backref("builds"))
567
568 module_id = db.Column(db.Integer, db.ForeignKey("module.id"), index=True)
569 module = db.relationship("Module", backref=db.backref("builds"))
570
571 @property
574
575 @property
578
579 @property
582
583 @property
584 - def fail_type_text(self):
586
587 @property
589 if self.repos is None:
590 return list()
591 else:
592 return self.repos.split()
593
594 @property
597
598 @property
600 return "{:08d}".format(self.id)
601
602 @property
610
611 @property
613 if app.config["COPR_DIST_GIT_LOGS_URL"]:
614 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
615 self.task_id.replace('/', '_'))
616 return None
617
618 @property
624
625 @property
630
631 @property
634
635 @property
643
644 @property
647
648 @property
655
656 @property
659
660 @property
663
664 @property
667
668 @property
677
678 @property
681
683 """
684 Get build chroots with states which present in `states` list
685 If states == None, function returns build_chroots
686 """
687 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
688 if statuses is not None:
689 statuses = set(statuses)
690 else:
691 return self.build_chroots
692
693 return [
694 chroot for chroot, status in chroot_states_map.items()
695 if status in statuses
696 ]
697
698 @property
700 return {b.name: b for b in self.build_chroots}
701
702 @property
718
719 @property
721 """
722 Return text representation of status of this build.
723 """
724 if self.status != None:
725 return StatusEnum(self.status)
726 return "unknown"
727
728 @property
730 """
731 Find out if this build is cancelable.
732 """
733 return not self.finished and self.status != StatusEnum("starting")
734
735 @property
737 """
738 Find out if this build is repeatable.
739
740 Build is repeatable only if sources has been imported.
741 """
742 return self.source_status == StatusEnum("succeeded")
743
744 @property
746 """
747 Find out if this build is in finished state.
748
749 Build is finished only if all its build_chroots are in finished state or
750 the build was canceled.
751 """
752 return self.canceled or all([chroot.finished for chroot in self.build_chroots])
753
754 @property
756 """
757 Find out if this build is persistent.
758
759 This property is inherited from the project.
760 """
761 return self.copr.persistent
762
763 @property
765 try:
766 return self.package.name
767 except:
768 return None
769
770 - def to_dict(self, options=None, with_chroot_states=False):
783
786 """
787 1:N mapping: branch -> chroots
788 """
789
790
791 name = db.Column(db.String(50), primary_key=True)
792
793
794 -class MockChroot(db.Model, helpers.Serializer):
795
796 """
797 Representation of mock chroot
798 """
799 __table_args__ = (
800 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
801 )
802
803 id = db.Column(db.Integer, primary_key=True)
804
805 os_release = db.Column(db.String(50), nullable=False)
806
807 os_version = db.Column(db.String(50), nullable=False)
808
809 arch = db.Column(db.String(50), nullable=False)
810 is_active = db.Column(db.Boolean, default=True)
811
812
813 distgit_branch_name = db.Column(db.String(50),
814 db.ForeignKey("dist_git_branch.name"),
815 nullable=False)
816
817 distgit_branch = db.relationship("DistGitBranch",
818 backref=db.backref("chroots"))
819
820 @classmethod
829
830 @property
832 """
833 Textual representation of name of this chroot
834 """
835 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
836
837 @property
839 """
840 Textual representation of name of this or release
841 """
842 return "{}-{}".format(self.os_release, self.os_version)
843
844 @property
846 """
847 Textual representation of the operating system name
848 """
849 return "{0} {1}".format(self.os_release, self.os_version)
850
851 @property
856
857
858 -class CoprChroot(db.Model, helpers.Serializer):
859
860 """
861 Representation of Copr<->MockChroot relation
862 """
863
864 buildroot_pkgs = db.Column(db.Text)
865 repos = db.Column(db.Text, default="", server_default="", nullable=False)
866 mock_chroot_id = db.Column(
867 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
868 mock_chroot = db.relationship(
869 "MockChroot", backref=db.backref("copr_chroots"))
870 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
871 copr = db.relationship("Copr",
872 backref=db.backref(
873 "copr_chroots",
874 single_parent=True,
875 cascade="all,delete,delete-orphan"))
876
877 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
878 comps_name = db.Column(db.String(127), nullable=True)
879
880 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
881 module_md_name = db.Column(db.String(127), nullable=True)
882
883 with_opts = db.Column(db.Text, default="", server_default="", nullable=False)
884 without_opts = db.Column(db.Text, default="", server_default="", nullable=False)
885
887 if isinstance(comps_xml, str):
888 data = comps_xml.encode("utf-8")
889 else:
890 data = comps_xml
891 self.comps_zlib = zlib.compress(data)
892
894 if isinstance(module_md_yaml, str):
895 data = module_md_yaml.encode("utf-8")
896 else:
897 data = module_md_yaml
898 self.module_md_zlib = zlib.compress(data)
899
900 @property
903
904 @property
906 return self.repos.split()
907
908 @property
912
913 @property
917
918 @property
924
925 @property
931
932 @property
935
936 @property
939
941 options = {"__columns_only__": [
942 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts"
943 ]}
944 d = super(CoprChroot, self).to_dict(options=options)
945 d["mock_chroot"] = self.mock_chroot.name
946 return d
947
950
951 """
952 Representation of Build<->MockChroot relation
953 """
954
955 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
956 primary_key=True)
957 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
958 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
959 primary_key=True)
960 build = db.relationship("Build", backref=db.backref("build_chroots"))
961 git_hash = db.Column(db.String(40))
962 status = db.Column(db.Integer, default=StatusEnum("waiting"))
963
964 started_on = db.Column(db.Integer)
965 ended_on = db.Column(db.Integer, index=True)
966
967
968 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
969
970 build_requires = db.Column(db.Text)
971
972 @property
974 """
975 Textual representation of name of this chroot
976 """
977 return self.mock_chroot.name
978
979 @property
981 """
982 Return text representation of status of this build chroot
983 """
984 if self.status is not None:
985 return StatusEnum(self.status)
986 return "unknown"
987
988 @property
990 return (self.state in ["succeeded", "forked", "canceled", "skipped", "failed"])
991
992 @property
995
996 @property
1008
1009 @property
1013
1014
1015 -class LegalFlag(db.Model, helpers.Serializer):
1016 id = db.Column(db.Integer, primary_key=True)
1017
1018 raise_message = db.Column(db.Text)
1019
1020 raised_on = db.Column(db.Integer)
1021
1022 resolved_on = db.Column(db.Integer)
1023
1024
1025 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1026
1027 copr = db.relationship(
1028 "Copr", backref=db.backref("legal_flags", cascade="all"))
1029
1030 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1031 reporter = db.relationship("User",
1032 backref=db.backref("legal_flags_raised"),
1033 foreign_keys=[reporter_id],
1034 primaryjoin="LegalFlag.reporter_id==User.id")
1035
1036 resolver_id = db.Column(
1037 db.Integer, db.ForeignKey("user.id"), nullable=True)
1038 resolver = db.relationship("User",
1039 backref=db.backref("legal_flags_resolved"),
1040 foreign_keys=[resolver_id],
1041 primaryjoin="LegalFlag.resolver_id==User.id")
1042
1043
1044 -class Action(db.Model, helpers.Serializer):
1101
1102
1103 -class Krb5Login(db.Model, helpers.Serializer):
1104 """
1105 Represents additional user information for kerberos authentication.
1106 """
1107
1108 __tablename__ = "krb5_login"
1109
1110
1111 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1112
1113
1114 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1115
1116
1117 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1118
1119 user = db.relationship("User", backref=db.backref("krb5_logins"))
1120
1123 """
1124 Generic store for simple statistics.
1125 """
1126
1127 name = db.Column(db.String(127), primary_key=True)
1128 counter_type = db.Column(db.String(30))
1129
1130 counter = db.Column(db.Integer, default=0, server_default="0")
1131
1132
1133 -class Group(db.Model, helpers.Serializer):
1134 """
1135 Represents FAS groups and their aliases in Copr
1136 """
1137 id = db.Column(db.Integer, primary_key=True)
1138 name = db.Column(db.String(127))
1139
1140
1141 fas_name = db.Column(db.String(127))
1142
1143 @property
1145 return u"@{}".format(self.name)
1146
1149
1152
1153
1154 -class Batch(db.Model):
1155 id = db.Column(db.Integer, primary_key=True)
1156
1157
1158 -class Module(db.Model, helpers.Serializer):
1159 id = db.Column(db.Integer, primary_key=True)
1160 name = db.Column(db.String(100), nullable=False)
1161 stream = db.Column(db.String(100), nullable=False)
1162 version = db.Column(db.BigInteger, nullable=False)
1163 summary = db.Column(db.String(100), nullable=False)
1164 description = db.Column(db.Text)
1165 created_on = db.Column(db.Integer, nullable=True)
1166
1167
1168
1169
1170
1171
1172
1173 yaml_b64 = db.Column(db.Text)
1174
1175
1176 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1177 copr = db.relationship("Copr", backref=db.backref("modules"))
1178
1179 __table_args__ = (
1180 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"),
1181 )
1182
1183 @property
1185 return base64.b64decode(self.yaml_b64)
1186
1187 @property
1189 mmd = modulemd.ModuleMetadata()
1190 mmd.loads(self.yaml)
1191 return mmd
1192
1193 @property
1196
1197 @property
1200
1201 @property
1204
1205 @property
1213
1214 @property
1220