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

Source Code for Module coprs.models

   1  import copy 
   2  import datetime 
   3  import os 
   4  import json 
   5  import base64 
   6  import uuid 
   7  from fnmatch import fnmatch 
   8   
   9  from sqlalchemy import outerjoin 
  10  from sqlalchemy.ext.associationproxy import association_proxy 
  11  from sqlalchemy.orm import column_property, validates 
  12  from six.moves.urllib.parse import urljoin 
  13  from libravatar import libravatar_url 
  14  import zlib 
  15   
  16  from flask import url_for 
  17   
  18  from copr_common.enums import ActionTypeEnum, BackendResultEnum, FailTypeEnum, ModuleStatusEnum, StatusEnum 
  19  from coprs import constants 
  20  from coprs import db 
  21  from coprs import helpers 
  22  from coprs import app 
  23   
  24  import itertools 
  25  import operator 
  26  from coprs.helpers import JSONEncodedDict 
  27   
  28  import gi 
  29  gi.require_version('Modulemd', '1.0') 
  30  from gi.repository import Modulemd 
31 32 33 -class CoprSearchRelatedData(object):
36
37 38 -class _UserPublic(db.Model, helpers.Serializer):
39 """ 40 Represents user of the copr frontend 41 """ 42 __tablename__ = "user" 43 44 id = db.Column(db.Integer, primary_key=True) 45 46 # unique username 47 username = db.Column(db.String(100), nullable=False, unique=True) 48 49 # is this user proven? proven users can modify builder memory and 50 # timeout for single builds 51 proven = db.Column(db.Boolean, default=False) 52 53 # is this user admin of the system? 54 admin = db.Column(db.Boolean, default=False) 55 56 # can this user behave as someone else? 57 proxy = db.Column(db.Boolean, default=False) 58 59 # list of groups as retrieved from openid 60 openid_groups = db.Column(JSONEncodedDict)
61
62 63 -class _UserPrivate(db.Model, helpers.Serializer):
64 """ 65 Records all the private information for a user. 66 """ 67 # id (primary key + foreign key) 68 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True, 69 nullable=False) 70 71 # email 72 mail = db.Column(db.String(150), nullable=False) 73 74 # optional timezone 75 timezone = db.Column(db.String(50), nullable=True) 76 77 # stuff for the cli interface 78 api_login = db.Column(db.String(40), nullable=False, default="abc") 79 api_token = db.Column(db.String(40), nullable=False, default="abc") 80 api_token_expiration = db.Column( 81 db.Date, nullable=False, default=datetime.date(2000, 1, 1))
82
83 84 -class User(db.Model, helpers.Serializer):
85 __table__ = outerjoin(_UserPublic.__table__, _UserPrivate.__table__) 86 id = column_property(_UserPublic.__table__.c.id, _UserPrivate.__table__.c.user_id) 87 88 @property
89 - def name(self):
90 """ 91 Return the short username of the user, e.g. bkabrda 92 """ 93 94 return self.username
95
96 - def permissions_for_copr(self, copr):
97 """ 98 Get permissions of this user for the given copr. 99 Caches the permission during one request, 100 so use this if you access them multiple times 101 """ 102 103 if not hasattr(self, "_permissions_for_copr"): 104 self._permissions_for_copr = {} 105 if copr.name not in self._permissions_for_copr: 106 self._permissions_for_copr[copr.name] = ( 107 CoprPermission.query 108 .filter_by(user=self) 109 .filter_by(copr=copr) 110 .first() 111 ) 112 return self._permissions_for_copr[copr.name]
113
114 - def can_build_in(self, copr):
115 """ 116 Determine if this user can build in the given copr. 117 """ 118 if self.admin: 119 return True 120 if copr.group: 121 if self.can_build_in_group(copr.group): 122 return True 123 elif copr.user_id == self.id: 124 return True 125 if (self.permissions_for_copr(copr) and 126 self.permissions_for_copr(copr).copr_builder == 127 helpers.PermissionEnum("approved")): 128 return True 129 return False
130 131 @property
132 - def user_teams(self):
133 if self.openid_groups and 'fas_groups' in self.openid_groups: 134 return self.openid_groups['fas_groups'] 135 else: 136 return []
137 138 @property
139 - def user_groups(self):
140 return Group.query.filter(Group.fas_name.in_(self.user_teams)).all()
141
142 - def can_build_in_group(self, group):
143 """ 144 :type group: Group 145 """ 146 if group.fas_name in self.user_teams: 147 return True 148 else: 149 return False
150
151 - def can_edit(self, copr):
152 """ 153 Determine if this user can edit the given copr. 154 """ 155 156 if copr.user == self or self.admin: 157 return True 158 if (self.permissions_for_copr(copr) and 159 self.permissions_for_copr(copr).copr_admin == 160 helpers.PermissionEnum("approved")): 161 162 return True 163 164 if copr.group is not None and \ 165 copr.group.fas_name in self.user_teams: 166 return True 167 168 return False
169 170 @property
171 - def serializable_attributes(self):
172 # enumerate here to prevent exposing credentials 173 return ["id", "name"]
174 175 @property
176 - def coprs_count(self):
177 """ 178 Get number of coprs for this user. 179 """ 180 181 return (Copr.query.filter_by(user=self). 182 filter_by(deleted=False). 183 filter_by(group_id=None). 184 count())
185 186 @property
187 - def gravatar_url(self):
188 """ 189 Return url to libravatar image. 190 """ 191 192 try: 193 return libravatar_url(email=self.mail, https=True) 194 except IOError: 195 return ""
196
197 198 -class PinnedCoprs(db.Model, helpers.Serializer):
199 """ 200 Representation of User or Group <-> Copr relation 201 """ 202 id = db.Column(db.Integer, primary_key=True) 203 204 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 205 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True, index=True) 206 group_id = db.Column(db.Integer, db.ForeignKey("group.id"), nullable=True, index=True) 207 position = db.Column(db.Integer, nullable=False) 208 209 copr = db.relationship("Copr") 210 user = db.relationship("User") 211 group = db.relationship("Group")
212
213 214 -class _CoprPublic(db.Model, helpers.Serializer, CoprSearchRelatedData):
215 """ 216 Represents public part of a single copr (personal repo with builds, mock 217 chroots, etc.). 218 """ 219 220 __tablename__ = "copr" 221 __table_args__ = ( 222 db.Index('copr_name_group_id_idx', 'name', 'group_id'), 223 ) 224 225 id = db.Column(db.Integer, primary_key=True) 226 # name of the copr, no fancy chars (checked by forms) 227 name = db.Column(db.String(100), nullable=False) 228 homepage = db.Column(db.Text) 229 contact = db.Column(db.Text) 230 # string containing urls of additional repos (separated by space) 231 # that this copr will pull dependencies from 232 repos = db.Column(db.Text) 233 # time of creation as returned by int(time.time()) 234 created_on = db.Column(db.Integer) 235 # description and instructions given by copr owner 236 description = db.Column(db.Text) 237 instructions = db.Column(db.Text) 238 deleted = db.Column(db.Boolean, default=False) 239 playground = db.Column(db.Boolean, default=False) 240 241 # should copr run `createrepo` each time when build packages are changed 242 auto_createrepo = db.Column(db.Boolean, default=True) 243 244 # relations 245 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True) 246 group_id = db.Column(db.Integer, db.ForeignKey("group.id")) 247 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 248 249 # enable networking for the builds by default 250 build_enable_net = db.Column(db.Boolean, default=True, 251 server_default="1", nullable=False) 252 253 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False) 254 255 # information for search index updating 256 latest_indexed_data_update = db.Column(db.Integer) 257 258 # builds and the project are immune against deletion 259 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0") 260 261 # if backend deletion script should be run for the project's builds 262 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1") 263 264 # use mock's bootstrap container feature 265 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0") 266 267 # if chroots for the new branch should be auto-enabled and populated from rawhide ones 268 follow_fedora_branching = db.Column(db.Boolean, default=True, nullable=False, server_default="1") 269 270 # scm integration properties 271 scm_repo_url = db.Column(db.Text) 272 scm_api_type = db.Column(db.Text) 273 274 # temporary project if non-null 275 delete_after = db.Column(db.DateTime, index=True, nullable=True)
276
277 278 -class _CoprPrivate(db.Model, helpers.Serializer):
279 """ 280 Represents private part of a single copr (personal repo with builds, mock 281 chroots, etc.). 282 """ 283 284 __table_args__ = ( 285 db.Index('copr_private_webhook_secret', 'webhook_secret'), 286 ) 287 288 # copr relation 289 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True, 290 nullable=False, primary_key=True) 291 292 # a secret to be used for webhooks authentication 293 webhook_secret = db.Column(db.String(100)) 294 295 # remote Git sites auth info 296 scm_api_auth_json = db.Column(db.Text)
297
298 299 -class Copr(db.Model, helpers.Serializer):
300 """ 301 Represents private a single copr (personal repo with builds, mock chroots, 302 etc.). 303 """ 304 305 # This model doesn't have a single corresponding database table - so please 306 # define any new Columns in _CoprPublic or _CoprPrivate models! 307 __table__ = outerjoin(_CoprPublic.__table__, _CoprPrivate.__table__) 308 id = column_property( 309 _CoprPublic.__table__.c.id, 310 _CoprPrivate.__table__.c.copr_id 311 ) 312 313 # relations 314 user = db.relationship("User", backref=db.backref("coprs")) 315 group = db.relationship("Group", backref=db.backref("groups")) 316 mock_chroots = association_proxy("copr_chroots", "mock_chroot") 317 forked_from = db.relationship("Copr", 318 remote_side=_CoprPublic.id, 319 foreign_keys=[_CoprPublic.forked_from_id], 320 backref=db.backref("all_forks")) 321 322 @property
323 - def forks(self):
324 return [fork for fork in self.all_forks if not fork.deleted]
325 326 @property
327 - def main_dir(self):
328 """ 329 Return main copr dir for a Copr 330 """ 331 return CoprDir.query.filter(CoprDir.copr_id==self.id).filter(CoprDir.main==True).one()
332 333 @property
334 - def scm_api_auth(self):
335 if not self.scm_api_auth_json: 336 return {} 337 return json.loads(self.scm_api_auth_json)
338 339 @property
340 - def is_a_group_project(self):
341 """ 342 Return True if copr belongs to a group 343 """ 344 return self.group is not None
345 346 @property
347 - def owner(self):
348 """ 349 Return owner (user or group) of this copr 350 """ 351 return self.group if self.is_a_group_project else self.user
352 353 @property
354 - def owner_name(self):
355 """ 356 Return @group.name for a copr owned by a group and user.name otherwise 357 """ 358 return self.group.at_name if self.is_a_group_project else self.user.name
359 360 @property
361 - def repos_list(self):
362 """ 363 Return repos of this copr as a list of strings 364 """ 365 return self.repos.split()
366 367 @property
368 - def active_chroots(self):
369 """ 370 Return list of active mock_chroots of this copr 371 """ 372 return filter(lambda x: x.is_active, self.mock_chroots)
373 374 @property
375 - def active_copr_chroots(self):
376 """ 377 :rtype: list of CoprChroot 378 """ 379 return [c for c in self.copr_chroots if c.is_active]
380 381 @property
382 - def active_chroots_sorted(self):
383 """ 384 Return list of active mock_chroots of this copr 385 """ 386 return sorted(self.active_chroots, key=lambda ch: ch.name)
387 388 @property
389 - def outdated_chroots(self):
390 return sorted([chroot for chroot in self.copr_chroots if chroot.delete_after], 391 key=lambda ch: ch.name)
392 393 @property
394 - def active_chroots_grouped(self):
395 """ 396 Return list of active mock_chroots of this copr 397 """ 398 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted] 399 output = [] 400 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)): 401 output.append((os, [ch[1] for ch in chs])) 402 403 return output
404 405 @property
406 - def build_count(self):
407 """ 408 Return number of builds in this copr 409 """ 410 return len(self.builds)
411 412 @property
413 - def disable_createrepo(self):
414 return not self.auto_createrepo
415 416 @disable_createrepo.setter
417 - def disable_createrepo(self, value):
418 self.auto_createrepo = not bool(value)
419 420 @property
421 - def devel_mode(self):
422 return self.disable_createrepo
423 424 @property
425 - def modified_chroots(self):
426 """ 427 Return list of chroots which has been modified 428 """ 429 modified_chroots = [] 430 for chroot in self.copr_chroots: 431 if ((chroot.buildroot_pkgs or chroot.repos 432 or chroot.with_opts or chroot.without_opts) 433 and chroot.is_active): 434 modified_chroots.append(chroot) 435 return modified_chroots
436
437 - def is_release_arch_modified(self, name_release, arch):
438 if "{}-{}".format(name_release, arch) in \ 439 [chroot.name for chroot in self.modified_chroots]: 440 return True 441 return False
442 443 @property
444 - def full_name(self):
445 return "{}/{}".format(self.owner_name, self.name)
446 447 @property
448 - def repo_name(self):
449 return "{}-{}".format(self.owner_name, self.main_dir.name)
450 451 @property
452 - def repo_url(self):
453 return "/".join([app.config["BACKEND_BASE_URL"], 454 u"results", 455 self.main_dir.full_name])
456 457 @property
458 - def repo_id(self):
459 return "-".join([self.owner_name.replace("@", "group_"), self.name])
460 461 @property
462 - def modules_url(self):
463 return "/".join([self.repo_url, "modules"])
464
465 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
466 result = {} 467 for key in ["id", "name", "description", "instructions"]: 468 result[key] = str(copy.copy(getattr(self, key))) 469 result["owner"] = self.owner_name 470 return result
471 472 @property
473 - def still_forking(self):
474 return bool(Action.query.filter(Action.result == BackendResultEnum("waiting")) 475 .filter(Action.action_type == ActionTypeEnum("fork")) 476 .filter(Action.new_value == self.full_name).all())
477 480 481 @property
482 - def enable_net(self):
483 return self.build_enable_net
484 485 @enable_net.setter
486 - def enable_net(self, value):
487 self.build_enable_net = value
488
489 - def new_webhook_secret(self):
490 self.webhook_secret = str(uuid.uuid4())
491 492 @property
493 - def delete_after_days(self):
494 if self.delete_after is None: 495 return None 496 497 delta = self.delete_after - datetime.datetime.now() 498 return delta.days if delta.days > 0 else 0
499 500 @delete_after_days.setter
501 - def delete_after_days(self, days):
502 if days is None or days == -1: 503 self.delete_after = None 504 return 505 506 delete_after = datetime.datetime.now() + datetime.timedelta(days=days+1) 507 delete_after = delete_after.replace(hour=0, minute=0, second=0, microsecond=0) 508 self.delete_after = delete_after
509 510 @property
511 - def delete_after_msg(self):
512 if self.delete_after_days == 0: 513 return "will be deleted ASAP" 514 return "will be deleted after {} days".format(self.delete_after_days)
515 516 @property
517 - def admin_mails(self):
518 mails = [self.user.mail] 519 for perm in self.copr_permissions: 520 if perm.copr_admin == helpers.PermissionEnum('approved'): 521 mails.append(perm.user.mail) 522 return mails
523
524 -class CoprPermission(db.Model, helpers.Serializer):
525 """ 526 Association class for Copr<->Permission relation 527 """ 528 529 # see helpers.PermissionEnum for possible values of the fields below 530 # can this user build in the copr? 531 copr_builder = db.Column(db.SmallInteger, default=0) 532 # can this user serve as an admin? (-> edit and approve permissions) 533 copr_admin = db.Column(db.SmallInteger, default=0) 534 535 # relations 536 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True) 537 user = db.relationship("User", backref=db.backref("copr_permissions")) 538 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 539 copr = db.relationship("Copr", backref=db.backref("copr_permissions")) 540
541 - def set_permission(self, name, value):
542 if name == 'admin': 543 self.copr_admin = value 544 elif name == 'builder': 545 self.copr_builder = value 546 else: 547 raise KeyError("{0} is not a valid copr permission".format(name))
548
549 - def get_permission(self, name):
550 if name == 'admin': 551 return 0 if self.copr_admin is None else self.copr_admin 552 if name == 'builder': 553 return 0 if self.copr_builder is None else self.copr_builder 554 raise KeyError("{0} is not a valid copr permission".format(name))
555
556 557 -class CoprDir(db.Model):
558 """ 559 Represents one of data directories for a copr. 560 """ 561 id = db.Column(db.Integer, primary_key=True) 562 563 name = db.Column(db.Text, index=True) 564 main = db.Column(db.Boolean, index=True, default=False, server_default="0", nullable=False) 565 566 ownername = db.Column(db.Text, index=True, nullable=False) 567 568 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True, nullable=False) 569 copr = db.relationship("Copr", backref=db.backref("dirs")) 570 571 __table_args__ = ( 572 db.Index('only_one_main_copr_dir', copr_id, main, 573 unique=True, postgresql_where=(main==True)), 574 575 db.UniqueConstraint('ownername', 'name', 576 name='ownername_copr_dir_uniq'), 577 ) 578
579 - def __init__(self, *args, **kwargs):
580 if kwargs.get('copr') and not kwargs.get('ownername'): 581 kwargs['ownername'] = kwargs.get('copr').owner_name 582 super(CoprDir, self).__init__(*args, **kwargs)
583 584 @property
585 - def full_name(self):
586 return "{}/{}".format(self.copr.owner_name, self.name)
587 588 @property
589 - def repo_name(self):
590 return "{}-{}".format(self.copr.owner_name, self.name)
591 592 @property
593 - def repo_url(self):
594 return "/".join([app.config["BACKEND_BASE_URL"], 595 u"results", self.full_name])
596 597 @property
598 - def repo_id(self):
599 if self.copr.is_a_group_project: 600 return "group_{}-{}".format(self.copr.group.name, self.name) 601 else: 602 return "{}-{}".format(self.copr.user.name, self.name)
603
604 605 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
606 """ 607 Represents a single package in a project_dir. 608 """ 609 610 __table_args__ = ( 611 db.UniqueConstraint('copr_dir_id', 'name', name='packages_copr_dir_pkgname'), 612 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'), 613 ) 614
615 - def __init__(self, *args, **kwargs):
616 if kwargs.get('copr') and not kwargs.get('copr_dir'): 617 kwargs['copr_dir'] = kwargs.get('copr').main_dir 618 super(Package, self).__init__(*args, **kwargs)
619 620 id = db.Column(db.Integer, primary_key=True) 621 name = db.Column(db.String(100), nullable=False) 622 # Source of the build: type identifier 623 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset")) 624 # Source of the build: description in json, example: git link, srpm url, etc. 625 source_json = db.Column(db.Text) 626 # True if the package is built automatically via webhooks 627 webhook_rebuild = db.Column(db.Boolean, default=False) 628 # enable networking during a build process 629 enable_net = db.Column(db.Boolean, default=False, server_default="0", nullable=False) 630 631 # don't keep more builds of this package per copr-dir 632 max_builds = db.Column(db.Integer, index=True) 633 634 @validates('max_builds')
635 - def validate_max_builds(self, field, value):
636 return None if value == 0 else value
637 638 builds = db.relationship("Build", order_by="Build.id") 639 640 # relations 641 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True) 642 copr = db.relationship("Copr", backref=db.backref("packages")) 643 644 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True) 645 copr_dir = db.relationship("CoprDir", backref=db.backref("packages")) 646 647 # comma-separated list of wildcards of chroot names that this package should 648 # not be built against, e.g. "fedora-*, epel-*-i386" 649 chroot_blacklist_raw = db.Column(db.Text) 650 651 @property
652 - def dist_git_repo(self):
653 return "{}/{}".format(self.copr_dir.full_name, self.name)
654 655 @property
656 - def source_json_dict(self):
657 if not self.source_json: 658 return {} 659 return json.loads(self.source_json)
660 661 @property
662 - def source_type_text(self):
664 665 @property
666 - def has_source_type_set(self):
667 """ 668 Package's source type (and source_json) is being derived from its first build, which works except 669 for "link" and "upload" cases. Consider these being equivalent to source_type being unset. 670 """ 671 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
672 673 @property
674 - def dist_git_url(self):
675 if "DIST_GIT_URL" in app.config: 676 return "{}/{}.git".format(app.config["DIST_GIT_URL"], self.dist_git_repo) 677 return None
678 679 @property
680 - def dist_git_clone_url(self):
681 if "DIST_GIT_CLONE_URL" in app.config: 682 return "{}/{}.git".format(app.config["DIST_GIT_CLONE_URL"], self.dist_git_repo) 683 else: 684 return self.dist_git_url
685
686 - def last_build(self, successful=False):
687 for build in reversed(self.builds): 688 if not successful or build.state == "succeeded": 689 return build 690 return None
691
692 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
693 package_dict = super(Package, self).to_dict() 694 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type']) 695 696 if with_latest_build: 697 build = self.last_build(successful=False) 698 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None 699 if with_latest_succeeded_build: 700 build = self.last_build(successful=True) 701 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None 702 if with_all_builds: 703 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)] 704 705 return package_dict
706 709 710 711 @property
712 - def chroot_blacklist(self):
713 if not self.chroot_blacklist_raw: 714 return [] 715 716 blacklisted = [] 717 for pattern in self.chroot_blacklist_raw.split(','): 718 pattern = pattern.strip() 719 if not pattern: 720 continue 721 blacklisted.append(pattern) 722 723 return blacklisted
724 725 726 @staticmethod
727 - def matched_chroot(chroot, patterns):
728 for pattern in patterns: 729 if fnmatch(chroot.name, pattern): 730 return True 731 return False
732 733 734 @property
735 - def main_pkg(self):
736 if self.copr_dir.main: 737 return self 738 739 main_pkg = Package.query.filter_by( 740 name=self.name, 741 copr_dir_id=self.copr.main_dir.id 742 ).first() 743 return main_pkg
744 745 746 @property
747 - def chroots(self):
748 chroots = list(self.copr.active_chroots) 749 if not self.chroot_blacklist_raw: 750 # no specific blacklist 751 if self.copr_dir.main: 752 return chroots 753 return self.main_pkg.chroots 754 755 filtered = [c for c in chroots if not self.matched_chroot(c, self.chroot_blacklist)] 756 # We never want to filter everything, this is a misconfiguration. 757 return filtered if filtered else chroots
758
759 760 -class Build(db.Model, helpers.Serializer):
761 """ 762 Representation of one build in one copr 763 """ 764 765 SCM_COMMIT = 'commit' 766 SCM_PULL_REQUEST = 'pull-request' 767 768 __table_args__ = (db.Index('build_canceled', "canceled"), 769 db.Index('build_order', "is_background", "id"), 770 db.Index('build_filter', "source_type", "canceled"), 771 db.Index('build_canceled_is_background_source_status_id_idx', 'canceled', "is_background", "source_status", "id"), 772 ) 773
774 - def __init__(self, *args, **kwargs):
775 if kwargs.get('source_type') == helpers.BuildSourceEnum("custom"): 776 source_dict = json.loads(kwargs['source_json']) 777 if 'fedora-latest' in source_dict['chroot']: 778 arch = source_dict['chroot'].rsplit('-', 2)[2] 779 source_dict['chroot'] = \ 780 MockChroot.latest_fedora_branched_chroot(arch=arch).name 781 kwargs['source_json'] = json.dumps(source_dict) 782 783 if kwargs.get('copr') and not kwargs.get('copr_dir'): 784 kwargs['copr_dir'] = kwargs.get('copr').main_dir 785 786 super(Build, self).__init__(*args, **kwargs)
787 788 id = db.Column(db.Integer, primary_key=True) 789 # single url to the source rpm, should not contain " ", "\n", "\t" 790 pkgs = db.Column(db.Text) 791 # built packages 792 built_packages = db.Column(db.Text) 793 # version of the srpm package got by rpm 794 pkg_version = db.Column(db.Text) 795 # was this build canceled by user? 796 canceled = db.Column(db.Boolean, default=False) 797 # list of space separated additional repos 798 repos = db.Column(db.Text) 799 # the three below represent time of important events for this build 800 # as returned by int(time.time()) 801 submitted_on = db.Column(db.Integer, nullable=False) 802 # directory name on backend with the srpm build results 803 result_dir = db.Column(db.Text, default='', server_default='', nullable=False) 804 # memory requirements for backend builder 805 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY) 806 # maximum allowed time of build, build will fail if exceeded 807 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT) 808 # enable networking during a build process 809 enable_net = db.Column(db.Boolean, default=False, 810 server_default="0", nullable=False) 811 # Source of the build: type identifier 812 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset")) 813 # Source of the build: description in json, example: git link, srpm url, etc. 814 source_json = db.Column(db.Text) 815 # Type of failure: type identifier 816 fail_type = db.Column(db.Integer, default=FailTypeEnum("unset")) 817 # background builds has lesser priority than regular builds. 818 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False) 819 820 source_status = db.Column(db.Integer, default=StatusEnum("waiting")) 821 srpm_url = db.Column(db.Text) 822 823 # relations 824 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True) 825 user = db.relationship("User", backref=db.backref("builds")) 826 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True) 827 copr = db.relationship("Copr", backref=db.backref("builds")) 828 package_id = db.Column(db.Integer, db.ForeignKey("package.id"), index=True) 829 package = db.relationship("Package") 830 831 chroots = association_proxy("build_chroots", "mock_chroot") 832 833 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id")) 834 batch = db.relationship("Batch", backref=db.backref("builds")) 835 836 module_id = db.Column(db.Integer, db.ForeignKey("module.id"), index=True) 837 module = db.relationship("Module", backref=db.backref("builds")) 838 839 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True) 840 copr_dir = db.relationship("CoprDir", backref=db.backref("builds")) 841 842 # scm integration properties 843 scm_object_id = db.Column(db.Text) 844 scm_object_type = db.Column(db.Text) 845 scm_object_url = db.Column(db.Text) 846 847 # method to call on build state change 848 update_callback = db.Column(db.Text) 849 850 # used by webhook builds; e.g. github.com:praiskup, or pagure.io:jdoe 851 submitted_by = db.Column(db.Text) 852 853 @property
854 - def user_name(self):
855 return self.user.name
856 857 @property
858 - def group_name(self):
859 return self.copr.group.name
860 861 @property
862 - def copr_name(self):
863 return self.copr.name
864 865 @property
866 - def copr_dirname(self):
867 return self.copr_dir.name
868 869 @property
870 - def copr_full_dirname(self):
871 return self.copr_dir.full_name
872 873 @property
874 - def fail_type_text(self):
875 return FailTypeEnum(self.fail_type)
876 877 @property
878 - def repos_list(self):
879 if self.repos is None: 880 return list() 881 else: 882 return self.repos.split()
883 884 @property
885 - def task_id(self):
886 return str(self.id)
887 888 @property
889 - def id_fixed_width(self):
890 return "{:08d}".format(self.id)
891
892 - def get_import_log_urls(self, admin=False):
893 logs = [self.import_log_url_backend] 894 if admin: 895 logs.append(self.import_log_url_distgit) 896 return list(filter(None, logs))
897 898 @property
899 - def import_log_url_distgit(self):
900 if app.config["COPR_DIST_GIT_LOGS_URL"]: 901 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"], 902 self.task_id.replace('/', '_')) 903 return None
904 905 @property
906 - def import_log_url_backend(self):
907 parts = ["results", self.copr.owner_name, self.copr_dirname, 908 "srpm-builds", self.id_fixed_width, 909 "builder-live.log" if self.source_status == StatusEnum("running") 910 else "builder-live.log.gz"] 911 path = os.path.normpath(os.path.join(*parts)) 912 return urljoin(app.config["BACKEND_BASE_URL"], path)
913 914 @property
915 - def source_json_dict(self):
916 if not self.source_json: 917 return {} 918 return json.loads(self.source_json)
919 920 @property
921 - def started_on(self):
922 return self.min_started_on
923 924 @property
925 - def min_started_on(self):
926 mb_list = [chroot.started_on for chroot in 927 self.build_chroots if chroot.started_on] 928 if len(mb_list) > 0: 929 return min(mb_list) 930 else: 931 return None
932 933 @property
934 - def ended_on(self):
935 return self.max_ended_on
936 937 @property
938 - def max_ended_on(self):
939 if not self.build_chroots: 940 return None 941 if any(chroot.ended_on is None for chroot in self.build_chroots): 942 return None 943 return max(chroot.ended_on for chroot in self.build_chroots)
944 945 @property
946 - def chroots_started_on(self):
947 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
948 949 @property
950 - def chroots_ended_on(self):
951 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
952 953 @property
954 - def source_type_text(self):
956 957 @property
958 - def source_metadata(self):
959 if self.source_json is None: 960 return None 961 962 try: 963 return json.loads(self.source_json) 964 except (TypeError, ValueError): 965 return None
966 967 @property
968 - def chroot_states(self):
969 return list(map(lambda chroot: chroot.status, self.build_chroots))
970
971 - def get_chroots_by_status(self, statuses=None):
972 """ 973 Get build chroots with states which present in `states` list 974 If states == None, function returns build_chroots 975 """ 976 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states)) 977 if statuses is not None: 978 statuses = set(statuses) 979 else: 980 return self.build_chroots 981 982 return [ 983 chroot for chroot, status in chroot_states_map.items() 984 if status in statuses 985 ]
986 987 @property
988 - def chroots_dict_by_name(self):
989 return {b.name: b for b in self.build_chroots}
990 991 @property
992 - def status(self):
993 """ 994 Return build status. 995 """ 996 if self.canceled: 997 return StatusEnum("canceled") 998 999 use_src_statuses = ["starting", "pending", "running", "importing", "failed"] 1000 if self.source_status in [StatusEnum(s) for s in use_src_statuses]: 1001 return self.source_status 1002 1003 if not self.chroot_states: 1004 # There were some builds in DB which had source_status equal 1005 # to 'succeeded', while they had no biuld_chroots created. 1006 # The original source of this inconsistency isn't known 1007 # because we only ever flip source_status to "succeded" directly 1008 # from the "importing" state. 1009 # Anyways, return something meaningful here so we can debug 1010 # properly if such situation happens. 1011 app.logger.error("Build %s has source_status succeeded, but " 1012 "no build_chroots", self.id) 1013 return StatusEnum("waiting") 1014 1015 for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked"]: 1016 if StatusEnum(state) in self.chroot_states: 1017 return StatusEnum(state) 1018 1019 if StatusEnum("waiting") in self.chroot_states: 1020 # We should atomically flip 1021 # a) build.source_status: "importing" -> "succeeded" and 1022 # b) biuld_chroot.status: "waiting" -> "pending" 1023 # so at this point nothing really should be in "waiting" state. 1024 app.logger.error("Build chroots pending, even though build %s" 1025 " has succeeded source_status", self.id) 1026 return StatusEnum("pending") 1027 1028 return None
1029 1030 @property
1031 - def state(self):
1032 """ 1033 Return text representation of status of this build. 1034 """ 1035 if self.status != None: 1036 return StatusEnum(self.status) 1037 return "unknown"
1038 1039 @property
1040 - def cancelable(self):
1041 """ 1042 Find out if this build is cancelable. 1043 """ 1044 return not self.finished and self.status != StatusEnum("starting")
1045 1046 @property
1047 - def repeatable(self):
1048 """ 1049 Find out if this build is repeatable. 1050 1051 Build is repeatable only if sources has been imported. 1052 """ 1053 return self.source_status == StatusEnum("succeeded")
1054 1055 @property
1056 - def finished(self):
1057 """ 1058 Find out if this build is in finished state. 1059 1060 Build is finished only if all its build_chroots are in finished state or 1061 the build was canceled. 1062 """ 1063 if self.canceled: 1064 return True 1065 if not self.build_chroots: 1066 return StatusEnum(self.source_status) in helpers.FINISHED_STATUSES 1067 return all([chroot.finished for chroot in self.build_chroots])
1068 1069 @property
1070 - def blocked(self):
1071 return bool(self.batch and self.batch.blocked_by and not self.batch.blocked_by.finished)
1072 1073 @property
1074 - def persistent(self):
1075 """ 1076 Find out if this build is persistent. 1077 1078 This property is inherited from the project. 1079 """ 1080 return self.copr.persistent
1081 1082 @property
1083 - def package_name(self):
1084 try: 1085 return self.package.name 1086 except: 1087 return None
1088
1089 - def to_dict(self, options=None, with_chroot_states=False):
1090 result = super(Build, self).to_dict(options) 1091 result["src_pkg"] = result["pkgs"] 1092 del result["pkgs"] 1093 del result["copr_id"] 1094 1095 result['source_type'] = helpers.BuildSourceEnum(result['source_type']) 1096 result["state"] = self.state 1097 1098 if with_chroot_states: 1099 result["chroots"] = {b.name: b.state for b in self.build_chroots} 1100 1101 return result
1102 1103 @property
1104 - def submitter(self):
1105 """ 1106 Return tuple (submitter_string, submitter_link), while the 1107 submitter_link may be empty if we are not able to detect it 1108 wisely. 1109 """ 1110 if self.user: 1111 user = self.user.name 1112 return (user, url_for('coprs_ns.coprs_by_user', username=user)) 1113 1114 if self.submitted_by: 1115 links = ['http://', 'https://'] 1116 if any([self.submitted_by.startswith(x) for x in links]): 1117 return (self.submitted_by, self.submitted_by) 1118 1119 return (self.submitted_by, None) 1120 1121 return (None, None)
1122 1123 @property
1124 - def sandbox(self):
1125 """ 1126 Return a string unique to project + submitter. At this level copr 1127 backend later applies builder user-VM separation policy (VMs are only 1128 re-used for builds which have the same build.sandbox value) 1129 """ 1130 submitter, _ = self.submitter 1131 if not submitter: 1132 # If we don't know build submitter, use "random" value and keep the 1133 # build separated from any other. 1134 submitter = uuid.uuid4() 1135 1136 return '{0}--{1}'.format(self.copr.full_name, submitter)
1137
1138 1139 -class DistGitBranch(db.Model, helpers.Serializer):
1140 """ 1141 1:N mapping: branch -> chroots 1142 """ 1143 1144 # Name of the branch used on dist-git machine. 1145 name = db.Column(db.String(50), primary_key=True)
1146
1147 1148 -class MockChroot(db.Model, helpers.Serializer):
1149 """ 1150 Representation of mock chroot 1151 """ 1152 1153 __table_args__ = ( 1154 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'), 1155 ) 1156 1157 id = db.Column(db.Integer, primary_key=True) 1158 # fedora/epel/..., mandatory 1159 os_release = db.Column(db.String(50), nullable=False) 1160 # 18/rawhide/..., optional (mock chroot doesn"t need to have this) 1161 os_version = db.Column(db.String(50), nullable=False) 1162 # x86_64/i686/..., mandatory 1163 arch = db.Column(db.String(50), nullable=False) 1164 is_active = db.Column(db.Boolean, default=True) 1165 1166 # Reference branch name 1167 distgit_branch_name = db.Column(db.String(50), 1168 db.ForeignKey("dist_git_branch.name"), 1169 nullable=False) 1170 1171 distgit_branch = db.relationship("DistGitBranch", 1172 backref=db.backref("chroots")) 1173 1174 # After a mock_chroot is EOLed, this is set to true so that copr_prune_results 1175 # will skip all projects using this chroot 1176 final_prunerepo_done = db.Column(db.Boolean, default=False, server_default="0", nullable=False) 1177 1178 @classmethod
1179 - def latest_fedora_branched_chroot(cls, arch='x86_64'):
1180 return (cls.query 1181 .filter(cls.is_active == True) 1182 .filter(cls.os_release == 'fedora') 1183 .filter(cls.os_version != 'rawhide') 1184 .filter(cls.arch == arch) 1185 .order_by(cls.os_version.desc()) 1186 .first())
1187 1188 @property
1189 - def name(self):
1190 """ 1191 Textual representation of name of this chroot 1192 """ 1193 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
1194 1195 @property
1196 - def name_release(self):
1197 """ 1198 Textual representation of name of this or release 1199 """ 1200 return "{}-{}".format(self.os_release, self.os_version)
1201 1202 @property
1203 - def os(self):
1204 """ 1205 Textual representation of the operating system name 1206 """ 1207 return "{0} {1}".format(self.os_release, self.os_version)
1208 1209 @property
1210 - def serializable_attributes(self):
1211 attr_list = super(MockChroot, self).serializable_attributes 1212 attr_list.extend(["name", "os"]) 1213 return attr_list
1214
1215 1216 -class CoprChroot(db.Model, helpers.Serializer):
1217 """ 1218 Representation of Copr<->MockChroot relation 1219 """ 1220 1221 buildroot_pkgs = db.Column(db.Text) 1222 repos = db.Column(db.Text, default="", server_default="", nullable=False) 1223 mock_chroot_id = db.Column( 1224 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True) 1225 mock_chroot = db.relationship( 1226 "MockChroot", backref=db.backref("copr_chroots")) 1227 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 1228 copr = db.relationship("Copr", 1229 backref=db.backref( 1230 "copr_chroots", 1231 single_parent=True, 1232 cascade="all,delete,delete-orphan")) 1233 1234 comps_zlib = db.Column(db.LargeBinary(), nullable=True) 1235 comps_name = db.Column(db.String(127), nullable=True) 1236 1237 with_opts = db.Column(db.Text, default="", server_default="", nullable=False) 1238 without_opts = db.Column(db.Text, default="", server_default="", nullable=False) 1239 1240 # Once mock_chroot gets EOL, copr_chroots are going to be deleted 1241 # if their owner doesn't extend their time span 1242 delete_after = db.Column(db.DateTime, index=True) 1243 delete_notify = db.Column(db.DateTime, index=True) 1244
1245 - def update_comps(self, comps_xml):
1246 if isinstance(comps_xml, str): 1247 data = comps_xml.encode("utf-8") 1248 else: 1249 data = comps_xml 1250 self.comps_zlib = zlib.compress(data)
1251 1252 @property
1253 - def buildroot_pkgs_list(self):
1254 return (self.buildroot_pkgs or "").split()
1255 1256 @property
1257 - def repos_list(self):
1258 return (self.repos or "").split()
1259 1260 @property
1261 - def comps(self):
1262 if self.comps_zlib: 1263 return zlib.decompress(self.comps_zlib).decode("utf-8")
1264 1265 @property
1266 - def comps_len(self):
1267 if self.comps_zlib: 1268 return len(zlib.decompress(self.comps_zlib)) 1269 else: 1270 return 0
1271 1272 @property
1273 - def name(self):
1274 return self.mock_chroot.name
1275 1276 @property
1277 - def is_active(self):
1278 return self.mock_chroot.is_active
1279 1280 @property
1281 - def delete_after_days(self):
1282 if not self.delete_after: 1283 return None 1284 now = datetime.datetime.now() 1285 days = (self.delete_after - now).days 1286 return days if days > 0 else 0
1287
1288 - def to_dict(self):
1289 options = {"__columns_only__": [ 1290 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts" 1291 ]} 1292 d = super(CoprChroot, self).to_dict(options=options) 1293 d["mock_chroot"] = self.mock_chroot.name 1294 return d
1295
1296 1297 -class BuildChroot(db.Model, helpers.Serializer):
1298 """ 1299 Representation of Build<->MockChroot relation 1300 """ 1301 1302 __table_args__ = (db.Index('build_chroot_status_started_on_idx', "status", "started_on"),) 1303 1304 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"), 1305 primary_key=True) 1306 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds")) 1307 build_id = db.Column(db.Integer, db.ForeignKey("build.id", ondelete="CASCADE"), 1308 primary_key=True) 1309 build = db.relationship("Build", backref=db.backref("build_chroots", cascade="all, delete-orphan", 1310 passive_deletes=True)) 1311 git_hash = db.Column(db.String(40)) 1312 status = db.Column(db.Integer, default=StatusEnum("waiting")) 1313 1314 started_on = db.Column(db.Integer, index=True) 1315 ended_on = db.Column(db.Integer, index=True) 1316 1317 # directory name on backend with build results 1318 result_dir = db.Column(db.Text, default='', server_default='', nullable=False) 1319 1320 build_requires = db.Column(db.Text) 1321 1322 @property
1323 - def name(self):
1324 """ 1325 Textual representation of name of this chroot 1326 """ 1327 return self.mock_chroot.name
1328 1329 @property
1330 - def state(self):
1331 """ 1332 Return text representation of status of this build chroot 1333 """ 1334 if self.status is not None: 1335 return StatusEnum(self.status) 1336 return "unknown"
1337 1338 @property
1339 - def finished(self):
1340 return self.state in helpers.FINISHED_STATUSES
1341 1342 @property
1343 - def task_id(self):
1344 return "{}-{}".format(self.build_id, self.name)
1345 1346 @property
1347 - def dist_git_url(self):
1348 if app.config["DIST_GIT_URL"]: 1349 if self.state == "forked": 1350 if self.build.copr.forked_from.deleted: 1351 return None 1352 copr_dirname = self.build.copr.forked_from.main_dir.full_name 1353 else: 1354 copr_dirname = self.build.copr_dir.full_name 1355 return "{}/{}/{}.git/commit/?id={}".format(app.config["DIST_GIT_URL"], 1356 copr_dirname, 1357 self.build.package.name, 1358 self.git_hash) 1359 return None
1360 1361 @property
1362 - def result_dir_url(self):
1363 if not self.result_dir: 1364 return None 1365 return urljoin(app.config["BACKEND_BASE_URL"], os.path.join( 1366 "results", self.build.copr_dir.full_name, self.name, self.result_dir, ""))
1367
1368 1369 -class LegalFlag(db.Model, helpers.Serializer):
1370 id = db.Column(db.Integer, primary_key=True) 1371 # message from user who raised the flag (what he thinks is wrong) 1372 raise_message = db.Column(db.Text) 1373 # time of raising the flag as returned by int(time.time()) 1374 raised_on = db.Column(db.Integer) 1375 # time of resolving the flag by admin as returned by int(time.time()) 1376 resolved_on = db.Column(db.Integer) 1377 1378 # relations 1379 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True) 1380 # cascade="all" means that we want to keep these even if copr is deleted 1381 copr = db.relationship( 1382 "Copr", backref=db.backref("legal_flags", cascade="all")) 1383 # user who reported the problem 1384 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id")) 1385 reporter = db.relationship("User", 1386 backref=db.backref("legal_flags_raised"), 1387 foreign_keys=[reporter_id], 1388 primaryjoin="LegalFlag.reporter_id==User.id") 1389 # admin who resolved the problem 1390 resolver_id = db.Column( 1391 db.Integer, db.ForeignKey("user.id"), nullable=True) 1392 resolver = db.relationship("User", 1393 backref=db.backref("legal_flags_resolved"), 1394 foreign_keys=[resolver_id], 1395 primaryjoin="LegalFlag.resolver_id==User.id")
1396
1397 1398 -class Action(db.Model, helpers.Serializer):
1399 """ 1400 Representation of a custom action that needs 1401 backends cooperation/admin attention/... 1402 """ 1403 1404 id = db.Column(db.Integer, primary_key=True) 1405 # see ActionTypeEnum 1406 action_type = db.Column(db.Integer, nullable=False) 1407 # copr, ...; downcase name of class of modified object 1408 object_type = db.Column(db.String(20)) 1409 # id of the modified object 1410 object_id = db.Column(db.Integer) 1411 # old and new values of the changed property 1412 old_value = db.Column(db.String(255)) 1413 new_value = db.Column(db.String(255)) 1414 # additional data 1415 data = db.Column(db.Text) 1416 # result of the action, see BackendResultEnum 1417 result = db.Column( 1418 db.Integer, default=BackendResultEnum("waiting")) 1419 # optional message from the backend/whatever 1420 message = db.Column(db.Text) 1421 # time created as returned by int(time.time()) 1422 created_on = db.Column(db.Integer) 1423 # time ended as returned by int(time.time()) 1424 ended_on = db.Column(db.Integer) 1425
1426 - def __str__(self):
1427 return self.__unicode__()
1428
1429 - def __unicode__(self):
1430 if self.action_type == ActionTypeEnum("delete"): 1431 return "Deleting {0} {1}".format(self.object_type, self.old_value) 1432 elif self.action_type == ActionTypeEnum("legal-flag"): 1433 return "Legal flag on copr {0}.".format(self.old_value) 1434 1435 return "Action {0} on {1}, old value: {2}, new value: {3}.".format( 1436 self.action_type, self.object_type, self.old_value, self.new_value)
1437
1438 - def to_dict(self, **kwargs):
1439 d = super(Action, self).to_dict() 1440 if d.get("object_type") == "module": 1441 module = Module.query.filter(Module.id == d["object_id"]).first() 1442 data = json.loads(d["data"]) 1443 data.update({ 1444 "projectname": module.copr.name, 1445 "ownername": module.copr.owner_name, 1446 "modulemd_b64": module.yaml_b64, 1447 }) 1448 d["data"] = json.dumps(data) 1449 return d
1450
1451 1452 -class Krb5Login(db.Model, helpers.Serializer):
1453 """ 1454 Represents additional user information for kerberos authentication. 1455 """ 1456 1457 __tablename__ = "krb5_login" 1458 1459 # FK to User table 1460 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) 1461 1462 # 'string' from 'copr.conf' from KRB5_LOGIN[string] 1463 config_name = db.Column(db.String(30), nullable=False, primary_key=True) 1464 1465 # krb's primary, i.e. 'username' from 'username@EXAMPLE.COM' 1466 primary = db.Column(db.String(80), nullable=False, primary_key=True) 1467 1468 user = db.relationship("User", backref=db.backref("krb5_logins"))
1469
1470 1471 -class CounterStat(db.Model, helpers.Serializer):
1472 """ 1473 Generic store for simple statistics. 1474 """ 1475 1476 name = db.Column(db.String(127), primary_key=True) 1477 counter_type = db.Column(db.String(30)) 1478 1479 counter = db.Column(db.Integer, default=0, server_default="0")
1480
1481 1482 -class Group(db.Model, helpers.Serializer):
1483 1484 """ 1485 Represents FAS groups and their aliases in Copr 1486 """ 1487 1488 id = db.Column(db.Integer, primary_key=True) 1489 name = db.Column(db.String(127)) 1490 1491 # TODO: add unique=True 1492 fas_name = db.Column(db.String(127)) 1493 1494 @property
1495 - def at_name(self):
1496 return u"@{}".format(self.name)
1497
1498 - def __str__(self):
1499 return self.__unicode__()
1500
1501 - def __unicode__(self):
1502 return "{} (fas: {})".format(self.name, self.fas_name)
1503
1504 1505 -class Batch(db.Model):
1506 id = db.Column(db.Integer, primary_key=True) 1507 blocked_by_id = db.Column(db.Integer, db.ForeignKey("batch.id"), nullable=True) 1508 blocked_by = db.relationship("Batch", remote_side=[id]) 1509 1510 @property
1511 - def finished(self):
1512 return all([b.finished for b in self.builds])
1513
1514 1515 -class Module(db.Model, helpers.Serializer):
1516 id = db.Column(db.Integer, primary_key=True) 1517 name = db.Column(db.String(100), nullable=False) 1518 stream = db.Column(db.String(100), nullable=False) 1519 version = db.Column(db.BigInteger, nullable=False) 1520 summary = db.Column(db.String(100), nullable=False) 1521 description = db.Column(db.Text) 1522 created_on = db.Column(db.Integer, nullable=True) 1523 1524 # When someone submits YAML (not generate one on the copr modules page), we might want to use that exact file. 1525 # Yaml produced by deconstructing into pieces and constructed back can look differently, 1526 # which is not desirable (Imo) 1527 # 1528 # Also if there are fields which are not covered by this model, we will be able to add them in the future 1529 # and fill them with data from this blob 1530 yaml_b64 = db.Column(db.Text) 1531 1532 # relations 1533 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 1534 copr = db.relationship("Copr", backref=db.backref("modules")) 1535 1536 __table_args__ = ( 1537 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"), 1538 ) 1539 1540 @property
1541 - def yaml(self):
1542 return base64.b64decode(self.yaml_b64)
1543 1544 @property
1545 - def modulemd(self):
1546 mmd = Modulemd.ModuleStream() 1547 mmd.import_from_string(self.yaml.decode("utf-8")) 1548 return mmd
1549 1550 @property
1551 - def nsv(self):
1552 return "-".join([self.name, self.stream, str(self.version)])
1553 1554 @property
1555 - def full_name(self):
1556 return "{}/{}".format(self.copr.full_name, self.nsv)
1557 1558 @property
1559 - def action(self):
1560 return Action.query.filter(Action.object_type == "module").filter(Action.object_id == self.id).first()
1561 1562 @property
1563 - def status(self):
1564 """ 1565 Return numeric representation of status of this build 1566 """ 1567 if self.action: 1568 return { BackendResultEnum("success"): ModuleStatusEnum("succeeded"), 1569 BackendResultEnum("failure"): ModuleStatusEnum("failed"), 1570 BackendResultEnum("waiting"): ModuleStatusEnum("waiting"), 1571 }[self.action.result] 1572 build_statuses = [b.status for b in self.builds] 1573 for state in ["canceled", "running", "starting", "pending", "failed", "succeeded"]: 1574 if ModuleStatusEnum(state) in build_statuses: 1575 return ModuleStatusEnum(state)
1576 1577 @property
1578 - def state(self):
1579 """ 1580 Return text representation of status of this build 1581 """ 1582 return ModuleStatusEnum(self.status)
1583 1584 @property
1585 - def rpm_filter(self):
1586 return self.modulemd.get_rpm_filter().get()
1587 1588 @property
1589 - def rpm_api(self):
1590 return self.modulemd.get_rpm_api().get()
1591 1592 @property
1593 - def profiles(self):
1594 return {k: v.get_rpms().get() for k, v in self.modulemd.get_profiles().items()}
1595
1596 1597 -class BuildsStatistics(db.Model):
1598 time = db.Column(db.Integer, primary_key=True) 1599 stat_type = db.Column(db.Text, primary_key=True) 1600 running = db.Column(db.Integer) 1601 pending = db.Column(db.Integer)
1602