Package coprs :: Module models
[hide private]
[frames] | no frames]

Source Code for Module coprs.models

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