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