1 import copy
2 import datetime
3 import os
4 import flask
5 import json
6 import base64
7 import modulemd
8 import uuid
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 Represents user of the copr frontend
33 """
34
35 id = db.Column(db.Integer, primary_key=True)
36
37
38 username = db.Column(db.String(100), nullable=False, unique=True)
39
40
41 mail = db.Column(db.String(150), nullable=False)
42
43
44 timezone = db.Column(db.String(50), nullable=True)
45
46
47
48 proven = db.Column(db.Boolean, default=False)
49
50
51 admin = db.Column(db.Boolean, default=False)
52
53
54 proxy = db.Column(db.Boolean, default=False)
55
56
57 api_login = db.Column(db.String(40), nullable=False, default="abc")
58 api_token = db.Column(db.String(40), nullable=False, default="abc")
59 api_token_expiration = db.Column(
60 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
61
62
63 openid_groups = db.Column(JSONEncodedDict)
64
65 @property
67 """
68 Return the short username of the user, e.g. bkabrda
69 """
70
71 return self.username
72
74 """
75 Get permissions of this user for the given copr.
76 Caches the permission during one request,
77 so use this if you access them multiple times
78 """
79
80 if not hasattr(self, "_permissions_for_copr"):
81 self._permissions_for_copr = {}
82 if copr.name not in self._permissions_for_copr:
83 self._permissions_for_copr[copr.name] = (
84 CoprPermission.query
85 .filter_by(user=self)
86 .filter_by(copr=copr)
87 .first()
88 )
89 return self._permissions_for_copr[copr.name]
90
110
111 @property
117
118 @property
121
123 """
124 :type group: Group
125 """
126 if group.fas_name in self.user_teams:
127 return True
128 else:
129 return False
130
149
150 @property
152
153 return ["id", "name"]
154
155 @property
157 """
158 Get number of coprs for this user.
159 """
160
161 return (Copr.query.filter_by(user=self).
162 filter_by(deleted=False).
163 filter_by(group_id=None).
164 count())
165
166 @property
168 """
169 Return url to libravatar image.
170 """
171
172 try:
173 return libravatar_url(email=self.mail, https=True)
174 except IOError:
175 return ""
176
177
178 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
179 """
180 Represents a single copr (private repo with builds, mock chroots, etc.).
181 """
182
183 __table_args__ = (
184 db.Index('copr_webhook_secret', 'webhook_secret'),
185 )
186
187 id = db.Column(db.Integer, primary_key=True)
188
189 name = db.Column(db.String(100), nullable=False)
190 homepage = db.Column(db.Text)
191 contact = db.Column(db.Text)
192
193
194 repos = db.Column(db.Text)
195
196 created_on = db.Column(db.Integer)
197
198 description = db.Column(db.Text)
199 instructions = db.Column(db.Text)
200 deleted = db.Column(db.Boolean, default=False)
201 playground = db.Column(db.Boolean, default=False)
202
203
204 auto_createrepo = db.Column(db.Boolean, default=True)
205
206
207 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
208 user = db.relationship("User", backref=db.backref("coprs"))
209 group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
210 group = db.relationship("Group", backref=db.backref("groups"))
211 mock_chroots = association_proxy("copr_chroots", "mock_chroot")
212 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
213 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
214
215
216 webhook_secret = db.Column(db.String(100))
217
218
219 build_enable_net = db.Column(db.Boolean, default=True,
220 server_default="1", nullable=False)
221
222 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
223
224
225 latest_indexed_data_update = db.Column(db.Integer)
226
227
228 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
229
230
231 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
232
233
234 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
235
236
237 follow_fedora_branching = db.Column(db.Boolean, default=True, nullable=False, server_default="1")
238
239
240 scm_repo_url = db.Column(db.Text)
241 scm_api_type = db.Column(db.Text)
242 scm_api_auth_json = db.Column(db.Text)
243
244 __mapper_args__ = {
245 "order_by": created_on.desc()
246 }
247
248 @property
249 - def main_dir(self):
250 """
251 Return main copr dir for a Copr
252 """
253 return CoprDir.query.filter(CoprDir.copr_id==self.id).filter(CoprDir.main==True).one()
254
255 @property
260
261 @property
263 """
264 Return True if copr belongs to a group
265 """
266 return self.group is not None
267
268 @property
274
275 @property
281
282 @property
284 """
285 Return repos of this copr as a list of strings
286 """
287 return self.repos.split()
288
289 @property
295
296 @property
298 """
299 :rtype: list of CoprChroot
300 """
301 return [c for c in self.copr_chroots if c.is_active]
302
303 @property
305 """
306 Return list of active mock_chroots of this copr
307 """
308 return sorted(self.active_chroots, key=lambda ch: ch.name)
309
310 @property
312 """
313 Return list of active mock_chroots of this copr
314 """
315 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
316 output = []
317 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
318 output.append((os, [ch[1] for ch in chs]))
319
320 return output
321
322 @property
324 """
325 Return number of builds in this copr
326 """
327 return len(self.builds)
328
329 @property
332
333 @disable_createrepo.setter
336
337 @property
340
341 @property
353
359
360 @property
363
364 @property
367
368 @property
373
374 @property
380
381 @property
383 return "/".join([self.repo_url, "modules"])
384
385 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
386 result = {}
387 for key in ["id", "name", "description", "instructions"]:
388 result[key] = str(copy.copy(getattr(self, key)))
389 result["owner"] = self.owner_name
390 return result
391
392 @property
397
400
401 @property
404
405 @enable_net.setter
408
411
414 """
415 Association class for Copr<->Permission relation
416 """
417
418
419
420 copr_builder = db.Column(db.SmallInteger, default=0)
421
422 copr_admin = db.Column(db.SmallInteger, default=0)
423
424
425 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
426 user = db.relationship("User", backref=db.backref("copr_permissions"))
427 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
428 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
429
432 """
433 Represents one of data directories for a copr.
434 """
435 id = db.Column(db.Integer, primary_key=True)
436
437 name = db.Column(db.Text, index=True)
438 main = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
439
440 ownername = db.Column(db.Text, index=True, nullable=False)
441
442 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True, nullable=False)
443 copr = db.relationship("Copr", backref=db.backref("dirs"))
444
445 __table_args__ = (
446 db.Index('only_one_main_copr_dir', copr_id, main,
447 unique=True, postgresql_where=(main==True)),
448
449 db.UniqueConstraint('ownername', 'name',
450 name='ownername_copr_dir_uniq'),
451 )
452
457
458 @property
461
462 @property
465
466 @property
470
471 @property
477
478
479 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
480 """
481 Represents a single package in a project_dir.
482 """
483
484 __table_args__ = (
485 db.UniqueConstraint('copr_dir_id', 'name', name='packages_copr_dir_pkgname'),
486 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'),
487 )
488
493
494 id = db.Column(db.Integer, primary_key=True)
495 name = db.Column(db.String(100), nullable=False)
496
497 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
498
499 source_json = db.Column(db.Text)
500
501 webhook_rebuild = db.Column(db.Boolean, default=False)
502
503 enable_net = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
504
505
506
507
508
509
510
511 old_status = db.Column(db.Integer)
512
513 builds = db.relationship("Build", order_by="Build.id")
514
515
516 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
517 copr = db.relationship("Copr", backref=db.backref("packages"))
518
519 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)
520 copr_dir = db.relationship("CoprDir", backref=db.backref("packages"))
521
522 @property
525
526 @property
531
532 @property
535
536 @property
538 """
539 Package's source type (and source_json) is being derived from its first build, which works except
540 for "link" and "upload" cases. Consider these being equivalent to source_type being unset.
541 """
542 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
543
544 @property
549
550 @property
556
562
563 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
564 package_dict = super(Package, self).to_dict()
565 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
566
567 if with_latest_build:
568 build = self.last_build(successful=False)
569 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
570 if with_latest_succeeded_build:
571 build = self.last_build(successful=True)
572 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
573 if with_all_builds:
574 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
575
576 return package_dict
577
580
581
582 -class Build(db.Model, helpers.Serializer):
583 """
584 Representation of one build in one copr
585 """
586
587 SCM_COMMIT = 'commit'
588 SCM_PULL_REQUEST = 'pull-request'
589
590 __table_args__ = (db.Index('build_canceled', "canceled"),
591 db.Index('build_order', "is_background", "id"),
592 db.Index('build_filter', "source_type", "canceled"))
593
607
608 id = db.Column(db.Integer, primary_key=True)
609
610 pkgs = db.Column(db.Text)
611
612 built_packages = db.Column(db.Text)
613
614 pkg_version = db.Column(db.Text)
615
616 canceled = db.Column(db.Boolean, default=False)
617
618 repos = db.Column(db.Text)
619
620
621 submitted_on = db.Column(db.Integer, nullable=False)
622
623 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
624
625 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
626
627 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
628
629 enable_net = db.Column(db.Boolean, default=False,
630 server_default="0", nullable=False)
631
632 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
633
634 source_json = db.Column(db.Text)
635
636 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
637
638 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
639
640 source_status = db.Column(db.Integer, default=StatusEnum("waiting"))
641 srpm_url = db.Column(db.Text)
642
643
644 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
645 user = db.relationship("User", backref=db.backref("builds"))
646 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
647 copr = db.relationship("Copr", backref=db.backref("builds"))
648 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
649 package = db.relationship("Package")
650
651 chroots = association_proxy("build_chroots", "mock_chroot")
652
653 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
654 batch = db.relationship("Batch", backref=db.backref("builds"))
655
656 module_id = db.Column(db.Integer, db.ForeignKey("module.id"), index=True)
657 module = db.relationship("Module", backref=db.backref("builds"))
658
659 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True)
660 copr_dir = db.relationship("CoprDir", backref=db.backref("builds"))
661
662
663 scm_object_id = db.Column(db.Text)
664 scm_object_type = db.Column(db.Text)
665 scm_object_url = db.Column(db.Text)
666
667
668 update_callback = db.Column(db.Text)
669
670 @property
673
674 @property
677
678 @property
681
682 @property
685
686 @property
689
690 @property
691 - def fail_type_text(self):
693
694 @property
696 if self.repos is None:
697 return list()
698 else:
699 return self.repos.split()
700
701 @property
704
705 @property
707 return "{:08d}".format(self.id)
708
709 @property
717
718 @property
720 if app.config["COPR_DIST_GIT_LOGS_URL"]:
721 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
722 self.task_id.replace('/', '_'))
723 return None
724
725 @property
731
732 @property
737
738 @property
741
742 @property
750
751 @property
754
755 @property
762
763 @property
766
767 @property
770
771 @property
774
775 @property
784
785 @property
788
790 """
791 Get build chroots with states which present in `states` list
792 If states == None, function returns build_chroots
793 """
794 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
795 if statuses is not None:
796 statuses = set(statuses)
797 else:
798 return self.build_chroots
799
800 return [
801 chroot for chroot, status in chroot_states_map.items()
802 if status in statuses
803 ]
804
805 @property
807 return {b.name: b for b in self.build_chroots}
808
809 @property
825
826 @property
828 """
829 Return text representation of status of this build.
830 """
831 if self.status != None:
832 return StatusEnum(self.status)
833 return "unknown"
834
835 @property
837 """
838 Find out if this build is cancelable.
839 """
840 return not self.finished and self.status != StatusEnum("starting")
841
842 @property
844 """
845 Find out if this build is repeatable.
846
847 Build is repeatable only if sources has been imported.
848 """
849 return self.source_status == StatusEnum("succeeded")
850
851 @property
853 """
854 Find out if this build is in finished state.
855
856 Build is finished only if all its build_chroots are in finished state or
857 the build was canceled.
858 """
859 return self.canceled or all([chroot.finished for chroot in self.build_chroots])
860
861 @property
863 """
864 Find out if this build is persistent.
865
866 This property is inherited from the project.
867 """
868 return self.copr.persistent
869
870 @property
872 try:
873 return self.package.name
874 except:
875 return None
876
877 - def to_dict(self, options=None, with_chroot_states=False):
890
893 """
894 1:N mapping: branch -> chroots
895 """
896
897
898 name = db.Column(db.String(50), primary_key=True)
899
900
901 -class MockChroot(db.Model, helpers.Serializer):
902 """
903 Representation of mock chroot
904 """
905
906 __table_args__ = (
907 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
908 )
909
910 id = db.Column(db.Integer, primary_key=True)
911
912 os_release = db.Column(db.String(50), nullable=False)
913
914 os_version = db.Column(db.String(50), nullable=False)
915
916 arch = db.Column(db.String(50), nullable=False)
917 is_active = db.Column(db.Boolean, default=True)
918
919
920 distgit_branch_name = db.Column(db.String(50),
921 db.ForeignKey("dist_git_branch.name"),
922 nullable=False)
923
924 distgit_branch = db.relationship("DistGitBranch",
925 backref=db.backref("chroots"))
926
927 @classmethod
936
937 @property
939 """
940 Textual representation of name of this chroot
941 """
942 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
943
944 @property
946 """
947 Textual representation of name of this or release
948 """
949 return "{}-{}".format(self.os_release, self.os_version)
950
951 @property
953 """
954 Textual representation of the operating system name
955 """
956 return "{0} {1}".format(self.os_release, self.os_version)
957
958 @property
963
964
965 -class CoprChroot(db.Model, helpers.Serializer):
966 """
967 Representation of Copr<->MockChroot relation
968 """
969
970 buildroot_pkgs = db.Column(db.Text)
971 repos = db.Column(db.Text, default="", server_default="", nullable=False)
972 mock_chroot_id = db.Column(
973 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
974 mock_chroot = db.relationship(
975 "MockChroot", backref=db.backref("copr_chroots"))
976 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
977 copr = db.relationship("Copr",
978 backref=db.backref(
979 "copr_chroots",
980 single_parent=True,
981 cascade="all,delete,delete-orphan"))
982
983 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
984 comps_name = db.Column(db.String(127), nullable=True)
985
986 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
987 module_md_name = db.Column(db.String(127), nullable=True)
988
989 with_opts = db.Column(db.Text, default="", server_default="", nullable=False)
990 without_opts = db.Column(db.Text, default="", server_default="", nullable=False)
991
993 if isinstance(comps_xml, str):
994 data = comps_xml.encode("utf-8")
995 else:
996 data = comps_xml
997 self.comps_zlib = zlib.compress(data)
998
1000 if isinstance(module_md_yaml, str):
1001 data = module_md_yaml.encode("utf-8")
1002 else:
1003 data = module_md_yaml
1004 self.module_md_zlib = zlib.compress(data)
1005
1006 @property
1009
1010 @property
1012 return (self.repos or "").split()
1013
1014 @property
1018
1019 @property
1023
1024 @property
1030
1031 @property
1037
1038 @property
1041
1042 @property
1045
1047 options = {"__columns_only__": [
1048 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts"
1049 ]}
1050 d = super(CoprChroot, self).to_dict(options=options)
1051 d["mock_chroot"] = self.mock_chroot.name
1052 return d
1053
1056 """
1057 Representation of Build<->MockChroot relation
1058 """
1059
1060 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
1061 primary_key=True)
1062 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
1063 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
1064 primary_key=True)
1065 build = db.relationship("Build", backref=db.backref("build_chroots"))
1066 git_hash = db.Column(db.String(40))
1067 status = db.Column(db.Integer, default=StatusEnum("waiting"))
1068
1069 started_on = db.Column(db.Integer, index=True)
1070 ended_on = db.Column(db.Integer, index=True)
1071
1072
1073 result_dir = db.Column(db.Text, default='', server_default='', nullable=False)
1074
1075 build_requires = db.Column(db.Text)
1076
1077 @property
1079 """
1080 Textual representation of name of this chroot
1081 """
1082 return self.mock_chroot.name
1083
1084 @property
1086 """
1087 Return text representation of status of this build chroot
1088 """
1089 if self.status is not None:
1090 return StatusEnum(self.status)
1091 return "unknown"
1092
1093 @property
1095 return (self.state in ["succeeded", "forked", "canceled", "skipped", "failed"])
1096
1097 @property
1100
1101 @property
1113
1114 @property
1118
1119
1120 -class LegalFlag(db.Model, helpers.Serializer):
1121 id = db.Column(db.Integer, primary_key=True)
1122
1123 raise_message = db.Column(db.Text)
1124
1125 raised_on = db.Column(db.Integer)
1126
1127 resolved_on = db.Column(db.Integer)
1128
1129
1130 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1131
1132 copr = db.relationship(
1133 "Copr", backref=db.backref("legal_flags", cascade="all"))
1134
1135 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1136 reporter = db.relationship("User",
1137 backref=db.backref("legal_flags_raised"),
1138 foreign_keys=[reporter_id],
1139 primaryjoin="LegalFlag.reporter_id==User.id")
1140
1141 resolver_id = db.Column(
1142 db.Integer, db.ForeignKey("user.id"), nullable=True)
1143 resolver = db.relationship("User",
1144 backref=db.backref("legal_flags_resolved"),
1145 foreign_keys=[resolver_id],
1146 primaryjoin="LegalFlag.resolver_id==User.id")
1147
1148
1149 -class Action(db.Model, helpers.Serializer):
1201
1202
1203 -class Krb5Login(db.Model, helpers.Serializer):
1204 """
1205 Represents additional user information for kerberos authentication.
1206 """
1207
1208 __tablename__ = "krb5_login"
1209
1210
1211 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1212
1213
1214 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1215
1216
1217 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1218
1219 user = db.relationship("User", backref=db.backref("krb5_logins"))
1220
1223 """
1224 Generic store for simple statistics.
1225 """
1226
1227 name = db.Column(db.String(127), primary_key=True)
1228 counter_type = db.Column(db.String(30))
1229
1230 counter = db.Column(db.Integer, default=0, server_default="0")
1231
1232
1233 -class Group(db.Model, helpers.Serializer):
1234
1235 """
1236 Represents FAS groups and their aliases in Copr
1237 """
1238
1239 id = db.Column(db.Integer, primary_key=True)
1240 name = db.Column(db.String(127))
1241
1242
1243 fas_name = db.Column(db.String(127))
1244
1245 @property
1247 return u"@{}".format(self.name)
1248
1251
1254
1255
1256 -class Batch(db.Model):
1257 id = db.Column(db.Integer, primary_key=True)
1258
1259
1260 -class Module(db.Model, helpers.Serializer):
1261 id = db.Column(db.Integer, primary_key=True)
1262 name = db.Column(db.String(100), nullable=False)
1263 stream = db.Column(db.String(100), nullable=False)
1264 version = db.Column(db.BigInteger, nullable=False)
1265 summary = db.Column(db.String(100), nullable=False)
1266 description = db.Column(db.Text)
1267 created_on = db.Column(db.Integer, nullable=True)
1268
1269
1270
1271
1272
1273
1274
1275 yaml_b64 = db.Column(db.Text)
1276
1277
1278 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1279 copr = db.relationship("Copr", backref=db.backref("modules"))
1280
1281 __table_args__ = (
1282 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"),
1283 )
1284
1285 @property
1287 return base64.b64decode(self.yaml_b64)
1288
1289 @property
1291 mmd = modulemd.ModuleMetadata()
1292 mmd.loads(self.yaml)
1293 return mmd
1294
1295 @property
1298
1299 @property
1302
1303 @property
1306
1307 @property
1315
1316 @property
1322
1329