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