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

Source Code for Module coprs.models

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