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