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

Source Code for Module coprs.models

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