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 flask 
   5  import json 
   6  import base64 
   7  import modulemd 
   8  import uuid 
   9   
  10  from sqlalchemy.ext.associationproxy import association_proxy 
  11  from six.moves.urllib.parse import urljoin 
  12  from libravatar import libravatar_url 
  13  import zlib 
  14   
  15  from coprs import constants 
  16  from coprs import db 
  17  from coprs import helpers 
  18  from coprs import app 
  19   
  20  import itertools 
  21  import operator 
  22  from coprs.helpers import BuildSourceEnum, StatusEnum, ActionTypeEnum, JSONEncodedDict 
23 24 25 -class CoprSearchRelatedData(object):
28
29 30 -class User(db.Model, helpers.Serializer):
31 """ 32 Represents user of the copr frontend 33 """ 34 35 id = db.Column(db.Integer, primary_key=True) 36 37 # unique username 38 username = db.Column(db.String(100), nullable=False, unique=True) 39 40 # email 41 mail = db.Column(db.String(150), nullable=False) 42 43 # optional timezone 44 timezone = db.Column(db.String(50), nullable=True) 45 46 # is this user proven? proven users can modify builder memory and 47 # timeout for single builds 48 proven = db.Column(db.Boolean, default=False) 49 50 # is this user admin of the system? 51 admin = db.Column(db.Boolean, default=False) 52 53 # can this user behave as someone else? 54 proxy = db.Column(db.Boolean, default=False) 55 56 # stuff for the cli interface 57 api_login = db.Column(db.String(40), nullable=False, default="abc") 58 api_token = db.Column(db.String(40), nullable=False, default="abc") 59 api_token_expiration = db.Column( 60 db.Date, nullable=False, default=datetime.date(2000, 1, 1)) 61 62 # list of groups as retrieved from openid 63 openid_groups = db.Column(JSONEncodedDict) 64 65 @property
66 - def name(self):
67 """ 68 Return the short username of the user, e.g. bkabrda 69 """ 70 71 return self.username
72
73 - def permissions_for_copr(self, copr):
74 """ 75 Get permissions of this user for the given copr. 76 Caches the permission during one request, 77 so use this if you access them multiple times 78 """ 79 80 if not hasattr(self, "_permissions_for_copr"): 81 self._permissions_for_copr = {} 82 if copr.name not in self._permissions_for_copr: 83 self._permissions_for_copr[copr.name] = ( 84 CoprPermission.query 85 .filter_by(user=self) 86 .filter_by(copr=copr) 87 .first() 88 ) 89 return self._permissions_for_copr[copr.name]
90
91 - def can_build_in(self, copr):
92 """ 93 Determine if this user can build in the given copr. 94 """ 95 can_build = False 96 if copr.user_id == self.id: 97 can_build = True 98 if (self.permissions_for_copr(copr) and 99 self.permissions_for_copr(copr).copr_builder == 100 helpers.PermissionEnum("approved")): 101 102 can_build = True 103 104 # a bit dirty code, here we access flask.session object 105 if copr.group is not None and \ 106 copr.group.fas_name in self.user_teams: 107 return True 108 109 return can_build
110 111 @property
112 - def user_teams(self):
113 if self.openid_groups and 'fas_groups' in self.openid_groups: 114 return self.openid_groups['fas_groups'] 115 else: 116 return []
117 118 @property
119 - def user_groups(self):
120 return Group.query.filter(Group.fas_name.in_(self.user_teams)).all()
121
122 - def can_build_in_group(self, group):
123 """ 124 :type group: Group 125 """ 126 if group.fas_name in self.user_teams: 127 return True 128 else: 129 return False
130
131 - def can_edit(self, copr):
132 """ 133 Determine if this user can edit the given copr. 134 """ 135 136 if copr.user == self or self.admin: 137 return True 138 if (self.permissions_for_copr(copr) and 139 self.permissions_for_copr(copr).copr_admin == 140 helpers.PermissionEnum("approved")): 141 142 return True 143 144 if copr.group is not None and \ 145 copr.group.fas_name in self.user_teams: 146 return True 147 148 return False
149 150 @property
151 - def serializable_attributes(self):
152 # enumerate here to prevent exposing credentials 153 return ["id", "name"]
154 155 @property
156 - def coprs_count(self):
157 """ 158 Get number of coprs for this user. 159 """ 160 161 return (Copr.query.filter_by(user=self). 162 filter_by(deleted=False). 163 filter_by(group_id=None). 164 count())
165 166 @property
167 - def gravatar_url(self):
168 """ 169 Return url to libravatar image. 170 """ 171 172 try: 173 return libravatar_url(email=self.mail, https=True) 174 except IOError: 175 return ""
176
177 178 -class Copr(db.Model, helpers.Serializer, CoprSearchRelatedData):
179 """ 180 Represents a single copr (private repo with builds, mock chroots, etc.). 181 """ 182 183 __table_args__ = ( 184 db.Index('copr_webhook_secret', 'webhook_secret'), 185 ) 186 187 id = db.Column(db.Integer, primary_key=True) 188 # name of the copr, no fancy chars (checked by forms) 189 name = db.Column(db.String(100), nullable=False) 190 homepage = db.Column(db.Text) 191 contact = db.Column(db.Text) 192 # string containing urls of additional repos (separated by space) 193 # that this copr will pull dependencies from 194 repos = db.Column(db.Text) 195 # time of creation as returned by int(time.time()) 196 created_on = db.Column(db.Integer) 197 # description and instructions given by copr owner 198 description = db.Column(db.Text) 199 instructions = db.Column(db.Text) 200 deleted = db.Column(db.Boolean, default=False) 201 playground = db.Column(db.Boolean, default=False) 202 203 # should copr run `createrepo` each time when build packages are changed 204 auto_createrepo = db.Column(db.Boolean, default=True) 205 206 # relations 207 user_id = db.Column(db.Integer, db.ForeignKey("user.id")) 208 user = db.relationship("User", backref=db.backref("coprs")) 209 group_id = db.Column(db.Integer, db.ForeignKey("group.id")) 210 group = db.relationship("Group", backref=db.backref("groups")) 211 mock_chroots = association_proxy("copr_chroots", "mock_chroot") 212 forked_from_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 213 forked_from = db.relationship("Copr", remote_side=id, backref=db.backref("forks")) 214 215 # a secret to be used for webhooks authentication 216 webhook_secret = db.Column(db.String(100)) 217 218 # enable networking for the builds by default 219 build_enable_net = db.Column(db.Boolean, default=True, 220 server_default="1", nullable=False) 221 222 unlisted_on_hp = db.Column(db.Boolean, default=False, nullable=False) 223 224 # information for search index updating 225 latest_indexed_data_update = db.Column(db.Integer) 226 227 # builds and the project are immune against deletion 228 persistent = db.Column(db.Boolean, default=False, nullable=False, server_default="0") 229 230 # if backend deletion script should be run for the project's builds 231 auto_prune = db.Column(db.Boolean, default=True, nullable=False, server_default="1") 232 233 # use mock's bootstrap container feature 234 use_bootstrap_container = db.Column(db.Boolean, default=False, nullable=False, server_default="0") 235 236 # if chroots for the new branch should be auto-enabled and populated from rawhide ones 237 follow_fedora_branching = db.Column(db.Boolean, default=True, nullable=False, server_default="1") 238 239 # scm integration properties 240 scm_repo_url = db.Column(db.Text) 241 scm_api_type = db.Column(db.Text) 242 scm_api_auth_json = db.Column(db.Text) 243 244 __mapper_args__ = { 245 "order_by": created_on.desc() 246 } 247 248 @property
249 - def main_dir(self):
250 """ 251 Return main copr dir for a Copr 252 """ 253 return CoprDir.query.filter(CoprDir.copr_id==self.id).filter(CoprDir.main==True).one()
254 255 @property
256 - def scm_api_auth(self):
257 if not self.scm_api_auth_json: 258 return {} 259 return json.loads(self.scm_api_auth_json)
260 261 @property
262 - def is_a_group_project(self):
263 """ 264 Return True if copr belongs to a group 265 """ 266 return self.group is not None
267 268 @property
269 - def owner(self):
270 """ 271 Return owner (user or group) of this copr 272 """ 273 return self.group if self.is_a_group_project else self.user
274 275 @property
276 - def owner_name(self):
277 """ 278 Return @group.name for a copr owned by a group and user.name otherwise 279 """ 280 return self.group.at_name if self.is_a_group_project else self.user.name
281 282 @property
283 - def repos_list(self):
284 """ 285 Return repos of this copr as a list of strings 286 """ 287 return self.repos.split()
288 289 @property
290 - def active_chroots(self):
291 """ 292 Return list of active mock_chroots of this copr 293 """ 294 return filter(lambda x: x.is_active, self.mock_chroots)
295 296 @property
297 - def active_copr_chroots(self):
298 """ 299 :rtype: list of CoprChroot 300 """ 301 return [c for c in self.copr_chroots if c.is_active]
302 303 @property
304 - def active_chroots_sorted(self):
305 """ 306 Return list of active mock_chroots of this copr 307 """ 308 return sorted(self.active_chroots, key=lambda ch: ch.name)
309 310 @property
311 - def active_chroots_grouped(self):
312 """ 313 Return list of active mock_chroots of this copr 314 """ 315 chroots = [("{} {}".format(c.os_release, c.os_version), c.arch) for c in self.active_chroots_sorted] 316 output = [] 317 for os, chs in itertools.groupby(chroots, operator.itemgetter(0)): 318 output.append((os, [ch[1] for ch in chs])) 319 320 return output
321 322 @property
323 - def build_count(self):
324 """ 325 Return number of builds in this copr 326 """ 327 return len(self.builds)
328 329 @property
330 - def disable_createrepo(self):
331 return not self.auto_createrepo
332 333 @disable_createrepo.setter
334 - def disable_createrepo(self, value):
335 self.auto_createrepo = not bool(value)
336 337 @property
338 - def devel_mode(self):
339 return self.disable_createrepo
340 341 @property
342 - def modified_chroots(self):
343 """ 344 Return list of chroots which has been modified 345 """ 346 modified_chroots = [] 347 for chroot in self.copr_chroots: 348 if ((chroot.buildroot_pkgs or chroot.repos 349 or chroot.with_opts or chroot.without_opts) 350 and chroot.is_active): 351 modified_chroots.append(chroot) 352 return modified_chroots
353
354 - def is_release_arch_modified(self, name_release, arch):
355 if "{}-{}".format(name_release, arch) in \ 356 [chroot.name for chroot in self.modified_chroots]: 357 return True 358 return False
359 360 @property
361 - def full_name(self):
362 return "{}/{}".format(self.owner_name, self.name)
363 364 @property
365 - def repo_name(self):
366 return "{}-{}".format(self.owner_name, self.main_dir.name)
367 368 @property
369 - def repo_url(self):
370 return "/".join([app.config["BACKEND_BASE_URL"], 371 u"results", 372 self.main_dir.full_name])
373 374 @property
375 - def repo_id(self):
376 if self.is_a_group_project: 377 return "group_{}-{}".format(self.group.name, self.main_dir.name) 378 else: 379 return "{}-{}".format(self.user.name, self.main_dir.name)
380 381 @property
382 - def modules_url(self):
383 return "/".join([self.repo_url, "modules"])
384
385 - def to_dict(self, private=False, show_builds=True, show_chroots=True):
386 result = {} 387 for key in ["id", "name", "description", "instructions"]: 388 result[key] = str(copy.copy(getattr(self, key))) 389 result["owner"] = self.owner_name 390 return result
391 392 @property
393 - def still_forking(self):
394 return bool(Action.query.filter(Action.result == helpers.BackendResultEnum("waiting")) 395 .filter(Action.action_type == helpers.ActionTypeEnum("fork")) 396 .filter(Action.new_value == self.full_name).all())
397 400 401 @property
402 - def enable_net(self):
403 return self.build_enable_net
404 405 @enable_net.setter
406 - def enable_net(self, value):
407 self.build_enable_net = value
408
409 - def new_webhook_secret(self):
410 self.webhook_secret = str(uuid.uuid4())
411
412 413 -class CoprPermission(db.Model, helpers.Serializer):
414 """ 415 Association class for Copr<->Permission relation 416 """ 417 418 # see helpers.PermissionEnum for possible values of the fields below 419 # can this user build in the copr? 420 copr_builder = db.Column(db.SmallInteger, default=0) 421 # can this user serve as an admin? (-> edit and approve permissions) 422 copr_admin = db.Column(db.SmallInteger, default=0) 423 424 # relations 425 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True) 426 user = db.relationship("User", backref=db.backref("copr_permissions")) 427 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 428 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
429
430 431 -class CoprDir(db.Model):
432 """ 433 Represents one of data directories for a copr. 434 """ 435 id = db.Column(db.Integer, primary_key=True) 436 437 name = db.Column(db.Text, index=True) 438 main = db.Column(db.Boolean, default=False, server_default="0", nullable=False) 439 440 ownername = db.Column(db.Text, index=True, nullable=False) 441 442 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), index=True, nullable=False) 443 copr = db.relationship("Copr", backref=db.backref("dirs")) 444 445 __table_args__ = ( 446 db.Index('only_one_main_copr_dir', copr_id, main, 447 unique=True, postgresql_where=(main==True)), 448 449 db.UniqueConstraint('ownername', 'name', 450 name='ownername_copr_dir_uniq'), 451 ) 452
453 - def __init__(self, *args, **kwargs):
454 if kwargs.get('copr') and not kwargs.get('ownername'): 455 kwargs['ownername'] = kwargs.get('copr').owner_name 456 super(CoprDir, self).__init__(*args, **kwargs)
457 458 @property
459 - def full_name(self):
460 return "{}/{}".format(self.copr.owner_name, self.name)
461 462 @property
463 - def repo_name(self):
464 return "{}-{}".format(self.copr.owner_name, self.name)
465 466 @property
467 - def repo_url(self):
468 return "/".join([app.config["BACKEND_BASE_URL"], 469 u"results", self.full_name])
470 471 @property
472 - def repo_id(self):
473 if self.copr.is_a_group_project: 474 return "group_{}-{}".format(self.copr.group.name, self.name) 475 else: 476 return "{}-{}".format(self.copr.user.name, self.name)
477
478 479 -class Package(db.Model, helpers.Serializer, CoprSearchRelatedData):
480 """ 481 Represents a single package in a project_dir. 482 """ 483 484 __table_args__ = ( 485 db.UniqueConstraint('copr_dir_id', 'name', name='packages_copr_dir_pkgname'), 486 db.Index('package_webhook_sourcetype', 'webhook_rebuild', 'source_type'), 487 ) 488
489 - def __init__(self, *args, **kwargs):
490 if kwargs.get('copr') and not kwargs.get('copr_dir'): 491 kwargs['copr_dir'] = kwargs.get('copr').main_dir 492 super(Package, self).__init__(*args, **kwargs)
493 494 id = db.Column(db.Integer, primary_key=True) 495 name = db.Column(db.String(100), nullable=False) 496 # Source of the build: type identifier 497 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset")) 498 # Source of the build: description in json, example: git link, srpm url, etc. 499 source_json = db.Column(db.Text) 500 # True if the package is built automatically via webhooks 501 webhook_rebuild = db.Column(db.Boolean, default=False) 502 # enable networking during a build process 503 enable_net = db.Column(db.Boolean, default=False, server_default="0", nullable=False) 504 505 # @TODO Remove me few weeks after Copr migration 506 # Contain status of the Package before migration 507 # Normally the `status` is not stored in `Package`. It is computed from `status` variable of `BuildChroot`, 508 # but `old_status` has to be stored here, because we migrate whole `package` table, but only succeeded builds. 509 # Therefore if `old_status` was in `BuildChroot` we wouldn't be able to know old state of non-succeeded packages 510 # even though it would be known before migration. 511 old_status = db.Column(db.Integer) 512 513 builds = db.relationship("Build", order_by="Build.id") 514 515 # relations 516 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 517 copr = db.relationship("Copr", backref=db.backref("packages")) 518 519 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True) 520 copr_dir = db.relationship("CoprDir", backref=db.backref("packages")) 521 522 @property
523 - def dist_git_repo(self):
524 return "{}/{}".format(self.copr_dir.full_name, self.name)
525 526 @property
527 - def source_json_dict(self):
528 if not self.source_json: 529 return {} 530 return json.loads(self.source_json)
531 532 @property
533 - def source_type_text(self):
535 536 @property
537 - def has_source_type_set(self):
538 """ 539 Package's source type (and source_json) is being derived from its first build, which works except 540 for "link" and "upload" cases. Consider these being equivalent to source_type being unset. 541 """ 542 return self.source_type and self.source_type_text != "link" and self.source_type_text != "upload"
543 544 @property
545 - def dist_git_url(self):
546 if "DIST_GIT_URL" in app.config: 547 return "{}/{}.git".format(app.config["DIST_GIT_URL"], self.dist_git_repo) 548 return None
549 550 @property
551 - def dist_git_clone_url(self):
552 if "DIST_GIT_CLONE_URL" in app.config: 553 return "{}/{}.git".format(app.config["DIST_GIT_CLONE_URL"], self.dist_git_repo) 554 else: 555 return self.dist_git_url
556
557 - def last_build(self, successful=False):
558 for build in reversed(self.builds): 559 if not successful or build.state == "succeeded": 560 return build 561 return None
562
563 - def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
564 package_dict = super(Package, self).to_dict() 565 package_dict['source_type'] = helpers.BuildSourceEnum(package_dict['source_type']) 566 567 if with_latest_build: 568 build = self.last_build(successful=False) 569 package_dict['latest_build'] = build.to_dict(with_chroot_states=True) if build else None 570 if with_latest_succeeded_build: 571 build = self.last_build(successful=True) 572 package_dict['latest_succeeded_build'] = build.to_dict(with_chroot_states=True) if build else None 573 if with_all_builds: 574 package_dict['builds'] = [build.to_dict(with_chroot_states=True) for build in reversed(self.builds)] 575 576 return package_dict
577
580
581 582 -class Build(db.Model, helpers.Serializer):
583 """ 584 Representation of one build in one copr 585 """ 586 587 SCM_COMMIT = 'commit' 588 SCM_PULL_REQUEST = 'pull-request' 589 590 __table_args__ = (db.Index('build_canceled', "canceled"), 591 db.Index('build_order', "is_background", "id"), 592 db.Index('build_filter', "source_type", "canceled")) 593
594 - def __init__(self, *args, **kwargs):
595 if kwargs.get('source_type') == helpers.BuildSourceEnum("custom"): 596 source_dict = json.loads(kwargs['source_json']) 597 if 'fedora-latest' in source_dict['chroot']: 598 arch = source_dict['chroot'].rsplit('-', 2)[2] 599 source_dict['chroot'] = \ 600 MockChroot.latest_fedora_branched_chroot(arch=arch).name 601 kwargs['source_json'] = json.dumps(source_dict) 602 603 if kwargs.get('copr') and not kwargs.get('copr_dir'): 604 kwargs['copr_dir'] = kwargs.get('copr').main_dir 605 606 super(Build, self).__init__(*args, **kwargs)
607 608 id = db.Column(db.Integer, primary_key=True) 609 # single url to the source rpm, should not contain " ", "\n", "\t" 610 pkgs = db.Column(db.Text) 611 # built packages 612 built_packages = db.Column(db.Text) 613 # version of the srpm package got by rpm 614 pkg_version = db.Column(db.Text) 615 # was this build canceled by user? 616 canceled = db.Column(db.Boolean, default=False) 617 # list of space separated additional repos 618 repos = db.Column(db.Text) 619 # the three below represent time of important events for this build 620 # as returned by int(time.time()) 621 submitted_on = db.Column(db.Integer, nullable=False) 622 # directory name on backend with the srpm build results 623 result_dir = db.Column(db.Text, default='', server_default='', nullable=False) 624 # memory requirements for backend builder 625 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY) 626 # maximum allowed time of build, build will fail if exceeded 627 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT) 628 # enable networking during a build process 629 enable_net = db.Column(db.Boolean, default=False, 630 server_default="0", nullable=False) 631 # Source of the build: type identifier 632 source_type = db.Column(db.Integer, default=helpers.BuildSourceEnum("unset")) 633 # Source of the build: description in json, example: git link, srpm url, etc. 634 source_json = db.Column(db.Text) 635 # Type of failure: type identifier 636 fail_type = db.Column(db.Integer, default=helpers.FailTypeEnum("unset")) 637 # background builds has lesser priority than regular builds. 638 is_background = db.Column(db.Boolean, default=False, server_default="0", nullable=False) 639 640 source_status = db.Column(db.Integer, default=StatusEnum("waiting")) 641 srpm_url = db.Column(db.Text) 642 643 # relations 644 user_id = db.Column(db.Integer, db.ForeignKey("user.id")) 645 user = db.relationship("User", backref=db.backref("builds")) 646 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 647 copr = db.relationship("Copr", backref=db.backref("builds")) 648 package_id = db.Column(db.Integer, db.ForeignKey("package.id")) 649 package = db.relationship("Package") 650 651 chroots = association_proxy("build_chroots", "mock_chroot") 652 653 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id")) 654 batch = db.relationship("Batch", backref=db.backref("builds")) 655 656 module_id = db.Column(db.Integer, db.ForeignKey("module.id"), index=True) 657 module = db.relationship("Module", backref=db.backref("builds")) 658 659 copr_dir_id = db.Column(db.Integer, db.ForeignKey("copr_dir.id"), index=True) 660 copr_dir = db.relationship("CoprDir", backref=db.backref("builds")) 661 662 # scm integration properties 663 scm_object_id = db.Column(db.Text) 664 scm_object_type = db.Column(db.Text) 665 scm_object_url = db.Column(db.Text) 666 667 # method to call on build state change 668 update_callback = db.Column(db.Text) 669 670 @property
671 - def user_name(self):
672 return self.user.name
673 674 @property
675 - def group_name(self):
676 return self.copr.group.name
677 678 @property
679 - def copr_name(self):
680 return self.copr.name
681 682 @property
683 - def copr_dirname(self):
684 return self.copr_dir.name
685 686 @property
687 - def copr_full_dirname(self):
688 return self.copr_dir.full_name
689 690 @property
691 - def fail_type_text(self):
692 return helpers.FailTypeEnum(self.fail_type)
693 694 @property
695 - def repos_list(self):
696 if self.repos is None: 697 return list() 698 else: 699 return self.repos.split()
700 701 @property
702 - def task_id(self):
703 return str(self.id)
704 705 @property
706 - def id_fixed_width(self):
707 return "{:08d}".format(self.id)
708 709 @property
710 - def import_log_urls(self):
711 backend_log = self.import_log_url_backend 712 types = [helpers.BuildSourceEnum("upload"), helpers.BuildSourceEnum("link")] 713 if self.source_type in types: 714 if json.loads(self.source_json).get("url", "").endswith(".src.rpm"): 715 backend_log = None 716 return filter(None, [backend_log, self.import_log_url_distgit])
717 718 @property
719 - def import_log_url_distgit(self):
720 if app.config["COPR_DIST_GIT_LOGS_URL"]: 721 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"], 722 self.task_id.replace('/', '_')) 723 return None
724 725 @property
726 - def import_log_url_backend(self):
727 parts = ["results", self.copr.owner_name, self.copr_dirname, 728 "srpm-builds", self.id_fixed_width, "builder-live.log"] 729 path = os.path.normpath(os.path.join(*parts)) 730 return urljoin(app.config["BACKEND_BASE_URL"], path)
731 732 @property
733 - def source_json_dict(self):
734 if not self.source_json: 735 return {} 736 return json.loads(self.source_json)
737 738 @property
739 - def started_on(self):
740 return self.min_started_on
741 742 @property
743 - def min_started_on(self):
744 mb_list = [chroot.started_on for chroot in 745 self.build_chroots if chroot.started_on] 746 if len(mb_list) > 0: 747 return min(mb_list) 748 else: 749 return None
750 751 @property
752 - def ended_on(self):
753 return self.max_ended_on
754 755 @property
756 - def max_ended_on(self):
757 if not self.build_chroots: 758 return None 759 if any(chroot.ended_on is None for chroot in self.build_chroots): 760 return None 761 return max(chroot.ended_on for chroot in self.build_chroots)
762 763 @property
764 - def chroots_started_on(self):
765 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
766 767 @property
768 - def chroots_ended_on(self):
769 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
770 771 @property
772 - def source_type_text(self):
774 775 @property
776 - def source_metadata(self):
777 if self.source_json is None: 778 return None 779 780 try: 781 return json.loads(self.source_json) 782 except (TypeError, ValueError): 783 return None
784 785 @property
786 - def chroot_states(self):
787 return map(lambda chroot: chroot.status, self.build_chroots)
788
789 - def get_chroots_by_status(self, statuses=None):
790 """ 791 Get build chroots with states which present in `states` list 792 If states == None, function returns build_chroots 793 """ 794 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states)) 795 if statuses is not None: 796 statuses = set(statuses) 797 else: 798 return self.build_chroots 799 800 return [ 801 chroot for chroot, status in chroot_states_map.items() 802 if status in statuses 803 ]
804 805 @property
806 - def chroots_dict_by_name(self):
807 return {b.name: b for b in self.build_chroots}
808 809 @property
810 - def status(self):
811 """ 812 Return build status. 813 """ 814 if self.canceled: 815 return StatusEnum("canceled") 816 817 for state in ["running", "starting", "pending", "failed", "succeeded", "skipped", "forked", "waiting"]: 818 if StatusEnum(state) in self.chroot_states: 819 if state == "waiting": 820 return self.source_status 821 else: 822 return StatusEnum(state) 823 824 return None
825 826 @property
827 - def state(self):
828 """ 829 Return text representation of status of this build. 830 """ 831 if self.status != None: 832 return StatusEnum(self.status) 833 return "unknown"
834 835 @property
836 - def cancelable(self):
837 """ 838 Find out if this build is cancelable. 839 """ 840 return not self.finished and self.status != StatusEnum("starting")
841 842 @property
843 - def repeatable(self):
844 """ 845 Find out if this build is repeatable. 846 847 Build is repeatable only if sources has been imported. 848 """ 849 return self.source_status == StatusEnum("succeeded")
850 851 @property
852 - def finished(self):
853 """ 854 Find out if this build is in finished state. 855 856 Build is finished only if all its build_chroots are in finished state or 857 the build was canceled. 858 """ 859 return self.canceled or all([chroot.finished for chroot in self.build_chroots])
860 861 @property
862 - def persistent(self):
863 """ 864 Find out if this build is persistent. 865 866 This property is inherited from the project. 867 """ 868 return self.copr.persistent
869 870 @property
871 - def package_name(self):
872 try: 873 return self.package.name 874 except: 875 return None
876
877 - def to_dict(self, options=None, with_chroot_states=False):
878 result = super(Build, self).to_dict(options) 879 result["src_pkg"] = result["pkgs"] 880 del result["pkgs"] 881 del result["copr_id"] 882 883 result['source_type'] = helpers.BuildSourceEnum(result['source_type']) 884 result["state"] = self.state 885 886 if with_chroot_states: 887 result["chroots"] = {b.name: b.state for b in self.build_chroots} 888 889 return result
890
891 892 -class DistGitBranch(db.Model, helpers.Serializer):
893 """ 894 1:N mapping: branch -> chroots 895 """ 896 897 # Name of the branch used on dist-git machine. 898 name = db.Column(db.String(50), primary_key=True)
899
900 901 -class MockChroot(db.Model, helpers.Serializer):
902 """ 903 Representation of mock chroot 904 """ 905 906 __table_args__ = ( 907 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'), 908 ) 909 910 id = db.Column(db.Integer, primary_key=True) 911 # fedora/epel/..., mandatory 912 os_release = db.Column(db.String(50), nullable=False) 913 # 18/rawhide/..., optional (mock chroot doesn"t need to have this) 914 os_version = db.Column(db.String(50), nullable=False) 915 # x86_64/i686/..., mandatory 916 arch = db.Column(db.String(50), nullable=False) 917 is_active = db.Column(db.Boolean, default=True) 918 919 # Reference branch name 920 distgit_branch_name = db.Column(db.String(50), 921 db.ForeignKey("dist_git_branch.name"), 922 nullable=False) 923 924 distgit_branch = db.relationship("DistGitBranch", 925 backref=db.backref("chroots")) 926 927 @classmethod
928 - def latest_fedora_branched_chroot(cls, arch='x86_64'):
929 return (cls.query 930 .filter(cls.is_active == True) 931 .filter(cls.os_release == 'fedora') 932 .filter(cls.os_version != 'rawhide') 933 .filter(cls.arch == arch) 934 .order_by(cls.os_version.desc()) 935 .first())
936 937 @property
938 - def name(self):
939 """ 940 Textual representation of name of this chroot 941 """ 942 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
943 944 @property
945 - def name_release(self):
946 """ 947 Textual representation of name of this or release 948 """ 949 return "{}-{}".format(self.os_release, self.os_version)
950 951 @property
952 - def os(self):
953 """ 954 Textual representation of the operating system name 955 """ 956 return "{0} {1}".format(self.os_release, self.os_version)
957 958 @property
959 - def serializable_attributes(self):
960 attr_list = super(MockChroot, self).serializable_attributes 961 attr_list.extend(["name", "os"]) 962 return attr_list
963
964 965 -class CoprChroot(db.Model, helpers.Serializer):
966 """ 967 Representation of Copr<->MockChroot relation 968 """ 969 970 buildroot_pkgs = db.Column(db.Text) 971 repos = db.Column(db.Text, default="", server_default="", nullable=False) 972 mock_chroot_id = db.Column( 973 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True) 974 mock_chroot = db.relationship( 975 "MockChroot", backref=db.backref("copr_chroots")) 976 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 977 copr = db.relationship("Copr", 978 backref=db.backref( 979 "copr_chroots", 980 single_parent=True, 981 cascade="all,delete,delete-orphan")) 982 983 comps_zlib = db.Column(db.LargeBinary(), nullable=True) 984 comps_name = db.Column(db.String(127), nullable=True) 985 986 module_md_zlib = db.Column(db.LargeBinary(), nullable=True) 987 module_md_name = db.Column(db.String(127), nullable=True) 988 989 with_opts = db.Column(db.Text, default="", server_default="", nullable=False) 990 without_opts = db.Column(db.Text, default="", server_default="", nullable=False) 991
992 - def update_comps(self, comps_xml):
993 if isinstance(comps_xml, str): 994 data = comps_xml.encode("utf-8") 995 else: 996 data = comps_xml 997 self.comps_zlib = zlib.compress(data)
998
999 - def update_module_md(self, module_md_yaml):
1000 if isinstance(module_md_yaml, str): 1001 data = module_md_yaml.encode("utf-8") 1002 else: 1003 data = module_md_yaml 1004 self.module_md_zlib = zlib.compress(data)
1005 1006 @property
1007 - def buildroot_pkgs_list(self):
1008 return (self.buildroot_pkgs or "").split()
1009 1010 @property
1011 - def repos_list(self):
1012 return (self.repos or "").split()
1013 1014 @property
1015 - def comps(self):
1016 if self.comps_zlib: 1017 return zlib.decompress(self.comps_zlib).decode("utf-8")
1018 1019 @property
1020 - def module_md(self):
1021 if self.module_md_zlib: 1022 return zlib.decompress(self.module_md_zlib).decode("utf-8")
1023 1024 @property
1025 - def comps_len(self):
1026 if self.comps_zlib: 1027 return len(zlib.decompress(self.comps_zlib)) 1028 else: 1029 return 0
1030 1031 @property
1032 - def module_md_len(self):
1033 if self.module_md_zlib: 1034 return len(zlib.decompress(self.module_md_zlib)) 1035 else: 1036 return 0
1037 1038 @property
1039 - def name(self):
1040 return self.mock_chroot.name
1041 1042 @property
1043 - def is_active(self):
1044 return self.mock_chroot.is_active
1045
1046 - def to_dict(self):
1047 options = {"__columns_only__": [ 1048 "buildroot_pkgs", "repos", "comps_name", "copr_id", "with_opts", "without_opts" 1049 ]} 1050 d = super(CoprChroot, self).to_dict(options=options) 1051 d["mock_chroot"] = self.mock_chroot.name 1052 return d
1053
1054 1055 -class BuildChroot(db.Model, helpers.Serializer):
1056 """ 1057 Representation of Build<->MockChroot relation 1058 """ 1059 1060 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"), 1061 primary_key=True) 1062 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds")) 1063 build_id = db.Column(db.Integer, db.ForeignKey("build.id"), 1064 primary_key=True) 1065 build = db.relationship("Build", backref=db.backref("build_chroots")) 1066 git_hash = db.Column(db.String(40)) 1067 status = db.Column(db.Integer, default=StatusEnum("waiting")) 1068 1069 started_on = db.Column(db.Integer, index=True) 1070 ended_on = db.Column(db.Integer, index=True) 1071 1072 # directory name on backend with build results 1073 result_dir = db.Column(db.Text, default='', server_default='', nullable=False) 1074 1075 build_requires = db.Column(db.Text) 1076 1077 @property
1078 - def name(self):
1079 """ 1080 Textual representation of name of this chroot 1081 """ 1082 return self.mock_chroot.name
1083 1084 @property
1085 - def state(self):
1086 """ 1087 Return text representation of status of this build chroot 1088 """ 1089 if self.status is not None: 1090 return StatusEnum(self.status) 1091 return "unknown"
1092 1093 @property
1094 - def finished(self):
1095 return (self.state in ["succeeded", "forked", "canceled", "skipped", "failed"])
1096 1097 @property
1098 - def task_id(self):
1099 return "{}-{}".format(self.build_id, self.name)
1100 1101 @property
1102 - def dist_git_url(self):
1103 if app.config["DIST_GIT_URL"]: 1104 if self.state == "forked": 1105 copr_dirname = self.build.copr.forked_from.main_dir.full_name 1106 else: 1107 copr_dirname = self.build.copr_dir.full_name 1108 return "{}/{}/{}.git/commit/?id={}".format(app.config["DIST_GIT_URL"], 1109 copr_dirname, 1110 self.build.package.name, 1111 self.git_hash) 1112 return None
1113 1114 @property
1115 - def result_dir_url(self):
1116 return urljoin(app.config["BACKEND_BASE_URL"], os.path.join( 1117 "results", self.build.copr_dir.full_name, self.name, self.result_dir, ""))
1118
1119 1120 -class LegalFlag(db.Model, helpers.Serializer):
1121 id = db.Column(db.Integer, primary_key=True) 1122 # message from user who raised the flag (what he thinks is wrong) 1123 raise_message = db.Column(db.Text) 1124 # time of raising the flag as returned by int(time.time()) 1125 raised_on = db.Column(db.Integer) 1126 # time of resolving the flag by admin as returned by int(time.time()) 1127 resolved_on = db.Column(db.Integer) 1128 1129 # relations 1130 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True) 1131 # cascade="all" means that we want to keep these even if copr is deleted 1132 copr = db.relationship( 1133 "Copr", backref=db.backref("legal_flags", cascade="all")) 1134 # user who reported the problem 1135 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id")) 1136 reporter = db.relationship("User", 1137 backref=db.backref("legal_flags_raised"), 1138 foreign_keys=[reporter_id], 1139 primaryjoin="LegalFlag.reporter_id==User.id") 1140 # admin who resolved the problem 1141 resolver_id = db.Column( 1142 db.Integer, db.ForeignKey("user.id"), nullable=True) 1143 resolver = db.relationship("User", 1144 backref=db.backref("legal_flags_resolved"), 1145 foreign_keys=[resolver_id], 1146 primaryjoin="LegalFlag.resolver_id==User.id")
1147
1148 1149 -class Action(db.Model, helpers.Serializer):
1150 """ 1151 Representation of a custom action that needs 1152 backends cooperation/admin attention/... 1153 """ 1154 1155 id = db.Column(db.Integer, primary_key=True) 1156 # see ActionTypeEnum 1157 action_type = db.Column(db.Integer, nullable=False) 1158 # copr, ...; downcase name of class of modified object 1159 object_type = db.Column(db.String(20)) 1160 # id of the modified object 1161 object_id = db.Column(db.Integer) 1162 # old and new values of the changed property 1163 old_value = db.Column(db.String(255)) 1164 new_value = db.Column(db.String(255)) 1165 # additional data 1166 data = db.Column(db.Text) 1167 # result of the action, see helpers.BackendResultEnum 1168 result = db.Column( 1169 db.Integer, default=helpers.BackendResultEnum("waiting")) 1170 # optional message from the backend/whatever 1171 message = db.Column(db.Text) 1172 # time created as returned by int(time.time()) 1173 created_on = db.Column(db.Integer) 1174 # time ended as returned by int(time.time()) 1175 ended_on = db.Column(db.Integer) 1176
1177 - def __str__(self):
1178 return self.__unicode__()
1179
1180 - def __unicode__(self):
1181 if self.action_type == ActionTypeEnum("delete"): 1182 return "Deleting {0} {1}".format(self.object_type, self.old_value) 1183 elif self.action_type == ActionTypeEnum("legal-flag"): 1184 return "Legal flag on copr {0}.".format(self.old_value) 1185 1186 return "Action {0} on {1}, old value: {2}, new value: {3}.".format( 1187 self.action_type, self.object_type, self.old_value, self.new_value)
1188
1189 - def to_dict(self, **kwargs):
1190 d = super(Action, self).to_dict() 1191 if d.get("object_type") == "module": 1192 module = Module.query.filter(Module.id == d["object_id"]).first() 1193 data = json.loads(d["data"]) 1194 data.update({ 1195 "projectname": module.copr.name, 1196 "ownername": module.copr.owner_name, 1197 "modulemd_b64": module.yaml_b64, 1198 }) 1199 d["data"] = json.dumps(data) 1200 return d
1201
1202 1203 -class Krb5Login(db.Model, helpers.Serializer):
1204 """ 1205 Represents additional user information for kerberos authentication. 1206 """ 1207 1208 __tablename__ = "krb5_login" 1209 1210 # FK to User table 1211 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) 1212 1213 # 'string' from 'copr.conf' from KRB5_LOGIN[string] 1214 config_name = db.Column(db.String(30), nullable=False, primary_key=True) 1215 1216 # krb's primary, i.e. 'username' from 'username@EXAMPLE.COM' 1217 primary = db.Column(db.String(80), nullable=False, primary_key=True) 1218 1219 user = db.relationship("User", backref=db.backref("krb5_logins"))
1220
1221 1222 -class CounterStat(db.Model, helpers.Serializer):
1223 """ 1224 Generic store for simple statistics. 1225 """ 1226 1227 name = db.Column(db.String(127), primary_key=True) 1228 counter_type = db.Column(db.String(30)) 1229 1230 counter = db.Column(db.Integer, default=0, server_default="0")
1231
1232 1233 -class Group(db.Model, helpers.Serializer):
1234 1235 """ 1236 Represents FAS groups and their aliases in Copr 1237 """ 1238 1239 id = db.Column(db.Integer, primary_key=True) 1240 name = db.Column(db.String(127)) 1241 1242 # TODO: add unique=True 1243 fas_name = db.Column(db.String(127)) 1244 1245 @property
1246 - def at_name(self):
1247 return u"@{}".format(self.name)
1248
1249 - def __str__(self):
1250 return self.__unicode__()
1251
1252 - def __unicode__(self):
1253 return "{} (fas: {})".format(self.name, self.fas_name)
1254
1255 1256 -class Batch(db.Model):
1257 id = db.Column(db.Integer, primary_key=True)
1258
1259 1260 -class Module(db.Model, helpers.Serializer):
1261 id = db.Column(db.Integer, primary_key=True) 1262 name = db.Column(db.String(100), nullable=False) 1263 stream = db.Column(db.String(100), nullable=False) 1264 version = db.Column(db.BigInteger, nullable=False) 1265 summary = db.Column(db.String(100), nullable=False) 1266 description = db.Column(db.Text) 1267 created_on = db.Column(db.Integer, nullable=True) 1268 1269 # When someone submits YAML (not generate one on the copr modules page), we might want to use that exact file. 1270 # Yaml produced by deconstructing into pieces and constructed back can look differently, 1271 # which is not desirable (Imo) 1272 # 1273 # Also if there are fields which are not covered by this model, we will be able to add them in the future 1274 # and fill them with data from this blob 1275 yaml_b64 = db.Column(db.Text) 1276 1277 # relations 1278 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 1279 copr = db.relationship("Copr", backref=db.backref("modules")) 1280 1281 __table_args__ = ( 1282 db.UniqueConstraint("copr_id", "name", "stream", "version", name="copr_name_stream_version_uniq"), 1283 ) 1284 1285 @property
1286 - def yaml(self):
1287 return base64.b64decode(self.yaml_b64)
1288 1289 @property
1290 - def modulemd(self):
1291 mmd = modulemd.ModuleMetadata() 1292 mmd.loads(self.yaml) 1293 return mmd
1294 1295 @property
1296 - def nsv(self):
1297 return "-".join([self.name, self.stream, str(self.version)])
1298 1299 @property
1300 - def full_name(self):
1301 return "{}/{}".format(self.copr.full_name, self.nsv)
1302 1303 @property
1304 - def action(self):
1305 return Action.query.filter(Action.object_type == "module").filter(Action.object_id == self.id).first()
1306 1307 @property
1308 - def status(self):
1309 """ 1310 Return numeric representation of status of this build 1311 """ 1312 if any(b for b in self.builds if b.status == StatusEnum("failed")): 1313 return helpers.ModuleStatusEnum("failed") 1314 return self.action.result if self.action else helpers.ModuleStatusEnum("pending")
1315 1316 @property
1317 - def state(self):
1318 """ 1319 Return text representation of status of this build 1320 """ 1321 return helpers.ModuleStatusEnum(self.status)
1322
1323 1324 -class BuildsStatistics(db.Model):
1325 time = db.Column(db.Integer, primary_key=True) 1326 stat_type = db.Column(db.Text, primary_key=True) 1327 running = db.Column(db.Integer) 1328 pending = db.Column(db.Integer)
1329