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
337
343
344 @property
347
348 @property
351
352 @property
357
358 @property
364
365 @property
367 return "/".join([self.repo_url, "modules"])
368
369 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
370 result = {}
371 for key in ["id", "name", "description", "instructions"]:
372 result[key] = str(copy.copy(getattr(self, key)))
373 result["owner"] = self.owner_name
374 return result
375
376 @property
381
384
387
388 """
389 Association class for Copr<->Permission relation
390 """
391
392
393
394 copr_builder = db.Column(db.SmallInteger, default=0)
395
396 copr_admin = db.Column(db.SmallInteger, default=0)
397
398
399 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
400 user = db.relationship("User", backref=db.backref("copr_permissions"))
401 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
402 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
403
404
405 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
406 """
407 Represents a single package in a project.
408 """
409 __table_args__ = (
410 db.UniqueConstraint('copr_id', 'name', name='packages_copr_pkgname'),
411 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'),
412 )
413
414 id = db.Column(db.Integer, primary_key=True)
415 name = db.Column(db.String(100), nullable=False)
416
417 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
418
419 source_json = db.Column(db.Text)
420
421 webhook_rebuild = db.Column(db.Boolean, default=False)
422
423 enable_net = db.Column(db.Boolean, default=False,
424 server_default="0", nullable=False)
425
426
427
428
429
430
431
432 old_status = db.Column(db.Integer)
433
434 builds = db.relationship("Build", order_by="Build.id")
435
436
437 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
438 copr = db.relationship("Copr", backref=db.backref("packages"))
439
440 @property
443
444 @property
449
450 @property
453
454 @property
456 """
457 Package's source type (and source_json) is being derived from its first build, which works except
458 for "link" and "upload" cases. Consider these being equivalent to source_type being unset.
459 """
460 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
461
462 @property
467
468 @property
474
480
481 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
482 package_dict = super(Package, self).to_dict()
483 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
484
485 if with_latest_build:
486 build = self.last_build(successful=False)
487 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
488 if with_latest_succeeded_build:
489 build = self.last_build(successful=True)
490 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
491 if with_all_builds:
492 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
493
494 return package_dict
495
498
499
500 -class Build(db.Model, helpers.Serializer):
501 """
502 Representation of one build in one copr
503 """
504 __table_args__ = (db.Index('build_canceled', "canceled"),
505 db.Index('build_order', "is_background", "id"),
506 db.Index('build_filter', "source_type", "canceled"))
507
518
519 id = db.Column(db.Integer, primary_key=True)
520
521 pkgs = db.Column(db.Text)
522
523 built_packages = db.Column(db.Text)
524
525 pkg_version = db.Column(db.Text)
526
527 canceled = db.Column(db.Boolean, default=False)
528
529 repos = db.Column(db.Text)
530
531
532 submitted_on = db.Column(db.Integer, nullable=False)
533
534 results = db.Column(db.Text)
535
536 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
537
538 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
539
540 enable_net = db.Column(db.Boolean, default=False,
541 server_default="0", nullable=False)
542
543 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
544
545 source_json = db.Column(db.Text)
546
547 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
548
549 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
550
551 source_status = db.Column(db.Integer, default=StatusEnum("waiting"))
552 srpm_url = db.Column(db.Text)
553
554
555 user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
556 user = db.relationship("User", backref=db.backref("builds"))
557 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
558 copr = db.relationship("Copr", backref=db.backref("builds"))
559 package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
560 package = db.relationship("Package")
561
562 chroots = association_proxy("build_chroots", "mock_chroot")
563
564 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id"))
565 batch = db.relationship("Batch", backref=db.backref("builds"))
566
567 module_id = db.Column(db.Integer, db.ForeignKey("module.id"), index=True)
568 module = db.relationship("Module", backref=db.backref("builds"))
569
570 @property
573
574 @property
577
578 @property
581
582 @property
583 - def fail_type_text(self):
585
586 @property
588
589
590 return self.build_chroots[0].git_hash is None
591
592 @property
594 if self.repos is None:
595 return list()
596 else:
597 return self.repos.split()
598
599 @property
602
603 @property
605 return "{:08d}".format(self.id)
606
607 @property
615
616 @property
618 if app.config["COPR_DIST_GIT_LOGS_URL"]:
619 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
620 self.task_id.replace('/', '_'))
621 return None
622
623 @property
629
630 @property
637
638 @property
643
644 @property
647
648 @property
656
657 @property
660
661 @property
668
669 @property
672
673 @property
676
677 @property
680
681 @property
690
691 @property
694
696 """
697 Get build chroots with states which present in `states` list
698 If states == None, function returns build_chroots
699 """
700 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
701 if statuses is not None:
702 statuses = set(statuses)
703 else:
704 return self.build_chroots
705
706 return [
707 chroot for chroot, status in chroot_states_map.items()
708 if status in statuses
709 ]
710
711 @property
713 return {b.name: b for b in self.build_chroots}
714
715 @property
731
732 @property
734 """
735 Return text representation of status of this build.
736 """
737 if self.status != None:
738 return StatusEnum(self.status)
739 return "unknown"
740
741 @property
743 """
744 Find out if this build is cancelable.
745 """
746 return not self.finished and self.status != StatusEnum("starting")
747
748 @property
750 """
751 Find out if this build is repeatable.
752
753 Build is repeatable only if sources has been imported.
754 """
755 return self.source_status == StatusEnum("succeeded")
756
757 @property
759 """
760 Find out if this build is in finished state.
761
762 Build is finished only if all its build_chroots are in finished state or
763 the build was canceled.
764 """
765 return self.canceled or all([chroot.finished for chroot in self.build_chroots])
766
767 @property
769 """
770 Find out if this build is persistent.
771
772 This property is inherited from the project.
773 """
774 return self.copr.persistent
775
776 @property
778 """
779 Extract source package name from source name or url.
780 todo: obsolete
781 """
782 try:
783 src_rpm_name = self.pkgs.split("/")[-1]
784 except:
785 return None
786 if src_rpm_name.endswith(".src.rpm"):
787 return src_rpm_name[:-8]
788 else:
789 return src_rpm_name
790
791 @property
793 try:
794 return self.package.name
795 except:
796 return None
797
798 - def to_dict(self, options=None, with_chroot_states=False):
811
814 """
815 1:N mapping: branch -> chroots
816 """
817
818
819 name = db.Column(db.String(50), primary_key=True)
820
821
822 -class MockChroot(db.Model, helpers.Serializer):
823
824 """
825 Representation of mock chroot
826 """
827 __table_args__ = (
828 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'),
829 )
830
831 id = db.Column(db.Integer, primary_key=True)
832
833 os_release = db.Column(db.String(50), nullable=False)
834
835 os_version = db.Column(db.String(50), nullable=False)
836
837 arch = db.Column(db.String(50), nullable=False)
838 is_active = db.Column(db.Boolean, default=True)
839
840
841 distgit_branch_name = db.Column(db.String(50),
842 db.ForeignKey("dist_git_branch.name"),
843 nullable=False)
844
845 distgit_branch = db.relationship("DistGitBranch",
846 backref=db.backref("chroots"))
847
848 @classmethod
857
858 @property
860 """
861 Textual representation of name of this chroot
862 """
863 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
864
865 @property
867 """
868 Textual representation of name of this or release
869 """
870 return "{}-{}".format(self.os_release, self.os_version)
871
872 @property
874 """
875 Textual representation of name of this or release
876 """
877 return "{} {}".format(self.os_release, self.os_version)
878
879 @property
881 """
882 Textual representation of the operating system name
883 """
884 return "{0} {1}".format(self.os_release, self.os_version)
885
886 @property
891
892
893 -class CoprChroot(db.Model, helpers.Serializer):
894
895 """
896 Representation of Copr<->MockChroot relation
897 """
898
899 buildroot_pkgs = db.Column(db.Text)
900 repos = db.Column(db.Text, default="", server_default="", nullable=False)
901 mock_chroot_id = db.Column(
902 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
903 mock_chroot = db.relationship(
904 "MockChroot", backref=db.backref("copr_chroots"))
905 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
906 copr = db.relationship("Copr",
907 backref=db.backref(
908 "copr_chroots",
909 single_parent=True,
910 cascade="all,delete,delete-orphan"))
911
912 comps_zlib = db.Column(db.LargeBinary(), nullable=True)
913 comps_name = db.Column(db.String(127), nullable=True)
914
915 module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
916 module_md_name = db.Column(db.String(127), nullable=True)
917
919 if isinstance(comps_xml, str):
920 data = comps_xml.encode("utf-8")
921 else:
922 data = comps_xml
923 self.comps_zlib = zlib.compress(data)
924
926 if isinstance(module_md_yaml, str):
927 data = module_md_yaml.encode("utf-8")
928 else:
929 data = module_md_yaml
930 self.module_md_zlib = zlib.compress(data)
931
932 @property
935
936 @property
938 return self.repos.split()
939
940 @property
944
945 @property
949
950 @property
956
957 @property
963
964 @property
967
968 @property
971
973 options = {"__columns_only__": [
974 "buildroot_pkgs", "repos", "comps_name", "copr_id"
975 ]}
976 d = super(CoprChroot, self).to_dict(options=options)
977 d["mock_chroot"] = self.mock_chroot.name
978 return d
979
982
983 """
984 Representation of Build<->MockChroot relation
985 """
986
987 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
988 primary_key=True)
989 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
990 build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
991 primary_key=True)
992 build = db.relationship("Build", backref=db.backref("build_chroots"))
993 git_hash = db.Column(db.String(40))
994 status = db.Column(db.Integer, default=StatusEnum("waiting"))
995
996 started_on = db.Column(db.Integer)
997 ended_on = db.Column(db.Integer, index=True)
998
999 build_requires = db.Column(db.Text)
1000
1001 @property
1003 """
1004 Textual representation of name of this chroot
1005 """
1006 return self.mock_chroot.name
1007
1008 @property
1010 """
1011 Return text representation of status of this build chroot
1012 """
1013 if self.status is not None:
1014 return StatusEnum(self.status)
1015 return "unknown"
1016
1017 @property
1019 return (self.state in ["succeeded", "forked", "canceled", "skipped", "failed"])
1020
1021 @property
1024
1025 @property
1037
1038 @property
1040 return urljoin(app.config["BACKEND_BASE_URL"],
1041 os.path.join("results", self.result_dir, "")
1042 )
1043
1044 @property
1065
1067 return "<BuildChroot: {}>".format(self.to_dict())
1068
1069
1070 -class LegalFlag(db.Model, helpers.Serializer):
1071 id = db.Column(db.Integer, primary_key=True)
1072
1073 raise_message = db.Column(db.Text)
1074
1075 raised_on = db.Column(db.Integer)
1076
1077 resolved_on = db.Column(db.Integer)
1078
1079
1080 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
1081
1082 copr = db.relationship(
1083 "Copr", backref=db.backref("legal_flags", cascade="all"))
1084
1085 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
1086 reporter = db.relationship("User",
1087 backref=db.backref("legal_flags_raised"),
1088 foreign_keys=[reporter_id],
1089 primaryjoin="LegalFlag.reporter_id==User.id")
1090
1091 resolver_id = db.Column(
1092 db.Integer, db.ForeignKey("user.id"), nullable=True)
1093 resolver = db.relationship("User",
1094 backref=db.backref("legal_flags_resolved"),
1095 foreign_keys=[resolver_id],
1096 primaryjoin="LegalFlag.resolver_id==User.id")
1097
1098
1099 -class Action(db.Model, helpers.Serializer):
1156
1157
1158 -class Krb5Login(db.Model, helpers.Serializer):
1159 """
1160 Represents additional user information for kerberos authentication.
1161 """
1162
1163 __tablename__ = "krb5_login"
1164
1165
1166 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1167
1168
1169 config_name = db.Column(db.String(30), nullable=False, primary_key=True)
1170
1171
1172 primary = db.Column(db.String(80), nullable=False, primary_key=True)
1173
1174 user = db.relationship("User", backref=db.backref("krb5_logins"))
1175
1178 """
1179 Generic store for simple statistics.
1180 """
1181
1182 name = db.Column(db.String(127), primary_key=True)
1183 counter_type = db.Column(db.String(30))
1184
1185 counter = db.Column(db.Integer, default=0, server_default="0")
1186
1187
1188 -class Group(db.Model, helpers.Serializer):
1189 """
1190 Represents FAS groups and their aliases in Copr
1191 """
1192 id = db.Column(db.Integer, primary_key=True)
1193 name = db.Column(db.String(127))
1194
1195
1196 fas_name = db.Column(db.String(127))
1197
1198 @property
1200 return u"@{}".format(self.name)
1201
1204
1207
1208
1209 -class Batch(db.Model):
1210 id = db.Column(db.Integer, primary_key=True)
1211
1212
1213 -class Module(db.Model, helpers.Serializer):
1214 id = db.Column(db.Integer, primary_key=True)
1215 name = db.Column(db.String(100), nullable=False)
1216 stream = db.Column(db.String(100), nullable=False)
1217 version = db.Column(db.BigInteger, nullable=False)
1218 summary = db.Column(db.String(100), nullable=False)
1219 description = db.Column(db.Text)
1220 created_on = db.Column(db.Integer, nullable=True)
1221
1222
1223
1224
1225
1226
1227
1228 yaml_b64 = db.Column(db.Text)
1229
1230
1231 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
1232 copr = db.relationship("Copr", backref=db.backref("modules"))
1233
1234 __table_args__ = (
1235 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"),
1236 )
1237
1238 @property
1240 return base64.b64decode(self.yaml_b64)
1241
1242 @property
1244 mmd = modulemd.ModuleMetadata()
1245 mmd.loads(self.yaml)
1246 return mmd
1247
1248 @property
1251
1252 @property
1255
1256 @property
1259
1260 @property
1268
1269 @property
1275