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 priority = db.Column(db.BigInteger, default=0, server_default="0", nullable=False) 541 srpm_url = db.Column(db.Text) 542 543 # relations 544 user_id = db.Column(db.Integer, db.ForeignKey("user.id")) 545 user = db.relationship("User", backref=db.backref("builds")) 546 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 547 copr = db.relationship("Copr", backref=db.backref("builds")) 548 package_id = db.Column(db.Integer, db.ForeignKey("package.id")) 549 package = db.relationship("Package") 550 551 chroots = association_proxy("build_chroots", "mock_chroot") 552 553 batch_id = db.Column(db.Integer, db.ForeignKey("batch.id")) 554 batch = db.relationship("Batch", backref=db.backref("builds")) 555 556 @property
557 - def user_name(self):
558 return self.user.name
559 560 @property
561 - def group_name(self):
562 return self.copr.group.name
563 564 @property
565 - def copr_name(self):
566 return self.copr.name
567 568 @property
569 - def fail_type_text(self):
570 return helpers.FailTypeEnum(self.fail_type)
571 572 @property
574 # we have changed result directory naming together with transition to dist-git 575 # that's why we use so strange criterion 576 return self.build_chroots[0].git_hash is None
577 578 @property
579 - def repos_list(self):
580 if self.repos is None: 581 return list() 582 else: 583 return self.repos.split()
584 585 @property
586 - def import_task_id(self):
587 return str(self.id)
588 589 @property
590 - def id_fixed_width(self):
591 return "{:08d}".format(self.id)
592 593 @property
594 - def import_log_urls(self):
595 backend_log = self.import_log_url_backend 596 types = [helpers.BuildSourceEnum("upload"), helpers.BuildSourceEnum("link")] 597 if self.source_type in types: 598 if json.loads(self.source_json).get("url", "").endswith(".src.rpm"): 599 backend_log = None 600 return filter(None, [backend_log, self.import_log_url_distgit])
601 602 @property
603 - def import_log_url_distgit(self):
604 if app.config["COPR_DIST_GIT_LOGS_URL"]: 605 return "{}/{}.log".format(app.config["COPR_DIST_GIT_LOGS_URL"], 606 self.import_task_id.replace('/', '_')) 607 return None
608 609 @property
610 - def import_log_url_backend(self):
611 parts = ["results", self.copr.owner_name, self.copr.name, 612 "srpm-builds", self.id_fixed_width, "builder-live.log"] 613 path = os.path.normpath(os.path.join(*parts)) 614 return urljoin(app.config["BACKEND_BASE_URL"], path)
615 616 @property
617 - def result_dir_name(self):
618 # We can remove this ugly condition after migrating Copr to new machines 619 # It is throw-back from era before dist-git 620 if self.is_older_results_naming_used: 621 return self.src_pkg_name 622 return "-".join([self.id_fixed_width, self.package.name])
623 624 @property
625 - def source_json_dict(self):
626 if not self.source_json: 627 return {} 628 return json.loads(self.source_json)
629 630 @property
631 - def started_on(self):
632 return self.min_started_on
633 634 @property
635 - def min_started_on(self):
636 mb_list = [chroot.started_on for chroot in 637 self.build_chroots if chroot.started_on] 638 if len(mb_list) > 0: 639 return min(mb_list) 640 else: 641 return None
642 643 @property
644 - def ended_on(self):
645 return self.max_ended_on
646 647 @property
648 - def max_ended_on(self):
649 if not self.build_chroots: 650 return None 651 if any(chroot.ended_on is None for chroot in self.build_chroots): 652 return None 653 return max(chroot.ended_on for chroot in self.build_chroots)
654 655 @property
656 - def chroots_started_on(self):
657 return {chroot.name: chroot.started_on for chroot in self.build_chroots}
658 659 @property
660 - def chroots_ended_on(self):
661 return {chroot.name: chroot.ended_on for chroot in self.build_chroots}
662 663 @property
664 - def source_type_text(self):
666 667 @property
668 - def source_metadata(self):
669 if self.source_json is None: 670 return None 671 672 try: 673 return json.loads(self.source_json) 674 except (TypeError, ValueError): 675 return None
676 677 @property
678 - def chroot_states(self):
679 return map(lambda chroot: chroot.status, self.build_chroots)
680
681 - def get_chroots_by_status(self, statuses=None):
682 """ 683 Get build chroots with states which present in `states` list 684 If states == None, function returns build_chroots 685 """ 686 chroot_states_map = dict(zip(self.build_chroots, self.chroot_states)) 687 if statuses is not None: 688 statuses = set(statuses) 689 else: 690 return self.build_chroots 691 692 return [ 693 chroot for chroot, status in chroot_states_map.items() 694 if status in statuses 695 ]
696 697 @property
698 - def chroots_dict_by_name(self):
699 return {b.name: b for b in self.build_chroots}
700 701 @property
702 - def has_pending_chroot(self):
703 # FIXME bad name 704 # used when checking if the repo is initialized and results can be set 705 # i think this is the only purpose - check 706 return StatusEnum("pending") in self.chroot_states or \ 707 StatusEnum("starting") in self.chroot_states
708 709 @property
710 - def has_unfinished_chroot(self):
711 return StatusEnum("pending") in self.chroot_states or \ 712 StatusEnum("starting") in self.chroot_states or \ 713 StatusEnum("running") in self.chroot_states
714 715 @property
716 - def has_importing_chroot(self):
717 return StatusEnum("importing") in self.chroot_states
718 719 @property
720 - def status(self):
721 """ 722 Return build status according to build status of its chroots 723 """ 724 if self.canceled: 725 return StatusEnum("canceled") 726 727 for state in ["running", "starting", "importing", "pending", "failed", "succeeded", "skipped", "forked"]: 728 if StatusEnum(state) in self.chroot_states: 729 return StatusEnum(state)
730 731 @property
732 - def state(self):
733 """ 734 Return text representation of status of this build 735 """ 736 737 if self.status is not None: 738 return StatusEnum(self.status) 739 740 return "unknown"
741 742 @property
743 - def cancelable(self):
744 """ 745 Find out if this build is cancelable. 746 747 Build is cancelabel only when it's pending (not started) 748 """ 749 750 return self.status == StatusEnum("pending") or \ 751 self.status == StatusEnum("importing") or \ 752 self.status == StatusEnum("running")
753 754 @property
755 - def repeatable(self):
756 """ 757 Find out if this build is repeatable. 758 759 Build is repeatable only if it's not pending, starting or running 760 """ 761 return self.status not in [StatusEnum("pending"), 762 StatusEnum("starting"), 763 StatusEnum("running"), 764 StatusEnum("forked")]
765 766 @property
767 - def finished(self):
768 """ 769 Find out if this build is in finished state. 770 771 Build is finished only if all its build_chroots are in finished state. 772 """ 773 return all([(chroot.state in ["succeeded", "forked", "canceled", "skipped", "failed"]) for chroot in self.build_chroots])
774 775 @property
776 - def persistent(self):
777 """ 778 Find out if this build is persistent. 779 780 This property is inherited from the project. 781 """ 782 return self.copr.persistent
783 784 @property
785 - def src_pkg_name(self):
786 """ 787 Extract source package name from source name or url 788 todo: obsolete 789 """ 790 try: 791 src_rpm_name = self.pkgs.split("/")[-1] 792 except: 793 return None 794 if src_rpm_name.endswith(".src.rpm"): 795 return src_rpm_name[:-8] 796 else: 797 return src_rpm_name
798 799 @property
800 - def package_name(self):
801 try: 802 return self.package.name 803 except: 804 return None
805
806 - def to_dict(self, options=None, with_chroot_states=False):
807 result = super(Build, self).to_dict(options) 808 result["src_pkg"] = result["pkgs"] 809 del result["pkgs"] 810 del result["copr_id"] 811 812 result['source_type'] = helpers.BuildSourceEnum(result['source_type']) 813 result["state"] = self.state 814 815 if with_chroot_states: 816 result["chroots"] = {b.name: b.state for b in self.build_chroots} 817 818 return result
819
820 821 -class DistGitBranch(db.Model, helpers.Serializer):
822 """ 823 1:N mapping: branch -> chroots 824 """ 825 826 # Name of the branch used on dist-git machine. 827 name = db.Column(db.String(50), primary_key=True)
828
829 830 -class MockChroot(db.Model, helpers.Serializer):
831 832 """ 833 Representation of mock chroot 834 """ 835 __table_args__ = ( 836 db.UniqueConstraint('os_release', 'os_version', 'arch', name='mock_chroot_uniq'), 837 ) 838 839 id = db.Column(db.Integer, primary_key=True) 840 # fedora/epel/..., mandatory 841 os_release = db.Column(db.String(50), nullable=False) 842 # 18/rawhide/..., optional (mock chroot doesn"t need to have this) 843 os_version = db.Column(db.String(50), nullable=False) 844 # x86_64/i686/..., mandatory 845 arch = db.Column(db.String(50), nullable=False) 846 is_active = db.Column(db.Boolean, default=True) 847 848 # Reference branch name 849 distgit_branch_name = db.Column(db.String(50), 850 db.ForeignKey("dist_git_branch.name"), 851 nullable=False) 852 853 distgit_branch = db.relationship("DistGitBranch", 854 backref=db.backref("chroots")) 855 856 @property
857 - def name(self):
858 """ 859 Textual representation of name of this chroot 860 """ 861 return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
862 863 @property
864 - def name_release(self):
865 """ 866 Textual representation of name of this or release 867 """ 868 return "{}-{}".format(self.os_release, self.os_version)
869 870 @property
871 - def name_release_human(self):
872 """ 873 Textual representation of name of this or release 874 """ 875 return "{} {}".format(self.os_release, self.os_version)
876 877 @property
878 - def os(self):
879 """ 880 Textual representation of the operating system name 881 """ 882 return "{0} {1}".format(self.os_release, self.os_version)
883 884 @property
885 - def serializable_attributes(self):
886 attr_list = super(MockChroot, self).serializable_attributes 887 attr_list.extend(["name", "os"]) 888 return attr_list
889
890 891 -class CoprChroot(db.Model, helpers.Serializer):
892 893 """ 894 Representation of Copr<->MockChroot relation 895 """ 896 897 buildroot_pkgs = db.Column(db.Text) 898 repos = db.Column(db.Text, default="", server_default="", nullable=False) 899 mock_chroot_id = db.Column( 900 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True) 901 mock_chroot = db.relationship( 902 "MockChroot", backref=db.backref("copr_chroots")) 903 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 904 copr = db.relationship("Copr", 905 backref=db.backref( 906 "copr_chroots", 907 single_parent=True, 908 cascade="all,delete,delete-orphan")) 909 910 comps_zlib = db.Column(db.LargeBinary(), nullable=True) 911 comps_name = db.Column(db.String(127), nullable=True) 912 913 module_md_zlib = db.Column(db.LargeBinary(), nullable=True) 914 module_md_name = db.Column(db.String(127), nullable=True) 915
916 - def update_comps(self, comps_xml):
917 self.comps_zlib = zlib.compress(comps_xml.encode("utf-8"))
918
919 - def update_module_md(self, module_md_yaml):
920 self.module_md_zlib = zlib.compress(module_md_yaml.encode("utf-8"))
921 922 @property
923 - def buildroot_pkgs_list(self):
924 return self.buildroot_pkgs.split()
925 926 @property
927 - def repos_list(self):
928 return self.repos.split()
929 930 @property
931 - def comps(self):
932 if self.comps_zlib: 933 return zlib.decompress(self.comps_zlib).decode("utf-8")
934 935 @property
936 - def module_md(self):
937 if self.module_md_zlib: 938 return zlib.decompress(self.module_md_zlib).decode("utf-8")
939 940 @property
941 - def comps_len(self):
942 if self.comps_zlib: 943 return len(zlib.decompress(self.comps_zlib)) 944 else: 945 return 0
946 947 @property
948 - def module_md_len(self):
949 if self.module_md_zlib: 950 return len(zlib.decompress(self.module_md_zlib)) 951 else: 952 return 0
953 954 @property
955 - def name(self):
956 return self.mock_chroot.name
957 958 @property
959 - def is_active(self):
960 return self.mock_chroot.is_active
961
962 - def to_dict(self):
963 options = {"__columns_only__": [ 964 "buildroot_pkgs", "repos", "comps_name", "copr_id" 965 ]} 966 d = super(CoprChroot, self).to_dict(options=options) 967 d["mock_chroot"] = self.mock_chroot.name 968 return d
969
970 971 -class BuildChroot(db.Model, helpers.Serializer):
972 973 """ 974 Representation of Build<->MockChroot relation 975 """ 976 977 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"), 978 primary_key=True) 979 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds")) 980 build_id = db.Column(db.Integer, db.ForeignKey("build.id"), 981 primary_key=True) 982 build = db.relationship("Build", backref=db.backref("build_chroots")) 983 git_hash = db.Column(db.String(40)) 984 status = db.Column(db.Integer, default=StatusEnum("importing")) 985 986 started_on = db.Column(db.Integer) 987 ended_on = db.Column(db.Integer, index=True) 988 989 priority = db.Column(db.BigInteger, default=0, server_default="0", nullable=False) 990 991 build_requires = db.Column(db.Text) 992 993 @property
994 - def name(self):
995 """ 996 Textual representation of name of this chroot 997 """ 998 999 return self.mock_chroot.name
1000 1001 @property
1002 - def state(self):
1003 """ 1004 Return text representation of status of this build chroot 1005 """ 1006 1007 if self.status is not None: 1008 return StatusEnum(self.status) 1009 1010 return "unknown"
1011 1012 @property
1013 - def task_id(self):
1014 return "{}-{}".format(self.build_id, self.name)
1015 1016 @property
1017 - def dist_git_url(self):
1018 if app.config["DIST_GIT_URL"]: 1019 if self.state == "forked": 1020 coprname = self.build.copr.forked_from.full_name 1021 else: 1022 coprname = self.build.copr.full_name 1023 return "{}/{}/{}.git/commit/?id={}".format(app.config["DIST_GIT_URL"], 1024 coprname, 1025 self.build.package.name, 1026 self.git_hash) 1027 return None
1028 1029 @property
1030 - def result_dir_url(self):
1031 return urljoin(app.config["BACKEND_BASE_URL"], 1032 os.path.join("results", self.result_dir, "") 1033 )
1034 1035 @property
1036 - def result_dir(self):
1037 # hide changes occurred after migration to dist-git 1038 # if build has defined dist-git, it means that new schema should be used 1039 # otherwise use older structure 1040 1041 # old: results/valtri/ruby/fedora-rawhide-x86_64/rubygem-aws-sdk-resources-2.1.11-1.fc24/ 1042 # new: results/asamalik/rh-perl520/epel-7-x86_64/00000187-rh-perl520/ 1043 1044 parts = [self.build.copr.owner_name] 1045 1046 parts.extend([ 1047 self.build.copr.name, 1048 self.name, 1049 ]) 1050 if self.git_hash is not None and self.build.package: 1051 parts.append(self.build.result_dir_name) 1052 else: 1053 parts.append(self.build.src_pkg_name) 1054 1055 return os.path.join(*parts)
1056
1057 - def __str__(self):
1058 return "<BuildChroot: {}>".format(self.to_dict())
1059
1060 1061 -class LegalFlag(db.Model, helpers.Serializer):
1062 id = db.Column(db.Integer, primary_key=True) 1063 # message from user who raised the flag (what he thinks is wrong) 1064 raise_message = db.Column(db.Text) 1065 # time of raising the flag as returned by int(time.time()) 1066 raised_on = db.Column(db.Integer) 1067 # time of resolving the flag by admin as returned by int(time.time()) 1068 resolved_on = db.Column(db.Integer) 1069 1070 # relations 1071 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True) 1072 # cascade="all" means that we want to keep these even if copr is deleted 1073 copr = db.relationship( 1074 "Copr", backref=db.backref("legal_flags", cascade="all")) 1075 # user who reported the problem 1076 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id")) 1077 reporter = db.relationship("User", 1078 backref=db.backref("legal_flags_raised"), 1079 foreign_keys=[reporter_id], 1080 primaryjoin="LegalFlag.reporter_id==User.id") 1081 # admin who resolved the problem 1082 resolver_id = db.Column( 1083 db.Integer, db.ForeignKey("user.id"), nullable=True) 1084 resolver = db.relationship("User", 1085 backref=db.backref("legal_flags_resolved"), 1086 foreign_keys=[resolver_id], 1087 primaryjoin="LegalFlag.resolver_id==User.id")
1088
1089 1090 -class Action(db.Model, helpers.Serializer):
1091 1092 """ 1093 Representation of a custom action that needs 1094 backends cooperation/admin attention/... 1095 """ 1096 1097 id = db.Column(db.Integer, primary_key=True) 1098 # delete, rename, ...; see ActionTypeEnum 1099 action_type = db.Column(db.Integer, nullable=False) 1100 # copr, ...; downcase name of class of modified object 1101 object_type = db.Column(db.String(20)) 1102 # id of the modified object 1103 object_id = db.Column(db.Integer) 1104 # old and new values of the changed property 1105 old_value = db.Column(db.String(255)) 1106 new_value = db.Column(db.String(255)) 1107 # additional data 1108 data = db.Column(db.Text) 1109 # result of the action, see helpers.BackendResultEnum 1110 result = db.Column( 1111 db.Integer, default=helpers.BackendResultEnum("waiting")) 1112 # optional message from the backend/whatever 1113 message = db.Column(db.Text) 1114 # time created as returned by int(time.time()) 1115 created_on = db.Column(db.Integer) 1116 # time ended as returned by int(time.time()) 1117 ended_on = db.Column(db.Integer) 1118
1119 - def __str__(self):
1120 return self.__unicode__()
1121
1122 - def __unicode__(self):
1123 if self.action_type == ActionTypeEnum("delete"): 1124 return "Deleting {0} {1}".format(self.object_type, self.old_value) 1125 elif self.action_type == ActionTypeEnum("rename"): 1126 return "Renaming {0} from {1} to {2}.".format(self.object_type, 1127 self.old_value, 1128 self.new_value) 1129 elif self.action_type == ActionTypeEnum("legal-flag"): 1130 return "Legal flag on copr {0}.".format(self.old_value) 1131 1132 return "Action {0} on {1}, old value: {2}, new value: {3}.".format( 1133 self.action_type, self.object_type, self.old_value, self.new_value)
1134
1135 - def to_dict(self, **kwargs):
1136 d = super(Action, self).to_dict() 1137 if d.get("object_type") == "module": 1138 module = Module.query.filter(Module.id == d["object_id"]).first() 1139 data = json.loads(d["data"]) 1140 data.update({ 1141 "projectname": module.copr.name, 1142 "ownername": module.copr.owner_name, 1143 "modulemd_b64": module.yaml_b64, 1144 }) 1145 d["data"] = json.dumps(data) 1146 return d
1147
1148 1149 -class Krb5Login(db.Model, helpers.Serializer):
1150 """ 1151 Represents additional user information for kerberos authentication. 1152 """ 1153 1154 __tablename__ = "krb5_login" 1155 1156 # FK to User table 1157 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) 1158 1159 # 'string' from 'copr.conf' from KRB5_LOGIN[string] 1160 config_name = db.Column(db.String(30), nullable=False, primary_key=True) 1161 1162 # krb's primary, i.e. 'username' from 'username@EXAMPLE.COM' 1163 primary = db.Column(db.String(80), nullable=False, primary_key=True) 1164 1165 user = db.relationship("User", backref=db.backref("krb5_logins"))
1166
1167 1168 -class CounterStat(db.Model, helpers.Serializer):
1169 """ 1170 Generic store for simple statistics. 1171 """ 1172 1173 name = db.Column(db.String(127), primary_key=True) 1174 counter_type = db.Column(db.String(30)) 1175 1176 counter = db.Column(db.Integer, default=0, server_default="0")
1177
1178 1179 -class Group(db.Model, helpers.Serializer):
1180 """ 1181 Represents FAS groups and their aliases in Copr 1182 """ 1183 id = db.Column(db.Integer, primary_key=True) 1184 name = db.Column(db.String(127)) 1185 1186 # TODO: add unique=True 1187 fas_name = db.Column(db.String(127)) 1188 1189 @property
1190 - def at_name(self):
1191 return u"@{}".format(self.name)
1192
1193 - def __str__(self):
1194 return self.__unicode__()
1195
1196 - def __unicode__(self):
1197 return "{} (fas: {})".format(self.name, self.fas_name)
1198
1199 1200 -class Batch(db.Model):
1201 id = db.Column(db.Integer, primary_key=True)
1202
1203 1204 -class Module(db.Model, helpers.Serializer):
1205 id = db.Column(db.Integer, primary_key=True) 1206 name = db.Column(db.String(100), nullable=False) 1207 stream = db.Column(db.String(100), nullable=False) 1208 version = db.Column(db.Integer, nullable=False) 1209 summary = db.Column(db.String(100), nullable=False) 1210 description = db.Column(db.Text) 1211 created_on = db.Column(db.Integer, nullable=True) 1212 1213 # When someone submits YAML (not generate one on the copr modules page), we might want to use that exact file. 1214 # Yaml produced by deconstructing into pieces and constructed back can look differently, 1215 # which is not desirable (Imo) 1216 # 1217 # Also if there are fields which are not covered by this model, we will be able to add them in the future 1218 # and fill them with data from this blob 1219 yaml_b64 = db.Column(db.Text) 1220 1221 # relations 1222 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 1223 copr = db.relationship("Copr", backref=db.backref("modules")) 1224 1225 @property
1226 - def yaml(self):
1227 return base64.b64decode(self.yaml_b64)
1228 1229 @property
1230 - def modulemd(self):
1231 mmd = modulemd.ModuleMetadata() 1232 mmd.loads(self.yaml) 1233 return mmd
1234 1235 @property
1236 - def nsv(self):
1237 return "-".join([self.name, self.stream, str(self.version)])
1238 1239 @property
1240 - def full_name(self):
1241 return "{}/{}".format(self.copr.full_name, self.nsv)
1242 1243 @property
1244 - def action(self):
1245 return Action.query.filter(Action.object_type == "module").filter(Action.object_id == self.id).first()
1246 1247 @property
1248 - def state(self):
1249 """ 1250 Return text representation of status of this build 1251 """ 1252 if self.action is not None: 1253 return helpers.ModuleStatusEnum(self.action.result) 1254 return "-"
1255
1256 - def repo_url(self, arch):
1257 # @TODO Use custom chroot instead of fedora-24 1258 # @TODO Get rid of OS name from module path, see how koji does it 1259 # https://kojipkgs.stg.fedoraproject.org/repos/module-base-runtime-0.25-9/latest/x86_64/toplink/packages/module-build-macros/0.1/ 1260 module_dir = "fedora-24-{}+{}-{}-{}".format(arch, self.name, self.stream, self.version) 1261 return "/".join([self.copr.repo_url, "modules", module_dir, "latest", arch])
1262