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