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