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 modules_url(self):
341 return "/".join([self.repo_url, "modules"])
342 343 @property
344 - def repo_id(self):
345 if self.is_a_group_project: 346 return "group_{}-{}".format(self.group.name, self.name) 347 else: 348 return "{}-{}".format(self.user.name, self.name)
349
350 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
351 result = {} 352 for key in ["id", "name", "description", "instructions"]: 353 result[key] = str(copy.copy(getattr(self, key))) 354 result["owner"] = self.owner_name 355 return result
356 357 @property
358 - def still_forking(self):
359 return bool(Action.query.filter(Action.result == helpers.BackendResultEnum("waiting")) 360 .filter(Action.action_type == helpers.ActionTypeEnum("fork")) 361 .filter(Action.new_value == self.full_name).all())
362
365
366 367 -class CoprPermission(db.Model, helpers.Serializer):
368 369 """ 370 Association class for Copr<->Permission relation 371 """ 372 373 # see helpers.PermissionEnum for possible values of the fields below 374 # can this user build in the copr? 375 copr_builder = db.Column(db.SmallInteger, default=0) 376 # can this user serve as an admin? (-> edit and approve permissions) 377 copr_admin = db.Column(db.SmallInteger, default=0) 378 379 # relations 380 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True) 381 user = db.relationship("User", backref=db.backref("copr_permissions")) 382 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 383 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
384
385 386 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
387 """ 388 Represents a single package in a project. 389 """ 390 id = db.Column(db.Integer, primary_key=True) 391 name = db.Column(db.String(100), nullable=False) 392 # Source of the build: type identifier 393 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset")) 394 # Source of the build: description in json, example: git link, srpm url, etc. 395 source_json = db.Column(db.Text) 396 # True if the package is built automatically via webhooks 397 webhook_rebuild = db.Column(db.Boolean, default=False) 398 # enable networking during a build process 399 enable_net = db.Column(db.Boolean, default=False, 400 server_default="0", nullable=False) 401 402 # @TODO Remove me few weeks after Copr migration 403 # Contain status of the Package before migration 404 # Normally the `status` is not stored in `Package`. It is computed from `status` variable of `BuildChroot`, 405 # but `old_status` has to be stored here, because we migrate whole `package` table, but only succeeded builds. 406 # Therefore if `old_status` was in `BuildChroot` we wouldn't be able to know old state of non-succeeded packages 407 # even though it would be known before migration. 408 old_status = db.Column(db.Integer) 409 410 builds = db.relationship("Build", order_by="Build.id") 411 412 # relations 413 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 414 copr = db.relationship("Copr", backref=db.backref("packages")) 415 416 @property
417 - def dist_git_repo(self):
418 return "{}/{}".format(self.copr.full_name, self.name)
419 420 @property
421 - def source_json_dict(self):
422 if not self.source_json: 423 return {} 424 return json.loads(self.source_json)
425 426 @property
427 - def source_type_text(self):
429 430 @property
431 - def has_source_type_set(self):
432 """ 433 Package's source type (and source_json) is being derived from its first build, which works except 434 for "srpm_link" and "srpm_upload" cases. Consider these being equivalent to source_type being unset. 435 """ 436 return self.source_type and self.source_type_text != "srpm_link" and self.source_type_text != "srpm_upload"
437 438 @property
439 - def dist_git_url(self):
440 if app.config["DIST_GIT_URL"]: 441 return "{}/{}.git".format(app.config["DIST_GIT_URL"], self.dist_git_repo) 442 return None
443
444 - def last_build(self, successful=False):
445 for build in reversed(self.builds): 446 if not successful or build.state == "succeeded": 447 return build 448 return None
449
450 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
451 package_dict = super(Package, self).to_dict() 452 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type']) 453 454 if with_latest_build: 455 build = self.last_build(successful=False) 456 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None 457 if with_latest_succeeded_build: 458 build = self.last_build(successful=True) 459 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None 460 if with_all_builds: 461 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)] 462 463 return package_dict
464
467
468 469 -class Build(db.Model, helpers.Serializer):
470 471 """ 472 Representation of one build in one copr 473 """ 474 __table_args__ = (db.Index('build_canceled', "canceled"), ) 475 476 id = db.Column(db.Integer, primary_key=True) 477 # single url to the source rpm, should not contain " ", "\n", "\t" 478 pkgs = db.Column(db.Text) 479 # built packages 480 built_packages = db.Column(db.Text) 481 # version of the srpm package got by rpm 482 pkg_version = db.Column(db.Text) 483 # was this build canceled by user? 484 canceled = db.Column(db.Boolean, default=False) 485 # list of space separated additional repos 486 repos = db.Column(db.Text) 487 # the three below represent time of important events for this build 488 # as returned by int(time.time()) 489 submitted_on = db.Column(db.Integer, nullable=False) 490 # url of the build results 491 results = db.Column(db.Text) 492 # memory requirements for backend builder 493 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY) 494 # maximum allowed time of build, build will fail if exceeded 495 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT) 496 # enable networking during a build process 497 enable_net = db.Column(db.Boolean, default=False, 498 server_default="0", nullable=False) 499 # Source of the build: type identifier 500 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset")) 501 # Source of the build: description in json, example: git link, srpm url, etc. 502 source_json = db.Column(db.Text) 503 # Type of failure: type identifier 504 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset")) 505 # background builds has lesser priority than regular builds. 506 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False) 507 508 # relations 509 user_id = db.Column(db.Integer, db.ForeignKey("user.id")) 510 user = db.relationship("User", backref=db.backref("builds")) 511 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 512 copr = db.relationship("Copr", backref=db.backref("builds")) 513 package_id = db.Column(db.Integer, db.ForeignKey("package.id")) 514 package = db.relationship("Package") 515 516 chroots = association_proxy("build_chroots", "mock_chroot") 517 518 @property
519 - def user_name(self):
520 return self.user.name
521 522 @property
523 - def group_name(self):
524 return self.copr.group.name
525 526 @property
527 - def copr_name(self):
528 return self.copr.name
529 530 @property
531 - def fail_type_text(self):
532 return helpers.FailTypeEnum(self.fail_type)
533 534 @property
536 # we have changed result directory naming together with transition to dist-git 537 # that's why we use so strange criterion 538 return self.build_chroots[0].git_hash is None
539 540 @property
541 - def repos_list(self):
542 if self.repos is None: 543 return list() 544 else: 545 return self.repos.split()
546 547 @property
548 - def result_dir_name(self):
549 # We can remove this ugly condition after migrating Copr to new machines 550 # It is throw-back from era before dist-git 551 if self.is_older_results_naming_used: 552 return self.src_pkg_name 553 554 return "{:08d}-{}".format(self.id, self.package.name)
555 556 @property
557 - def source_json_dict(self):
558 if not self.source_json: 559 return {} 560 return json.loads(self.source_json)
561 562 @property
563 - def started_on(self):
564 return self.min_started_on
565 566 @property
567 - def min_started_on(self):
568 mb_list = [chroot.started_on for chroot in 569 self.build_chroots if chroot.started_on] 570 if len(mb_list) > 0: 571 return min(mb_list) 572 else: 573 return None
574 575 @property
576 - def ended_on(self):
577 return self.max_ended_on
578 579 @property
580 - def max_ended_on(self):
581 if not self.build_chroots: 582 return None 583 if any(chroot.ended_on is None for chroot in self.build_chroots): 584 return None 585 return max(chroot.ended_on for chroot in self.build_chroots)
586 587 @property
588 - def chroots_started_on(self):
589 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
590 591 @property
592 - def chroots_ended_on(self):
593 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
594 595 @property
596 - def source_type_text(self):
598 599 @property
600 - def source_metadata(self):
601 if self.source_json is None: 602 return None 603 604 try: 605 return json.loads(self.source_json) 606 except (TypeError, ValueError): 607 return None
608 609 @property
610 - def chroot_states(self):
611 return map(lambda chroot: chroot.status, self.build_chroots)
612
613 - def get_chroots_by_status(self, statuses=None):
614 """ 615 Get build chroots with states which present in `states` list 616 If states == None, function returns build_chroots 617 """ 618 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states)) 619 if statuses is not None: 620 statuses = set(statuses) 621 else: 622 return self.build_chroots 623 624 return [ 625 chroot for chroot, status in chroot_states_map.items() 626 if status in statuses 627 ]
628 629 @property
630 - def chroots_dict_by_name(self):
631 return {b.name: b for b in self.build_chroots}
632 633 @property
634 - def has_pending_chroot(self):
635 # FIXME bad name 636 # used when checking if the repo is initialized and results can be set 637 # i think this is the only purpose - check 638 return StatusEnum("pending") in self.chroot_states or \ 639 StatusEnum("starting") in self.chroot_states
640 641 @property
642 - def has_unfinished_chroot(self):
643 return StatusEnum("pending") in self.chroot_states or \ 644 StatusEnum("starting") in self.chroot_states or \ 645 StatusEnum("running") in self.chroot_states
646 647 @property
648 - def has_importing_chroot(self):
649 return StatusEnum("importing") in self.chroot_states
650 651 @property
652 - def status(self):
653 """ 654 Return build status according to build status of its chroots 655 """ 656 if self.canceled: 657 return StatusEnum("canceled") 658 659 for state in ["running", "starting", "importing", "pending", "failed", "succeeded", "skipped"]: 660 if StatusEnum(state) in self.chroot_states: 661 return StatusEnum(state)
662 663 @property
664 - def state(self):
665 """ 666 Return text representation of status of this build 667 """ 668 669 if self.status is not None: 670 return StatusEnum(self.status) 671 672 return "unknown"
673 674 @property
675 - def cancelable(self):
676 """ 677 Find out if this build is cancelable. 678 679 Build is cancelabel only when it's pending (not started) 680 """ 681 682 return self.status == StatusEnum("pending") or \ 683 self.status == StatusEnum("importing")
684 685 @property
686 - def repeatable(self):
687 """ 688 Find out if this build is repeatable. 689 690 Build is repeatable only if it's not pending, starting or running 691 """ 692 return self.status not in [StatusEnum("pending"), 693 StatusEnum("starting"), 694 StatusEnum("running"), ]
695 696 @property
697 - def finished(self):
698 """ 699 Find out if this build is in finished state. 700 701 Build is finished only if all its build_chroots are in finished state. 702 """ 703 return all([(chroot.state in ["succeeded", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
704 705 @property
706 - def persistent(self):
707 """ 708 Find out if this build is persistent. 709 710 This property is inherited from the project. 711 """ 712 return self.copr.persistent
713 714 @property
715 - def src_pkg_name(self):
716 """ 717 Extract source package name from source name or url 718 todo: obsolete 719 """ 720 try: 721 src_rpm_name = self.pkgs.split("/")[-1] 722 except: 723 return None 724 if src_rpm_name.endswith(".src.rpm"): 725 return src_rpm_name[:-8] 726 else: 727 return src_rpm_name
728 729 @property
730 - def package_name(self):
731 try: 732 return self.package.name 733 except: 734 return None
735
736 - def to_dict(self, options=None, with_chroot_states=False):
737 result = super(Build, self).to_dict(options) 738 result["src_pkg"] = result["pkgs"] 739 del result["pkgs"] 740 del result["copr_id"] 741 742 result['source_type'] = helpers.BuildSourceEnum(result['source_type']) 743 result["state"] = self.state 744 745 if with_chroot_states: 746 result["chroots"] = {b.name: b.state for b in self.build_chroots} 747 748 return result
749
750 751 -class MockChroot(db.Model, helpers.Serializer):
752 753 """ 754 Representation of mock chroot 755 """ 756 757 id = db.Column(db.Integer, primary_key=True) 758 # fedora/epel/..., mandatory 759 os_release = db.Column(db.String(50), nullable=False) 760 # 18/rawhide/..., optional (mock chroot doesn"t need to have this) 761 os_version = db.Column(db.String(50), nullable=False) 762 # x86_64/i686/..., mandatory 763 arch = db.Column(db.String(50), nullable=False) 764 is_active = db.Column(db.Boolean, default=True) 765 766 @property
767 - def name(self):
768 """ 769 Textual representation of name of this chroot 770 """ 771 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
772 773 @property
774 - def name_release(self):
775 """ 776 Textual representation of name of this or release 777 """ 778 return "{}-{}".format(self.os_release, self.os_version)
779 780 @property
781 - def name_release_human(self):
782 """ 783 Textual representation of name of this or release 784 """ 785 return "{} {}".format(self.os_release, self.os_version)
786 787 @property
788 - def os(self):
789 """ 790 Textual representation of the operating system name 791 """ 792 return "{0} {1}".format(self.os_release, self.os_version)
793 794 @property
795 - def serializable_attributes(self):
796 attr_list = super(MockChroot, self).serializable_attributes 797 attr_list.extend(["name", "os"]) 798 return attr_list
799
800 801 -class CoprChroot(db.Model, helpers.Serializer):
802 803 """ 804 Representation of Copr<->MockChroot relation 805 """ 806 807 buildroot_pkgs = db.Column(db.Text) 808 mock_chroot_id = db.Column( 809 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True) 810 mock_chroot = db.relationship( 811 "MockChroot", backref=db.backref("copr_chroots")) 812 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 813 copr = db.relationship("Copr", 814 backref=db.backref( 815 "copr_chroots", 816 single_parent=True, 817 cascade="all,delete,delete-orphan")) 818 819 comps_zlib = db.Column(db.LargeBinary(), nullable=True) 820 comps_name = db.Column(db.String(127), nullable=True) 821 822 module_md_zlib = db.Column(db.LargeBinary(), nullable=True) 823 module_md_name = db.Column(db.String(127), nullable=True) 824
825 - def update_comps(self, comps_xml):
826 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
827
828 - def update_module_md(self, module_md_yaml):
829 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
830 831 @property
832 - def buildroot_pkgs_list(self):
833 return self.buildroot_pkgs.split()
834 835 @property
836 - def comps(self):
837 if self.comps_zlib: 838 return zlib.decompress(self.comps_zlib).decode("utf-8")
839 840 @property
841 - def module_md(self):
842 if self.module_md_zlib: 843 return zlib.decompress(self.module_md_zlib).decode("utf-8")
844 845 @property
846 - def comps_len(self):
847 if self.comps_zlib: 848 return len(zlib.decompress(self.comps_zlib)) 849 else: 850 return 0
851 852 @property
853 - def module_md_len(self):
854 if self.module_md_zlib: 855 return len(zlib.decompress(self.module_md_zlib)) 856 else: 857 return 0
858 859 @property
860 - def name(self):
861 return self.mock_chroot.name
862 863 @property
864 - def is_active(self):
865 return self.mock_chroot.is_active
866
867 868 -class BuildChroot(db.Model, helpers.Serializer):
869 870 """ 871 Representation of Build<->MockChroot relation 872 """ 873 874 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"), 875 primary_key=True) 876 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds")) 877 build_id = db.Column(db.Integer, db.ForeignKey("build.id"), 878 primary_key=True) 879 build = db.relationship("Build", backref=db.backref("build_chroots")) 880 git_hash = db.Column(db.String(40)) 881 status = db.Column(db.Integer, default=StatusEnum("importing")) 882 883 started_on = db.Column(db.Integer) 884 ended_on = db.Column(db.Integer) 885 886 last_deferred = db.Column(db.Integer) 887 888 @property
889 - def name(self):
890 """ 891 Textual representation of name of this chroot 892 """ 893 894 return self.mock_chroot.name
895 896 @property
897 - def state(self):
898 """ 899 Return text representation of status of this build chroot 900 """ 901 902 if self.status is not None: 903 return StatusEnum(self.status) 904 905 return "unknown"
906 907 @property
908 - def task_id(self):
909 return "{}-{}".format(self.build_id, self.name)
910 911 @property
912 - def import_task_id(self):
913 return "{}-{}".format(self.build_id, helpers.chroot_to_branch(self.name))
914 915 @property
916 - def dist_git_url(self):
917 if app.config["DIST_GIT_URL"]: 918 return "{}/{}.git/commit/?id={}".format(app.config["DIST_GIT_URL"], 919 self.build.package.dist_git_repo, 920 self.git_hash) 921 return None
922 923 @property
924 - def import_log_url(self):
925 if app.config["COPR_DIST_GIT_LOGS_URL"]: 926 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"], 927 self.import_task_id) 928 return None
929 930 @property
931 - def result_dir_url(self):
932 return "/".join([app.config["BACKEND_BASE_URL"], 933 u"results", 934 self.result_dir])
935 936 @property
937 - def result_dir(self):
938 # hide changes occurred after migration to dist-git 939 # if build has defined dist-git, it means that new schema should be used 940 # otherwise use older structure 941 942 # old: results/valtri/ruby/fedora-rawhide-x86_64/rubygem-aws-sdk-resources-2.1.11-1.fc24/ 943 # new: results/asamalik/rh-perl520/epel-7-x86_64/00000187-rh-perl520/ 944 945 parts = [self.build.copr.owner_name] 946 947 parts.extend([ 948 self.build.copr.name, 949 self.name, 950 ]) 951 if self.git_hash is not None and self.build.package: 952 parts.append(self.build.result_dir_name) 953 else: 954 parts.append(self.build.src_pkg_name) 955 956 return os.path.join(*parts)
957
958 - def __str__(self):
959 return "<BuildChroot: {}>".format(self.to_dict())
960
961 962 -class LegalFlag(db.Model, helpers.Serializer):
963 id = db.Column(db.Integer, primary_key=True) 964 # message from user who raised the flag (what he thinks is wrong) 965 raise_message = db.Column(db.Text) 966 # time of raising the flag as returned by int(time.time()) 967 raised_on = db.Column(db.Integer) 968 # time of resolving the flag by admin as returned by int(time.time()) 969 resolved_on = db.Column(db.Integer) 970 971 # relations 972 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True) 973 # cascade="all" means that we want to keep these even if copr is deleted 974 copr = db.relationship( 975 "Copr", backref=db.backref("legal_flags", cascade="all")) 976 # user who reported the problem 977 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id")) 978 reporter = db.relationship("User", 979 backref=db.backref("legal_flags_raised"), 980 foreign_keys=[reporter_id], 981 primaryjoin="LegalFlag.reporter_id==User.id") 982 # admin who resolved the problem 983 resolver_id = db.Column( 984 db.Integer, db.ForeignKey("user.id"), nullable=True) 985 resolver = db.relationship("User", 986 backref=db.backref("legal_flags_resolved"), 987 foreign_keys=[resolver_id], 988 primaryjoin="LegalFlag.resolver_id==User.id")
989
990 991 -class Action(db.Model, helpers.Serializer):
992 993 """ 994 Representation of a custom action that needs 995 backends cooperation/admin attention/... 996 """ 997 998 id = db.Column(db.Integer, primary_key=True) 999 # delete, rename, ...; see ActionTypeEnum 1000 action_type = db.Column(db.Integer, nullable=False) 1001 # copr, ...; downcase name of class of modified object 1002 object_type = db.Column(db.String(20)) 1003 # id of the modified object 1004 object_id = db.Column(db.Integer) 1005 # old and new values of the changed property 1006 old_value = db.Column(db.String(255)) 1007 new_value = db.Column(db.String(255)) 1008 # additional data 1009 data = db.Column(db.Text) 1010 # result of the action, see helpers.BackendResultEnum 1011 result = db.Column( 1012 db.Integer, default=helpers.BackendResultEnum("waiting")) 1013 # optional message from the backend/whatever 1014 message = db.Column(db.Text) 1015 # time created as returned by int(time.time()) 1016 created_on = db.Column(db.Integer) 1017 # time ended as returned by int(time.time()) 1018 ended_on = db.Column(db.Integer) 1019
1020 - def __str__(self):
1021 return self.__unicode__()
1022
1023 - def __unicode__(self):
1024 if self.action_type == ActionTypeEnum("delete"): 1025 return "Deleting {0} {1}".format(self.object_type, self.old_value) 1026 elif self.action_type == ActionTypeEnum("rename"): 1027 return "Renaming {0} from {1} to {2}.".format(self.object_type, 1028 self.old_value, 1029 self.new_value) 1030 elif self.action_type == ActionTypeEnum("legal-flag"): 1031 return "Legal flag on copr {0}.".format(self.old_value) 1032 1033 return "Action {0} on {1}, old value: {2}, new value: {3}.".format( 1034 self.action_type, self.object_type, self.old_value, self.new_value)
1035
1036 1037 -class Krb5Login(db.Model, helpers.Serializer):
1038 """ 1039 Represents additional user information for kerberos authentication. 1040 """ 1041 1042 __tablename__ = "krb5_login" 1043 1044 # FK to User table 1045 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) 1046 1047 # 'string' from 'copr.conf' from KRB5_LOGIN[string] 1048 config_name = db.Column(db.String(30), nullable=False, primary_key=True) 1049 1050 # krb's primary, i.e. 'username' from 'username@EXAMPLE.COM' 1051 primary = db.Column(db.String(80), nullable=False, primary_key=True) 1052 1053 user = db.relationship("User", backref=db.backref("krb5_logins"))
1054
1055 1056 -class CounterStat(db.Model, helpers.Serializer):
1057 """ 1058 Generic store for simple statistics. 1059 """ 1060 1061 name = db.Column(db.String(127), primary_key=True) 1062 counter_type = db.Column(db.String(30)) 1063 1064 counter = db.Column(db.Integer, default=0, server_default="0")
1065
1066 1067 -class Group(db.Model, helpers.Serializer):
1068 """ 1069 Represents FAS groups and their aliases in Copr 1070 """ 1071 id = db.Column(db.Integer, primary_key=True) 1072 name = db.Column(db.String(127)) 1073 1074 # TODO: add unique=True 1075 fas_name = db.Column(db.String(127)) 1076 1077 @property
1078 - def at_name(self):
1079 return u"@{}".format(self.name)
1080
1081 - def __str__(self):
1082 return self.__unicode__()
1083
1084 - def __unicode__(self):
1085 return "{} (fas: {})".format(self.name, self.fas_name)
1086