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