Package coprs :: Module models
[hide private]
[frames] | no frames]

Source Code for Module coprs.models

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