-
Notifications
You must be signed in to change notification settings - Fork 56
/
models.py
1113 lines (887 loc) · 35 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import copy
import datetime
import json
import os
import flask
import json
import base64
import modulemd
from sqlalchemy import orm
from sqlalchemy.ext.associationproxy import association_proxy
from libravatar import libravatar_url
import zlib
from coprs import constants
from coprs import db
from coprs import helpers
from coprs import app
import itertools
import operator
from coprs.helpers import BuildSourceEnum, StatusEnum, ActionTypeEnum, JSONEncodedDict
class CoprSearchRelatedData(object):
def get_search_related_copr_id(self):
raise "Not Implemented"
class User(db.Model, helpers.Serializer):
"""
Represents user of the copr frontend
"""
# PK; TODO: the 'username' could be also PK
id = db.Column(db.Integer, primary_key=True)
# unique username
username = db.Column(db.String(100), nullable=False, unique=True)
# email
mail = db.Column(db.String(150), nullable=False)
# optional timezone
timezone = db.Column(db.String(50), nullable=True)
# is this user proven? proven users can modify builder memory and
# timeout for single builds
proven = db.Column(db.Boolean, default=False)
# is this user admin of the system?
admin = db.Column(db.Boolean, default=False)
# stuff for the cli interface
api_login = db.Column(db.String(40), nullable=False, default="abc")
api_token = db.Column(db.String(40), nullable=False, default="abc")
api_token_expiration = db.Column(
db.Date, nullable=False, default=datetime.date(2000, 1, 1))
# list of groups as retrieved from openid
openid_groups = db.Column(JSONEncodedDict)
@property
def name(self):
"""
Return the short username of the user, e.g. bkabrda
"""
return self.username
def permissions_for_copr(self, copr):
"""
Get permissions of this user for the given copr.
Caches the permission during one request,
so use this if you access them multiple times
"""
if not hasattr(self, "_permissions_for_copr"):
self._permissions_for_copr = {}
if copr.name not in self._permissions_for_copr:
self._permissions_for_copr[copr.name] = (
CoprPermission.query
.filter_by(user=self)
.filter_by(copr=copr)
.first()
)
return self._permissions_for_copr[copr.name]
def can_build_in(self, copr):
"""
Determine if this user can build in the given copr.
"""
can_build = False
if copr.user_id == self.id:
can_build = True
if (self.permissions_for_copr(copr) and
self.permissions_for_copr(copr).copr_builder ==
helpers.PermissionEnum("approved")):
can_build = True
# a bit dirty code, here we access flask.session object
if copr.group is not None and \
copr.group.fas_name in self.user_teams:
return True
return can_build
@property
def user_teams(self):
if self.openid_groups and 'fas_groups' in self.openid_groups:
return self.openid_groups['fas_groups']
else:
return []
@property
def user_groups(self):
return Group.query.filter(Group.fas_name.in_(self.user_teams)).all()
def can_build_in_group(self, group):
"""
:type group: Group
"""
if group.fas_name in self.user_teams:
return True
else:
return False
def can_edit(self, copr):
"""
Determine if this user can edit the given copr.
"""
if copr.user == self or self.admin:
return True
if (self.permissions_for_copr(copr) and
self.permissions_for_copr(copr).copr_admin ==
helpers.PermissionEnum("approved")):
return True
if copr.group is not None and \
copr.group.fas_name in self.user_teams:
return True
return False
@property
def serializable_attributes(self):
# enumerate here to prevent exposing credentials
return ["id", "name"]
@property
def coprs_count(self):
"""
Get number of coprs for this user.
"""
return (Copr.query.filter_by(user=self).
filter_by(deleted=False).
filter_by(group_id=None).
count())
@property
def gravatar_url(self):
"""
Return url to libravatar image.
"""
try:
return libravatar_url(email=self.mail, https=True)
except IOError:
return ""
class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
"""
Represents a single copr (private repo with builds, mock chroots, etc.).
"""
id = db.Column(db.Integer, primary_key=True)
# name of the copr, no fancy chars (checked by forms)
name = db.Column(db.String(100), nullable=False)
homepage = db.Column(db.Text)
contact = db.Column(db.Text)
# string containing urls of additional repos (separated by space)
# that this copr will pull dependencies from
repos = db.Column(db.Text)
# time of creation as returned by int(time.time())
created_on = db.Column(db.Integer)
# description and instructions given by copr owner
description = db.Column(db.Text)
instructions = db.Column(db.Text)
deleted = db.Column(db.Boolean, default=False)
playground = db.Column(db.Boolean, default=False)
# should copr run `createrepo` each time when build packages are changed
auto_createrepo = db.Column(db.Boolean, default=True)
# relations
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
user = db.relationship("User", backref=db.backref("coprs"))
group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
group = db.relationship("Group", backref=db.backref("groups"))
mock_chroots = association_proxy("copr_chroots", "mock_chroot")
forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks"))
# a secret to be used for webhooks authentication
webhook_secret = db.Column(db.String(100))
# enable networking for the builds by default
build_enable_net = db.Column(db.Boolean, default=True,
server_default="1", nullable=False)
unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False)
# information for search index updating
latest_indexed_data_update = db.Column(db.Integer)
# builds and the project are immune against deletion
persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0")
__mapper_args__ = {
"order_by": created_on.desc()
}
@property
def is_a_group_project(self):
"""
Return True if copr belongs to a group
"""
return self.group_id is not None
@property
def owner(self):
"""
Return owner (user or group) of this copr
"""
return self.group if self.is_a_group_project else self.user
@property
def owner_name(self):
"""
Return @group.name for a copr owned by a group and user.name otherwise
"""
return self.group.at_name if self.is_a_group_project else self.user.name
@property
def repos_list(self):
"""
Return repos of this copr as a list of strings
"""
return self.repos.split()
@property
def active_chroots(self):
"""
Return list of active mock_chroots of this copr
"""
return filter(lambda x: x.is_active, self.mock_chroots)
@property
def active_copr_chroots(self):
"""
:rtype: list of CoprChroot
"""
return [c for c in self.copr_chroots if c.is_active]
@property
def active_chroots_sorted(self):
"""
Return list of active mock_chroots of this copr
"""
return sorted(self.active_chroots, key=lambda ch: ch.name)
@property
def active_chroots_grouped(self):
"""
Return list of active mock_chroots of this copr
"""
chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted]
output = []
for os, chs in itertools.groupby(chroots, operator.itemgetter(0)):
output.append((os, [ch[1] for ch in chs]))
return output
@property
def build_count(self):
"""
Return number of builds in this copr
"""
return len(self.builds)
@property
def disable_createrepo(self):
return not self.auto_createrepo
@disable_createrepo.setter
def disable_createrepo(self, value):
self.auto_createrepo = not bool(value)
@property
def modified_chroots(self):
"""
Return list of chroots which has been modified
"""
modified_chroots = []
for chroot in self.copr_chroots:
if chroot.buildroot_pkgs and chroot.is_active:
modified_chroots.append(chroot)
return modified_chroots
def is_release_arch_modified(self, name_release, arch):
if "{}-{}".format(name_release, arch) in \
[chroot.name for chroot in self.modified_chroots]:
return True
return False
@property
def full_name(self):
return "{}/{}".format(self.owner_name, self.name)
@property
def repo_name(self):
return "{}-{}".format(self.owner_name, self.name)
@property
def repo_url(self):
return "/".join([app.config["BACKEND_BASE_URL"],
u"results",
self.full_name])
@property
def modules_url(self):
return "/".join([self.repo_url, "modules"])
@property
def repo_id(self):
if self.is_a_group_project:
return "group_{}-{}".format(self.group.name, self.name)
else:
return "{}-{}".format(self.user.name, self.name)
def to_dict(self, private=False, show_builds=True, show_chroots=True):
result = {}
for key in ["id", "name", "description", "instructions"]:
result[key] = str(copy.copy(getattr(self, key)))
result["owner"] = self.owner_name
return result
@property
def still_forking(self):
return bool(Action.query.filter(Action.result == helpers.BackendResultEnum("waiting"))
.filter(Action.action_type == helpers.ActionTypeEnum("fork"))
.filter(Action.new_value == self.full_name).all())
def get_search_related_copr_id(self):
return self.id
class CoprPermission(db.Model, helpers.Serializer):
"""
Association class for Copr<->Permission relation
"""
# see helpers.PermissionEnum for possible values of the fields below
# can this user build in the copr?
copr_builder = db.Column(db.SmallInteger, default=0)
# can this user serve as an admin? (-> edit and approve permissions)
copr_admin = db.Column(db.SmallInteger, default=0)
# relations
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
user = db.relationship("User", backref=db.backref("copr_permissions"))
copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
"""
Represents a single package in a project.
"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
# Source of the build: type identifier
source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
# Source of the build: description in json, example: git link, srpm url, etc.
source_json = db.Column(db.Text)
# True if the package is built automatically via webhooks
webhook_rebuild = db.Column(db.Boolean, default=False)
# enable networking during a build process
enable_net = db.Column(db.Boolean, default=False,
server_default="0", nullable=False)
# @TODO Remove me few weeks after Copr migration
# Contain status of the Package before migration
# Normally the `status` is not stored in `Package`. It is computed from `status` variable of `BuildChroot`,
# but `old_status` has to be stored here, because we migrate whole `package` table, but only succeeded builds.
# Therefore if `old_status` was in `BuildChroot` we wouldn't be able to know old state of non-succeeded packages
# even though it would be known before migration.
old_status = db.Column(db.Integer)
builds = db.relationship("Build", order_by="Build.id")
# relations
copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
copr = db.relationship("Copr", backref=db.backref("packages"))
@property
def dist_git_repo(self):
return "{}/{}".format(self.copr.full_name, self.name)
@property
def source_json_dict(self):
if not self.source_json:
return {}
return json.loads(self.source_json)
@property
def source_type_text(self):
return helpers.BuildSourceEnum(self.source_type)
@property
def has_source_type_set(self):
"""
Package's source type (and source_json) is being derived from its first build, which works except
for "srpm_link" and "srpm_upload" cases. Consider these being equivalent to source_type being unset.
"""
return self.source_type and self.source_type_text != "srpm_link" and self.source_type_text != "srpm_upload"
@property
def dist_git_url(self):
if app.config["DIST_GIT_URL"]:
return "{}/{}.git".format(app.config["DIST_GIT_URL"], self.dist_git_repo)
return None
def last_build(self, successful=False):
for build in reversed(self.builds):
if not successful or build.state == "succeeded":
return build
return None
def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
package_dict = super(Package, self).to_dict()
package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type'])
if with_latest_build:
build = self.last_build(successful=False)
package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None
if with_latest_succeeded_build:
build = self.last_build(successful=True)
package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None
if with_all_builds:
package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)]
return package_dict
def get_search_related_copr_id(self):
return self.copr.id
class Build(db.Model, helpers.Serializer):
"""
Representation of one build in one copr
"""
__table_args__ = (db.Index('build_canceled', "canceled"), )
id = db.Column(db.Integer, primary_key=True)
# single url to the source rpm, should not contain " ", "\n", "\t"
pkgs = db.Column(db.Text)
# built packages
built_packages = db.Column(db.Text)
# version of the srpm package got by rpm
pkg_version = db.Column(db.Text)
# was this build canceled by user?
canceled = db.Column(db.Boolean, default=False)
# list of space separated additional repos
repos = db.Column(db.Text)
# the three below represent time of important events for this build
# as returned by int(time.time())
submitted_on = db.Column(db.Integer, nullable=False)
# url of the build results
results = db.Column(db.Text)
# memory requirements for backend builder
memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY)
# maximum allowed time of build, build will fail if exceeded
timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT)
# enable networking during a build process
enable_net = db.Column(db.Boolean, default=False,
server_default="0", nullable=False)
# Source of the build: type identifier
source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset"))
# Source of the build: description in json, example: git link, srpm url, etc.
source_json = db.Column(db.Text)
# Type of failure: type identifier
fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset"))
# background builds has lesser priority than regular builds.
is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False)
# relations
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
user = db.relationship("User", backref=db.backref("builds"))
copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
copr = db.relationship("Copr", backref=db.backref("builds"))
package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
package = db.relationship("Package")
chroots = association_proxy("build_chroots", "mock_chroot")
@property
def user_name(self):
return self.user.name
@property
def group_name(self):
return self.copr.group.name
@property
def copr_name(self):
return self.copr.name
@property
def fail_type_text(self):
return helpers.FailTypeEnum(self.fail_type)
@property
def is_older_results_naming_used(self):
# we have changed result directory naming together with transition to dist-git
# that's why we use so strange criterion
return self.build_chroots[0].git_hash is None
@property
def repos_list(self):
if self.repos is None:
return list()
else:
return self.repos.split()
@property
def result_dir_name(self):
# We can remove this ugly condition after migrating Copr to new machines
# It is throw-back from era before dist-git
if self.is_older_results_naming_used:
return self.src_pkg_name
return "{:08d}-{}".format(self.id, self.package.name)
@property
def source_json_dict(self):
if not self.source_json:
return {}
return json.loads(self.source_json)
@property
def started_on(self):
return self.min_started_on
@property
def min_started_on(self):
mb_list = [chroot.started_on for chroot in
self.build_chroots if chroot.started_on]
if len(mb_list) > 0:
return min(mb_list)
else:
return None
@property
def ended_on(self):
return self.max_ended_on
@property
def max_ended_on(self):
if not self.build_chroots:
return None
if any(chroot.ended_on is None for chroot in self.build_chroots):
return None
return max(chroot.ended_on for chroot in self.build_chroots)
@property
def chroots_started_on(self):
return {chroot.name: chroot.started_on for chroot in self.build_chroots}
@property
def chroots_ended_on(self):
return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
@property
def source_type_text(self):
return helpers.BuildSourceEnum(self.source_type)
@property
def source_metadata(self):
if self.source_json is None:
return None
try:
return json.loads(self.source_json)
except (TypeError, ValueError):
return None
@property
def chroot_states(self):
return map(lambda chroot: chroot.status, self.build_chroots)
def get_chroots_by_status(self, statuses=None):
"""
Get build chroots with states which present in `states` list
If states == None, function returns build_chroots
"""
chroot_states_map = dict(zip(self.build_chroots, self.chroot_states))
if statuses is not None:
statuses = set(statuses)
else:
return self.build_chroots
return [
chroot for chroot, status in chroot_states_map.items()
if status in statuses
]
@property
def chroots_dict_by_name(self):
return {b.name: b for b in self.build_chroots}
@property
def has_pending_chroot(self):
# FIXME bad name
# used when checking if the repo is initialized and results can be set
# i think this is the only purpose - check
return StatusEnum("pending") in self.chroot_states or \
StatusEnum("starting") in self.chroot_states
@property
def has_unfinished_chroot(self):
return StatusEnum("pending") in self.chroot_states or \
StatusEnum("starting") in self.chroot_states or \
StatusEnum("running") in self.chroot_states
@property
def has_importing_chroot(self):
return StatusEnum("importing") in self.chroot_states
@property
def status(self):
"""
Return build status according to build status of its chroots
"""
if self.canceled:
return StatusEnum("canceled")
for state in ["running", "starting", "importing", "pending", "failed", "succeeded", "skipped"]:
if StatusEnum(state) in self.chroot_states:
return StatusEnum(state)
@property
def state(self):
"""
Return text representation of status of this build
"""
if self.status is not None:
return StatusEnum(self.status)
return "unknown"
@property
def cancelable(self):
"""
Find out if this build is cancelable.
Build is cancelabel only when it's pending (not started)
"""
return self.status == StatusEnum("pending") or \
self.status == StatusEnum("importing")
@property
def repeatable(self):
"""
Find out if this build is repeatable.
Build is repeatable only if it's not pending, starting or running
"""
return self.status not in [StatusEnum("pending"),
StatusEnum("starting"),
StatusEnum("running"), ]
@property
def finished(self):
"""
Find out if this build is in finished state.
Build is finished only if all its build_chroots are in finished state.
"""
return all([(chroot.state in ["succeeded", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
@property
def persistent(self):
"""
Find out if this build is persistent.
This property is inherited from the project.
"""
return self.copr.persistent
@property
def src_pkg_name(self):
"""
Extract source package name from source name or url
todo: obsolete
"""
try:
src_rpm_name = self.pkgs.split("/")[-1]
except:
return None
if src_rpm_name.endswith(".src.rpm"):
return src_rpm_name[:-8]
else:
return src_rpm_name
@property
def package_name(self):
try:
return self.package.name
except:
return None
def to_dict(self, options=None, with_chroot_states=False):
result = super(Build, self).to_dict(options)
result["src_pkg"] = result["pkgs"]
del result["pkgs"]
del result["copr_id"]
result['source_type'] = helpers.BuildSourceEnum(result['source_type'])
result["state"] = self.state
if with_chroot_states:
result["chroots"] = {b.name: b.state for b in self.build_chroots}
return result
class MockChroot(db.Model, helpers.Serializer):
"""
Representation of mock chroot
"""
id = db.Column(db.Integer, primary_key=True)
# fedora/epel/..., mandatory
os_release = db.Column(db.String(50), nullable=False)
# 18/rawhide/..., optional (mock chroot doesn"t need to have this)
os_version = db.Column(db.String(50), nullable=False)
# x86_64/i686/..., mandatory
arch = db.Column(db.String(50), nullable=False)
is_active = db.Column(db.Boolean, default=True)
@property
def name(self):
"""
Textual representation of name of this chroot
"""
return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
@property
def name_release(self):
"""
Textual representation of name of this or release
"""
return "{}-{}".format(self.os_release, self.os_version)
@property
def name_release_human(self):
"""
Textual representation of name of this or release
"""
return "{} {}".format(self.os_release, self.os_version)
@property
def os(self):
"""
Textual representation of the operating system name
"""
return "{0} {1}".format(self.os_release, self.os_version)
@property
def serializable_attributes(self):
attr_list = super(MockChroot, self).serializable_attributes
attr_list.extend(["name", "os"])
return attr_list
class CoprChroot(db.Model, helpers.Serializer):
"""
Representation of Copr<->MockChroot relation
"""
buildroot_pkgs = db.Column(db.Text)
mock_chroot_id = db.Column(
db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True)
mock_chroot = db.relationship(
"MockChroot", backref=db.backref("copr_chroots"))
copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True)
copr = db.relationship("Copr",
backref=db.backref(
"copr_chroots",
single_parent=True,
cascade="all,delete,delete-orphan"))
comps_zlib = db.Column(db.LargeBinary(), nullable=True)
comps_name = db.Column(db.String(127), nullable=True)
module_md_zlib = db.Column(db.LargeBinary(), nullable=True)
module_md_name = db.Column(db.String(127), nullable=True)
def update_comps(self, comps_xml):
self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
def update_module_md(self, module_md_yaml):
self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
@property
def buildroot_pkgs_list(self):
return self.buildroot_pkgs.split()
@property
def comps(self):
if self.comps_zlib:
return zlib.decompress(self.comps_zlib).decode("utf-8")
@property
def module_md(self):
if self.module_md_zlib:
return zlib.decompress(self.module_md_zlib).decode("utf-8")
@property
def comps_len(self):
if self.comps_zlib:
return len(zlib.decompress(self.comps_zlib))
else:
return 0
@property
def module_md_len(self):
if self.module_md_zlib:
return len(zlib.decompress(self.module_md_zlib))
else:
return 0
@property
def name(self):
return self.mock_chroot.name
@property
def is_active(self):
return self.mock_chroot.is_active
class BuildChroot(db.Model, helpers.Serializer):
"""
Representation of Build<->MockChroot relation
"""
mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"),
primary_key=True)
mock_chroot = db.relationship("MockChroot", backref=db.backref("builds"))
build_id = db.Column(db.Integer, db.ForeignKey("build.id"),
primary_key=True)
build = db.relationship("Build", backref=db.backref("build_chroots"))
git_hash = db.Column(db.String(40))
status = db.Column(db.Integer, default=StatusEnum("importing"))
started_on = db.Column(db.Integer)
ended_on = db.Column(db.Integer, index=True)
last_deferred = db.Column(db.Integer)
@property
def name(self):
"""
Textual representation of name of this chroot
"""
return self.mock_chroot.name
@property
def state(self):
"""
Return text representation of status of this build chroot
"""
if self.status is not None:
return StatusEnum(self.status)
return "unknown"
@property
def task_id(self):
return "{}-{}".format(self.build_id, self.name)
@property
def import_task_id(self):
return "{}-{}".format(self.build_id, helpers.chroot_to_branch(self.name))
@property
def dist_git_url(self):
if app.config["DIST_GIT_URL"]:
return "{}/{}.git/commit/?id={}".format(app.config["DIST_GIT_URL"],
self.build.package.dist_git_repo,
self.git_hash)
return None
@property
def import_log_url(self):
if app.config["COPR_DIST_GIT_LOGS_URL"]:
return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"],
self.import_task_id)
return None
@property
def result_dir_url(self):
return "/".join([app.config["BACKEND_BASE_URL"],
u"results",
self.result_dir])
@property
def result_dir(self):
# hide changes occurred after migration to dist-git
# if build has defined dist-git, it means that new schema should be used
# otherwise use older structure
# old: results/valtri/ruby/fedora-rawhide-x86_64/rubygem-aws-sdk-resources-2.1.11-1.fc24/
# new: results/asamalik/rh-perl520/epel-7-x86_64/00000187-rh-perl520/
parts = [self.build.copr.owner_name]
parts.extend([
self.build.copr.name,
self.name,
])
if self.git_hash is not None and self.build.package:
parts.append(self.build.result_dir_name)
else:
parts.append(self.build.src_pkg_name)
return os.path.join(*parts)
def __str__(self):
return "<BuildChroot: {}>".format(self.to_dict())
class LegalFlag(db.Model, helpers.Serializer):
id = db.Column(db.Integer, primary_key=True)
# message from user who raised the flag (what he thinks is wrong)
raise_message = db.Column(db.Text)
# time of raising the flag as returned by int(time.time())
raised_on = db.Column(db.Integer)
# time of resolving the flag by admin as returned by int(time.time())
resolved_on = db.Column(db.Integer)
# relations
copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True)
# cascade="all" means that we want to keep these even if copr is deleted
copr = db.relationship(
"Copr", backref=db.backref("legal_flags", cascade="all"))
# user who reported the problem
reporter_id = db.Column(db.Integer, db.ForeignKey("user.id"))
reporter = db.relationship("User",
backref=db.backref("legal_flags_raised"),
foreign_keys=[reporter_id],
primaryjoin="LegalFlag.reporter_id==User.id")
# admin who resolved the problem
resolver_id = db.Column(
db.Integer, db.ForeignKey("user.id"), nullable=True)
resolver = db.relationship("User",
backref=db.backref("legal_flags_resolved"),
foreign_keys=[resolver_id],
primaryjoin="LegalFlag.resolver_id==User.id")
class Action(db.Model, helpers.Serializer):
"""
Representation of a custom action that needs
backends cooperation/admin attention/...
"""