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