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 priority = db.Column(db.BigInteger, default=0, server_default="0", nullable=False)
541 srpm_url = db.Column(db.Text)
542
543
544 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
545 user = db.relationship("User", backref=db.backref("builds"))
546 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
547 copr = db.relationship("Copr", backref=db.backref("builds"))
548 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
549 package = db.relationship("Package")
550
551 chroots = association_proxy("build_chroots", "mock_chroot")
552
553 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
554 batch = db.relationship("Batch", backref=db.backref("builds"))
555
556 @property
559
560 @property
563
564 @property
567
568 @property
569 - def fail_type_text(self):
571
572 @property
574
575
576 return self.build_chroots[0].git_hash is None
577
578 @property
580 if self.repos is None:
581 return list()
582 else:
583 return self.repos.split()
584
585 @property
588
589 @property
591 return "{:08d}".format(self.id)
592
593 @property
601
602 @property
604 if app.config["COPR_DIST_GIT_LOGS_URL"]:
605 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
606 self.import_task_id.replace('/', '_'))
607 return None
608
609 @property
615
616 @property
623
624 @property
629
630 @property
633
634 @property
636 mb_list = [chroot.started_on for chroot in
637 self.build_chroots if chroot.started_on]
638 if len(mb_list) > 0:
639 return min(mb_list)
640 else:
641 return None
642
643 @property
646
647 @property
649 if not self.build_chroots:
650 return None
651 if any(chroot.ended_on is None for chroot in self.build_chroots):
652 return None
653 return max(chroot.ended_on for chroot in self.build_chroots)
654
655 @property
657 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
658
659 @property
661 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
662
663 @property
666
667 @property
676
677 @property
679 return map(lambda chroot: chroot.status, self.build_chroots)
680
682 """
683 Get build chroots with states which present in `states` list
684 If states == None, function returns build_chroots
685 """
686 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
687 if statuses is not None:
688 statuses = set(statuses)
689 else:
690 return self.build_chroots
691
692 return [
693 chroot for chroot, status in chroot_states_map.items()
694 if status in statuses
695 ]
696
697 @property
699 return {b.name: b for b in self.build_chroots}
700
701 @property
708
709 @property
714
715 @property
718
719 @property
730
731 @property
733 """
734 Return text representation of status of this build
735 """
736
737 if self.status is not None:
738 return StatusEnum(self.status)
739
740 return "unknown"
741
742 @property
744 """
745 Find out if this build is cancelable.
746
747 Build is cancelabel only when it's pending (not started)
748 """
749
750 return self.status == StatusEnum("pending") or \
751 self.status == StatusEnum("importing") or \
752 self.status == StatusEnum("running")
753
754 @property
756 """
757 Find out if this build is repeatable.
758
759 Build is repeatable only if it's not pending, starting or running
760 """
761 return self.status not in [StatusEnum("pending"),
762 StatusEnum("starting"),
763 StatusEnum("running"),
764 StatusEnum("forked")]
765
766 @property
768 """
769 Find out if this build is in finished state.
770
771 Build is finished only if all its build_chroots are in finished state.
772 """
773 return all([(chroot.state in ["succeeded", "forked", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
774
775 @property
777 """
778 Find out if this build is persistent.
779
780 This property is inherited from the project.
781 """
782 return self.copr.persistent
783
784 @property
786 """
787 Extract source package name from source name or url
788 todo: obsolete
789 """
790 try:
791 src_rpm_name = self.pkgs.split("/")[-1]
792 except:
793 return None
794 if src_rpm_name.endswith(".src.rpm"):
795 return src_rpm_name[:-8]
796 else:
797 return src_rpm_name
798
799 @property
801 try:
802 return self.package.name
803 except:
804 return None
805
806 - def to_dict(self, options=None, with_chroot_states=False):
819
822 """
823 1:N mapping: branch -> chroots
824 """
825
826
827 name = db.Column(db.String(50), primary_key=True)
828
829
830 -class MockChroot(db.Model, helpers.Serializer):
831
832 """
833 Representation of mock chroot
834 """
835 __table_args__ = (
836 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
837 )
838
839 id = db.Column(db.Integer, primary_key=True)
840
841 os_release = db.Column(db.String(50), nullable=False)
842
843 os_version = db.Column(db.String(50), nullable=False)
844
845 arch = db.Column(db.String(50), nullable=False)
846 is_active = db.Column(db.Boolean, default=True)
847
848
849 distgit_branch_name = db.Column(db.String(50),
850 db.ForeignKey("dist_git_branch.name"),
851 nullable=False)
852
853 distgit_branch = db.relationship("DistGitBranch",
854 backref=db.backref("chroots"))
855
856 @property
858 """
859 Textual representation of name of this chroot
860 """
861 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
862
863 @property
865 """
866 Textual representation of name of this or release
867 """
868 return "{}-{}".format(self.os_release, self.os_version)
869
870 @property
872 """
873 Textual representation of name of this or release
874 """
875 return "{} {}".format(self.os_release, self.os_version)
876
877 @property
879 """
880 Textual representation of the operating system name
881 """
882 return "{0} {1}".format(self.os_release, self.os_version)
883
884 @property
889
890
891 -class CoprChroot(db.Model, helpers.Serializer):
892
893 """
894 Representation of Copr<->MockChroot relation
895 """
896
897 buildroot_pkgs = db.Column(db.Text)
898 repos = db.Column(db.Text, default="", server_default="", nullable=False)
899 mock_chroot_id = db.Column(
900 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
901 mock_chroot = db.relationship(
902 "MockChroot", backref=db.backref("copr_chroots"))
903 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
904 copr = db.relationship("Copr",
905 backref=db.backref(
906 "copr_chroots",
907 single_parent=True,
908 cascade="all,delete,delete-orphan"))
909
910 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
911 comps_name = db.Column(db.String(127), nullable=True)
912
913 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
914 module_md_name = db.Column(db.String(127), nullable=True)
915
917 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
918
920 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
921
922 @property
925
926 @property
928 return self.repos.split()
929
930 @property
934
935 @property
939
940 @property
946
947 @property
953
954 @property
957
958 @property
961
963 options = {"__columns_only__": [
964 "buildroot_pkgs", "repos", "comps_name", "copr_id"
965 ]}
966 d = super(CoprChroot, self).to_dict(options=options)
967 d["mock_chroot"] = self.mock_chroot.name
968 return d
969
972
973 """
974 Representation of Build<->MockChroot relation
975 """
976
977 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
978 primary_key=True)
979 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
980 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
981 primary_key=True)
982 build = db.relationship("Build", backref=db.backref("build_chroots"))
983 git_hash = db.Column(db.String(40))
984 status = db.Column(db.Integer, default=StatusEnum("importing"))
985
986 started_on = db.Column(db.Integer)
987 ended_on = db.Column(db.Integer, index=True)
988
989 priority = db.Column(db.BigInteger, default=0, server_default="0", nullable=False)
990
991 build_requires = db.Column(db.Text)
992
993 @property
995 """
996 Textual representation of name of this chroot
997 """
998
999 return self.mock_chroot.name
1000
1001 @property
1003 """
1004 Return text representation of status of this build chroot
1005 """
1006
1007 if self.status is not None:
1008 return StatusEnum(self.status)
1009
1010 return "unknown"
1011
1012 @property
1015
1016 @property
1028
1029 @property
1031 return urljoin(app.config["BACKEND_BASE_URL"],
1032 os.path.join("results", self.result_dir, "")
1033 )
1034
1035 @property
1056
1058 return "<BuildChroot: {}>".format(self.to_dict())
1059
1060
1061 -class LegalFlag(db.Model, helpers.Serializer):
1062 id = db.Column(db.Integer, primary_key=True)
1063
1064 raise_message = db.Column(db.Text)
1065
1066 raised_on = db.Column(db.Integer)
1067
1068 resolved_on = db.Column(db.Integer)
1069
1070
1071 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1072
1073 copr = db.relationship(
1074 "Copr", backref=db.backref("legal_flags", cascade="all"))
1075
1076 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1077 reporter = db.relationship("User",
1078 backref=db.backref("legal_flags_raised"),
1079 foreign_keys=[reporter_id],
1080 primaryjoin="LegalFlag.reporter_id==User.id")
1081
1082 resolver_id = db.Column(
1083 db.Integer, db.ForeignKey("user.id"), nullable=True)
1084 resolver = db.relationship("User",
1085 backref=db.backref("legal_flags_resolved"),
1086 foreign_keys=[resolver_id],
1087 primaryjoin="LegalFlag.resolver_id==User.id")
1088
1089
1090 -class Action(db.Model, helpers.Serializer):
1147
1148
1149 -class Krb5Login(db.Model, helpers.Serializer):
1150 """
1151 Represents additional user information for kerberos authentication.
1152 """
1153
1154 __tablename__ = "krb5_login"
1155
1156
1157 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1158
1159
1160 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1161
1162
1163 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1164
1165 user = db.relationship("User", backref=db.backref("krb5_logins"))
1166
1169 """
1170 Generic store for simple statistics.
1171 """
1172
1173 name = db.Column(db.String(127), primary_key=True)
1174 counter_type = db.Column(db.String(30))
1175
1176 counter = db.Column(db.Integer, default=0, server_default="0")
1177
1178
1179 -class Group(db.Model, helpers.Serializer):
1180 """
1181 Represents FAS groups and their aliases in Copr
1182 """
1183 id = db.Column(db.Integer, primary_key=True)
1184 name = db.Column(db.String(127))
1185
1186
1187 fas_name = db.Column(db.String(127))
1188
1189 @property
1191 return u"@{}".format(self.name)
1192
1195
1198
1199
1200 -class Batch(db.Model):
1201 id = db.Column(db.Integer, primary_key=True)
1202
1203
1204 -class Module(db.Model, helpers.Serializer):
1205 id = db.Column(db.Integer, primary_key=True)
1206 name = db.Column(db.String(100), nullable=False)
1207 stream = db.Column(db.String(100), nullable=False)
1208 version = db.Column(db.Integer, nullable=False)
1209 summary = db.Column(db.String(100), nullable=False)
1210 description = db.Column(db.Text)
1211 created_on = db.Column(db.Integer, nullable=True)
1212
1213
1214
1215
1216
1217
1218
1219 yaml_b64 = db.Column(db.Text)
1220
1221
1222 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1223 copr = db.relationship("Copr", backref=db.backref("modules"))
1224
1225 @property
1227 return base64.b64decode(self.yaml_b64)
1228
1229 @property
1231 mmd = modulemd.ModuleMetadata()
1232 mmd.loads(self.yaml)
1233 return mmd
1234
1235 @property
1238
1239 @property
1242
1243 @property
1246
1247 @property
1255
1257
1258
1259
1260 module_dir = "fedora-24-{}+{}-{}-{}".format(arch, self.name, self.stream, self.version)
1261 return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch])
1262