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