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 copr.user.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, 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, "builder-live.log"] 909 path = os.path.normpath(os.path.join(*parts)) 910 return urljoin(app.config["BACKEND_BASE_URL"], path)
911 912 @property
913 - def source_json_dict(self):
914 if not self.source_json: 915 return {} 916 return json.loads(self.source_json)
917 918 @property
919 - def started_on(self):
920 return self.min_started_on
921 922 @property
923 - def min_started_on(self):
924 mb_list = [chroot.started_on for chroot in 925 self.build_chroots if chroot.started_on] 926 if len(mb_list) > 0: 927 return min(mb_list) 928 else: 929 return None
930 931 @property
932 - def ended_on(self):
933 return self.max_ended_on
934 935 @property
936 - def max_ended_on(self):
937 if not self.build_chroots: 938 return None 939 if any(chroot.ended_on is None for chroot in self.build_chroots): 940 return None 941 return max(chroot.ended_on for chroot in self.build_chroots)
942 943 @property
944 - def chroots_started_on(self):
945 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
946 947 @property
948 - def chroots_ended_on(self):
949 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
950 951 @property
952 - def source_type_text(self):
954 955 @property
956 - def source_metadata(self):
957 if self.source_json is None: 958 return None 959 960 try: 961 return json.loads(self.source_json) 962 except (TypeError, ValueError): 963 return None
964 965 @property
966 - def chroot_states(self):
967 return list(map(lambda chroot: chroot.status, self.build_chroots))
968
969 - def get_chroots_by_status(self, statuses=None):
970 """ 971 Get build chroots with states which present in `states` list 972 If states == None, function returns build_chroots 973 """ 974 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states)) 975 if statuses is not None: 976 statuses = set(statuses) 977 else: 978 return self.build_chroots 979 980 return [ 981 chroot for chroot, status in chroot_states_map.items() 982 if status in statuses 983 ]
984 985 @property
986 - def chroots_dict_by_name(self):
987 return {b.name: b for b in self.build_chroots}
988 989 @property
990 - def status(self):
991 """ 992 Return build status. 993 """ 994 if self.canceled: 995 return StatusEnum("canceled") 996 997 use_src_statuses = ["starting", "pending", "running", "importing", "failed"] 998 if self.source_status in [StatusEnum(s) for s in use_src_statuses]: 999 return self.source_status 1000 1001 if not self.chroot_states: 1002 # There were some builds in DB which had source_status equal 1003 # to 'succeeded', while they had no biuld_chroots created. 1004 # The original source of this inconsistency isn't known 1005 # because we only ever flip source_status to "succeded" directly 1006 # from the "importing" state. 1007 # Anyways, return something meaningful here so we can debug 1008 # properly if such situation happens. 1009 app.logger.error("Build %s has source_status succeeded, but " 1010 "no build_chroots", self.id) 1011 return StatusEnum("waiting") 1012 1013 for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked"]: 1014 if StatusEnum(state) in self.chroot_states: 1015 return StatusEnum(state) 1016 1017 if StatusEnum("waiting") in self.chroot_states: 1018 # We should atomically flip 1019 # a) build.source_status: "importing" -> "succeeded" and 1020 # b) biuld_chroot.status: "waiting" -> "pending" 1021 # so at this point nothing really should be in "waiting" state. 1022 app.logger.error("Build chroots pending, even though build %s" 1023 " has succeeded source_status", self.id) 1024 return StatusEnum("pending") 1025 1026 return None
1027 1028 @property
1029 - def state(self):
1030 """ 1031 Return text representation of status of this build. 1032 """ 1033 if self.status != None: 1034 return StatusEnum(self.status) 1035 return "unknown"
1036 1037 @property
1038 - def cancelable(self):
1039 """ 1040 Find out if this build is cancelable. 1041 """ 1042 return not self.finished and self.status != StatusEnum("starting")
1043 1044 @property
1045 - def repeatable(self):
1046 """ 1047 Find out if this build is repeatable. 1048 1049 Build is repeatable only if sources has been imported. 1050 """ 1051 return self.source_status == StatusEnum("succeeded")
1052 1053 @property
1054 - def finished(self):
1055 """ 1056 Find out if this build is in finished state. 1057 1058 Build is finished only if all its build_chroots are in finished state or 1059 the build was canceled. 1060 """ 1061 if self.canceled: 1062 return True 1063 if not self.build_chroots: 1064 return StatusEnum(self.source_status) in helpers.FINISHED_STATUSES 1065 return all([chroot.finished for chroot in self.build_chroots])
1066 1067 @property
1068 - def blocked(self):
1069 return bool(self.batch and self.batch.blocked_by and not self.batch.blocked_by.finished)
1070 1071 @property
1072 - def persistent(self):
1073 """ 1074 Find out if this build is persistent. 1075 1076 This property is inherited from the project. 1077 """ 1078 return self.copr.persistent
1079 1080 @property
1081 - def package_name(self):
1082 try: 1083 return self.package.name 1084 except: 1085 return None
1086
1087 - def to_dict(self, options=None, with_chroot_states=False):
1088 result = super(Build, self).to_dict(options) 1089 result["src_pkg"] = result["pkgs"] 1090 del result["pkgs"] 1091 del result["copr_id"] 1092 1093 result['source_type'] = helpers.BuildSourceEnum(result['source_type']) 1094 result["state"] = self.state 1095 1096 if with_chroot_states: 1097 result["chroots"] = {b.name: b.state for b in self.build_chroots} 1098 1099 return result
1100 1101 @property
1102 - def submitter(self):
1103 """ 1104 Return tuple (submitter_string, submitter_link), while the 1105 submitter_link may be empty if we are not able to detect it 1106 wisely. 1107 """ 1108 if self.user: 1109 user = self.user.name 1110 return (user, url_for('coprs_ns.coprs_by_user', username=user)) 1111 1112 if self.submitted_by: 1113 links = ['http://', 'https://'] 1114 if any([self.submitted_by.startswith(x) for x in links]): 1115 return (self.submitted_by, self.submitted_by) 1116 1117 return (self.submitted_by, None) 1118 1119 return (None, None)
1120 1121 @property
1122 - def sandbox(self):
1123 """ 1124 Return a string unique to project + submitter. At this level copr 1125 backend later applies builder user-VM separation policy (VMs are only 1126 re-used for builds which have the same build.sandbox value) 1127 """ 1128 submitter, _ = self.submitter 1129 if not submitter: 1130 # If we don't know build submitter, use "random" value and keep the 1131 # build separated from any other. 1132 submitter = uuid.uuid4() 1133 1134 return '{0}--{1}'.format(self.copr.full_name, submitter)
1135
1136 1137 -class DistGitBranch(db.Model, helpers.Serializer):
1138 """ 1139 1:N mapping: branch -> chroots 1140 """ 1141 1142 # Name of the branch used on dist-git machine. 1143 name = db.Column(db.String(50), primary_key=True)
1144
1145 1146 -class MockChroot(db.Model, helpers.Serializer):
1147 """ 1148 Representation of mock chroot 1149 """ 1150 1151 __table_args__ = ( 1152 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'), 1153 ) 1154 1155 id = db.Column(db.Integer, primary_key=True) 1156 # fedora/epel/..., mandatory 1157 os_release = db.Column(db.String(50), nullable=False) 1158 # 18/rawhide/..., optional (mock chroot doesn"t need to have this) 1159 os_version = db.Column(db.String(50), nullable=False) 1160 # x86_64/i686/..., mandatory 1161 arch = db.Column(db.String(50), nullable=False) 1162 is_active = db.Column(db.Boolean, default=True) 1163 1164 # Reference branch name 1165 distgit_branch_name = db.Column(db.String(50), 1166 db.ForeignKey("dist_git_branch.name"), 1167 nullable=False) 1168 1169 distgit_branch = db.relationship("DistGitBranch", 1170 backref=db.backref("chroots")) 1171 1172 # After a mock_chroot is EOLed, this is set to true so that copr_prune_results 1173 # will skip all projects using this chroot 1174 final_prunerepo_done = db.Column(db.Boolean, default=False, server_default="0", nullable=False) 1175 1176 @classmethod
1177 - def latest_fedora_branched_chroot(cls, arch='x86_64'):
1178 return (cls.query 1179 .filter(cls.is_active == True) 1180 .filter(cls.os_release == 'fedora') 1181 .filter(cls.os_version != 'rawhide') 1182 .filter(cls.arch == arch) 1183 .order_by(cls.os_version.desc()) 1184 .first())
1185 1186 @property
1187 - def name(self):
1188 """ 1189 Textual representation of name of this chroot 1190 """ 1191 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
1192 1193 @property
1194 - def name_release(self):
1195 """ 1196 Textual representation of name of this or release 1197 """ 1198 return "{}-{}".format(self.os_release, self.os_version)
1199 1200 @property
1201 - def os(self):
1202 """ 1203 Textual representation of the operating system name 1204 """ 1205 return "{0} {1}".format(self.os_release, self.os_version)
1206 1207 @property
1208 - def serializable_attributes(self):
1209 attr_list = super(MockChroot, self).serializable_attributes 1210 attr_list.extend(["name", "os"]) 1211 return attr_list
1212
1213 1214 -class CoprChroot(db.Model, helpers.Serializer):
1215 """ 1216 Representation of Copr<->MockChroot relation 1217 """ 1218 1219 buildroot_pkgs = db.Column(db.Text) 1220 repos = db.Column(db.Text, default="", server_default="", nullable=False) 1221 mock_chroot_id = db.Column( 1222 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True) 1223 mock_chroot = db.relationship( 1224 "MockChroot", backref=db.backref("copr_chroots")) 1225 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 1226 copr = db.relationship("Copr", 1227 backref=db.backref( 1228 "copr_chroots", 1229 single_parent=True, 1230 cascade="all,delete,delete-orphan")) 1231 1232 comps_zlib = db.Column(db.LargeBinary(), nullable=True) 1233 comps_name = db.Column(db.String(127), nullable=True) 1234 1235 with_opts = db.Column(db.Text, default="", server_default="", nullable=False) 1236 without_opts = db.Column(db.Text, default="", server_default="", nullable=False) 1237 1238 # Once mock_chroot gets EOL, copr_chroots are going to be deleted 1239 # if their owner doesn't extend their time span 1240 delete_after = db.Column(db.DateTime, index=True) 1241 delete_notify = db.Column(db.DateTime, index=True) 1242
1243 - def update_comps(self, comps_xml):
1244 if isinstance(comps_xml, str): 1245 data = comps_xml.encode("utf-8") 1246 else: 1247 data = comps_xml 1248 self.comps_zlib = zlib.compress(data)
1249 1250 @property
1251 - def buildroot_pkgs_list(self):
1252 return (self.buildroot_pkgs or "").split()
1253 1254 @property
1255 - def repos_list(self):
1256 return (self.repos or "").split()
1257 1258 @property
1259 - def comps(self):
1260 if self.comps_zlib: 1261 return zlib.decompress(self.comps_zlib).decode("utf-8")
1262 1263 @property
1264 - def comps_len(self):
1265 if self.comps_zlib: 1266 return len(zlib.decompress(self.comps_zlib)) 1267 else: 1268 return 0
1269 1270 @property
1271 - def name(self):
1272 return self.mock_chroot.name
1273 1274 @property
1275 - def is_active(self):
1276 return self.mock_chroot.is_active
1277 1278 @property
1279 - def delete_after_days(self):
1280 if not self.delete_after: 1281 return None 1282 now = datetime.datetime.now() 1283 days = (self.delete_after - now).days 1284 return days if days > 0 else 0
1285
1286 - def to_dict(self):
1287 options = {"__columns_only__": [ 1288 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts" 1289 ]} 1290 d = super(CoprChroot, self).to_dict(options=options) 1291 d["mock_chroot"] = self.mock_chroot.name 1292 return d
1293
1294 1295 -class BuildChroot(db.Model, helpers.Serializer):
1296 """ 1297 Representation of Build<->MockChroot relation 1298 """ 1299 1300 __table_args__ = (db.Index('build_chroot_status_started_on_idx', "status", "started_on"),) 1301 1302 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"), 1303 primary_key=True) 1304 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds")) 1305 build_id = db.Column(db.Integer, db.ForeignKey("build.id"), 1306 primary_key=True) 1307 build = db.relationship("Build", backref=db.backref("build_chroots")) 1308 git_hash = db.Column(db.String(40)) 1309 status = db.Column(db.Integer, default=StatusEnum("waiting")) 1310 1311 started_on = db.Column(db.Integer, index=True) 1312 ended_on = db.Column(db.Integer, index=True) 1313 1314 # directory name on backend with build results 1315 result_dir = db.Column(db.Text, default='', server_default='', nullable=False) 1316 1317 build_requires = db.Column(db.Text) 1318 1319 @property
1320 - def name(self):
1321 """ 1322 Textual representation of name of this chroot 1323 """ 1324 return self.mock_chroot.name
1325 1326 @property
1327 - def state(self):
1328 """ 1329 Return text representation of status of this build chroot 1330 """ 1331 if self.status is not None: 1332 return StatusEnum(self.status) 1333 return "unknown"
1334 1335 @property
1336 - def finished(self):
1337 return self.state in helpers.FINISHED_STATUSES
1338 1339 @property
1340 - def task_id(self):
1341 return "{}-{}".format(self.build_id, self.name)
1342 1343 @property
1344 - def dist_git_url(self):
1345 if app.config["DIST_GIT_URL"]: 1346 if self.state == "forked": 1347 if self.build.copr.forked_from.deleted: 1348 return None 1349 copr_dirname = self.build.copr.forked_from.main_dir.full_name 1350 else: 1351 copr_dirname = self.build.copr_dir.full_name 1352 return "{}/{}/{}.git/commit/?id={}".format(app.config["DIST_GIT_URL"], 1353 copr_dirname, 1354 self.build.package.name, 1355 self.git_hash) 1356 return None
1357 1358 @property
1359 - def result_dir_url(self):
1360 if not self.result_dir: 1361 return None 1362 return urljoin(app.config["BACKEND_BASE_URL"], os.path.join( 1363 "results", self.build.copr_dir.full_name, self.name, self.result_dir, ""))
1364
1365 1366 -class LegalFlag(db.Model, helpers.Serializer):
1367 id = db.Column(db.Integer, primary_key=True) 1368 # message from user who raised the flag (what he thinks is wrong) 1369 raise_message = db.Column(db.Text) 1370 # time of raising the flag as returned by int(time.time()) 1371 raised_on = db.Column(db.Integer) 1372 # time of resolving the flag by admin as returned by int(time.time()) 1373 resolved_on = db.Column(db.Integer) 1374 1375 # relations 1376 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True) 1377 # cascade="all" means that we want to keep these even if copr is deleted 1378 copr = db.relationship( 1379 "Copr", backref=db.backref("legal_flags", cascade="all")) 1380 # user who reported the problem 1381 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id")) 1382 reporter = db.relationship("User", 1383 backref=db.backref("legal_flags_raised"), 1384 foreign_keys=[reporter_id], 1385 primaryjoin="LegalFlag.reporter_id==User.id") 1386 # admin who resolved the problem 1387 resolver_id = db.Column( 1388 db.Integer, db.ForeignKey("user.id"), nullable=True) 1389 resolver = db.relationship("User", 1390 backref=db.backref("legal_flags_resolved"), 1391 foreign_keys=[resolver_id], 1392 primaryjoin="LegalFlag.resolver_id==User.id")
1393
1394 1395 -class Action(db.Model, helpers.Serializer):
1396 """ 1397 Representation of a custom action that needs 1398 backends cooperation/admin attention/... 1399 """ 1400 1401 id = db.Column(db.Integer, primary_key=True) 1402 # see ActionTypeEnum 1403 action_type = db.Column(db.Integer, nullable=False) 1404 # copr, ...; downcase name of class of modified object 1405 object_type = db.Column(db.String(20)) 1406 # id of the modified object 1407 object_id = db.Column(db.Integer) 1408 # old and new values of the changed property 1409 old_value = db.Column(db.String(255)) 1410 new_value = db.Column(db.String(255)) 1411 # additional data 1412 data = db.Column(db.Text) 1413 # result of the action, see BackendResultEnum 1414 result = db.Column( 1415 db.Integer, default=BackendResultEnum("waiting")) 1416 # optional message from the backend/whatever 1417 message = db.Column(db.Text) 1418 # time created as returned by int(time.time()) 1419 created_on = db.Column(db.Integer) 1420 # time ended as returned by int(time.time()) 1421 ended_on = db.Column(db.Integer) 1422
1423 - def __str__(self):
1424 return self.__unicode__()
1425
1426 - def __unicode__(self):
1427 if self.action_type == ActionTypeEnum("delete"): 1428 return "Deleting {0} {1}".format(self.object_type, self.old_value) 1429 elif self.action_type == ActionTypeEnum("legal-flag"): 1430 return "Legal flag on copr {0}.".format(self.old_value) 1431 1432 return "Action {0} on {1}, old value: {2}, new value: {3}.".format( 1433 self.action_type, self.object_type, self.old_value, self.new_value)
1434
1435 - def to_dict(self, **kwargs):
1436 d = super(Action, self).to_dict() 1437 if d.get("object_type") == "module": 1438 module = Module.query.filter(Module.id == d["object_id"]).first() 1439 data = json.loads(d["data"]) 1440 data.update({ 1441 "projectname": module.copr.name, 1442 "ownername": module.copr.owner_name, 1443 "modulemd_b64": module.yaml_b64, 1444 }) 1445 d["data"] = json.dumps(data) 1446 return d
1447
1448 1449 -class Krb5Login(db.Model, helpers.Serializer):
1450 """ 1451 Represents additional user information for kerberos authentication. 1452 """ 1453 1454 __tablename__ = "krb5_login" 1455 1456 # FK to User table 1457 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) 1458 1459 # 'string' from 'copr.conf' from KRB5_LOGIN[string] 1460 config_name = db.Column(db.String(30), nullable=False, primary_key=True) 1461 1462 # krb's primary, i.e. 'username' from 'username@EXAMPLE.COM' 1463 primary = db.Column(db.String(80), nullable=False, primary_key=True) 1464 1465 user = db.relationship("User", backref=db.backref("krb5_logins"))
1466
1467 1468 -class CounterStat(db.Model, helpers.Serializer):
1469 """ 1470 Generic store for simple statistics. 1471 """ 1472 1473 name = db.Column(db.String(127), primary_key=True) 1474 counter_type = db.Column(db.String(30)) 1475 1476 counter = db.Column(db.Integer, default=0, server_default="0")
1477
1478 1479 -class Group(db.Model, helpers.Serializer):
1480 1481 """ 1482 Represents FAS groups and their aliases in Copr 1483 """ 1484 1485 id = db.Column(db.Integer, primary_key=True) 1486 name = db.Column(db.String(127)) 1487 1488 # TODO: add unique=True 1489 fas_name = db.Column(db.String(127)) 1490 1491 @property
1492 - def at_name(self):
1493 return u"@{}".format(self.name)
1494
1495 - def __str__(self):
1496 return self.__unicode__()
1497
1498 - def __unicode__(self):
1499 return "{} (fas: {})".format(self.name, self.fas_name)
1500
1501 1502 -class Batch(db.Model):
1503 id = db.Column(db.Integer, primary_key=True) 1504 blocked_by_id = db.Column(db.Integer, db.ForeignKey("batch.id"), nullable=True) 1505 blocked_by = db.relationship("Batch", remote_side=[id]) 1506 1507 @property
1508 - def finished(self):
1509 return all([b.finished for b in self.builds])
1510
1511 1512 -class Module(db.Model, helpers.Serializer):
1513 id = db.Column(db.Integer, primary_key=True) 1514 name = db.Column(db.String(100), nullable=False) 1515 stream = db.Column(db.String(100), nullable=False) 1516 version = db.Column(db.BigInteger, nullable=False) 1517 summary = db.Column(db.String(100), nullable=False) 1518 description = db.Column(db.Text) 1519 created_on = db.Column(db.Integer, nullable=True) 1520 1521 # When someone submits YAML (not generate one on the copr modules page), we might want to use that exact file. 1522 # Yaml produced by deconstructing into pieces and constructed back can look differently, 1523 # which is not desirable (Imo) 1524 # 1525 # Also if there are fields which are not covered by this model, we will be able to add them in the future 1526 # and fill them with data from this blob 1527 yaml_b64 = db.Column(db.Text) 1528 1529 # relations 1530 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 1531 copr = db.relationship("Copr", backref=db.backref("modules")) 1532 1533 __table_args__ = ( 1534 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"), 1535 ) 1536 1537 @property
1538 - def yaml(self):
1539 return base64.b64decode(self.yaml_b64)
1540 1541 @property
1542 - def modulemd(self):
1543 mmd = Modulemd.ModuleStream() 1544 mmd.import_from_string(self.yaml.decode("utf-8")) 1545 return mmd
1546 1547 @property
1548 - def nsv(self):
1549 return "-".join([self.name, self.stream, str(self.version)])
1550 1551 @property
1552 - def full_name(self):
1553 return "{}/{}".format(self.copr.full_name, self.nsv)
1554 1555 @property
1556 - def action(self):
1557 return Action.query.filter(Action.object_type == "module").filter(Action.object_id == self.id).first()
1558 1559 @property
1560 - def status(self):
1561 """ 1562 Return numeric representation of status of this build 1563 """ 1564 if any(b for b in self.builds if b.status == StatusEnum("failed")): 1565 return ModuleStatusEnum("failed") 1566 return self.action.result if self.action else ModuleStatusEnum("pending")
1567 1568 @property
1569 - def state(self):
1570 """ 1571 Return text representation of status of this build 1572 """ 1573 return ModuleStatusEnum(self.status)
1574 1575 @property
1576 - def rpm_filter(self):
1577 return self.modulemd.get_rpm_filter().get()
1578 1579 @property
1580 - def rpm_api(self):
1581 return self.modulemd.get_rpm_api().get()
1582 1583 @property
1584 - def profiles(self):
1585 return {k: v.get_rpms().get() for k, v in self.modulemd.get_profiles().items()}
1586
1587 1588 -class BuildsStatistics(db.Model):
1589 time = db.Column(db.Integer, primary_key=True) 1590 stat_type = db.Column(db.Text, primary_key=True) 1591 running = db.Column(db.Integer) 1592 pending = db.Column(db.Integer)
1593