class Build(ProjectBoundMixin, StandardAttributes, db.Model): """ A single build linked to a source. Each Build contains many Jobs. """ source_id = db.Column( GUID, db.ForeignKey('source.id', ondelete='CASCADE'), nullable=False, index=True ) number = db.Column(db.Integer, nullable=False) label = db.Column(db.String, nullable=False) status = db.Column(Enum(Status), nullable=False, default=Status.unknown) result = db.Column(Enum(Result), nullable=False, default=Result.unknown) date_started = db.Column(db.TIMESTAMP(timezone=True), nullable=True) date_finished = db.Column(db.TIMESTAMP(timezone=True), nullable=True) data = db.Column(JSONEncodedDict, nullable=True) provider = db.Column(db.String, nullable=True) external_id = db.Column(db.String(64), nullable=True) source = db.relationship('Source') stats = db.relationship( 'ItemStat', foreign_keys='[ItemStat.item_id]', primaryjoin='ItemStat.item_id == Build.id', lazy='subquery', viewonly=True, uselist=True ) __tablename__ = 'build' __table_args__ = ( db.UniqueConstraint('project_id', 'number', name='unq_build_number'), db.UniqueConstraint('project_id', 'provider', 'external_id', name='unq_build_provider') ) __repr__ = model_repr('number', 'status', 'result')
class FailureReason(RepositoryBoundMixin, StandardAttributes, db.Model): class Reason(enum.Enum): failing_tests = "failing_tests" missing_tests = "missing_tests" no_jobs = "no_jobs" unresolvable_ref = "unresolvable_ref" build_id = db.Column(GUID, db.ForeignKey("build.id", ondelete="CASCADE"), nullable=True) job_id = db.Column(GUID, db.ForeignKey("job.id", ondelete="CASCADE"), nullable=True) reason = db.Column(StrEnum(Reason), nullable=False) build = db.relationship("Build") job = db.relationship("Job") __tablename__ = "failurereason" __table_args__ = ( db.UniqueConstraint("build_id", "job_id", "reason", name="unq_failurereason_key"), db.Index( "unq_failurereason_buildonly", build_id, reason, unique=True, postgresql_where=job_id.is_(None), ), ) __repr__ = model_repr("reason")
class Artifact(RepositoryBoundMixin, StandardAttributes, db.Model): job_id = db.Column(GUID, db.ForeignKey('job.id', ondelete='CASCADE'), nullable=False) testcase_id = db.Column(GUID, db.ForeignKey('testcase.id', ondelete='CASCADE'), nullable=True) name = db.Column(db.String(length=256), nullable=False) type = db.Column(Enum(ArtifactType), default=ArtifactType.UNKNOWN, nullable=False, server_default='0') file = db.Column( File(**ARTIFACT_STORAGE_OPTIONS), nullable=False, # TODO(dcramer): this is super hacky but not sure a better way to # do it with SQLAlchemy default=lambda: FileData({}, ARTIFACT_STORAGE_OPTIONS)) job = db.relationship('Job', innerjoin=True, uselist=False) testcase = db.relationship('TestCase', uselist=False) __tablename__ = 'artifact' def save_base64_content(self, base64): content = b64decode(base64) self.file.save( BytesIO(content), '{0}/{1}_{2}'.format(self.job_id.hex, self.id.hex, self.name))
class Hook(RepositoryBoundMixin, StandardAttributes, db.Model): """ An webhook bound to a single respository. """ token = db.Column( db.LargeBinary(64), default=lambda: Hook.generate_token(), unique=True, nullable=False, ) provider = db.Column(db.String(64), nullable=False) # the provider controls the format of data config = db.Column(JSONEncodedDict, nullable=True) __tablename__ = "hook" __repr__ = model_repr("repository_id") @classmethod def generate_token(cls): return token_bytes(64) def get_signature(self): return hmac.new(key=self.token, msg=self.repository_id.bytes, digestmod=sha256).hexdigest() def is_valid_signature(self, signature): return compare_digest(self.get_signature(), signature)
class ChangeRequest(RepositoryBoundMixin, StandardAttributes, db.Model): number = db.Column(db.Integer, nullable=False) # the parent revision is our base commit that this change request applies to parent_revision_sha = db.Column(db.String(40), nullable=False) # for branch-style change requests (e.g. GitHub Pull Requests) we capture # the 'current revision' in addition to the 'parent revision' head_revision_sha = db.Column(db.String(40), nullable=True) message = db.Column(db.String, nullable=False) author_id = db.Column(GUID, db.ForeignKey("author.id"), index=True, nullable=True) provider = db.Column(db.String, nullable=True) external_id = db.Column(db.String(64), nullable=True) url = db.Column(db.String, nullable=True) data = db.Column(JSONEncodedDict, nullable=True) date_updated = db.Column(db.TIMESTAMP(timezone=True), nullable=True, onupdate=timezone.now) head_revision = db.relationship( "Revision", foreign_keys= "[ChangeRequest.repository_id, ChangeRequest.head_revision_sha]", viewonly=True, ) parent_revision = db.relationship( "Revision", foreign_keys= "[ChangeRequest.repository_id, ChangeRequest.parent_revision_sha]", viewonly=True, ) author = db.relationship("Author") __tablename__ = "change_request" __table_args__ = ( db.ForeignKeyConstraint( ("repository_id", "parent_revision_sha"), ("revision.repository_id", "revision.sha"), ), db.ForeignKeyConstraint( ("repository_id", "head_revision_sha"), ("revision.repository_id", "revision.sha"), ), db.Index("idx_cr_parent_revision", "repository_id", "parent_revision_sha"), db.Index("idx_cr_head_revision", "repository_id", "head_revision_sha"), db.UniqueConstraint("repository_id", "number", name="unq_cr_number"), db.UniqueConstraint("repository_id", "provider", "external_id", name="unq_cr_provider"), ) __repr__ = model_repr("repository_id", "parent_revision_sha") @property def subject(self): return self.message.splitlines()[0]
class OrganizationAccess(db.Model): organization_id = db.Column(GUID, db.ForeignKey('organization.id'), primary_key=True) user_id = db.Column(GUID, db.ForeignKey('user.id'), primary_key=True) organization = db.relationship('Organization', innerjoin=True, uselist=False) user = db.relationship('User', innerjoin=True, uselist=False) __tablename__ = 'organization_access' __repr__ = model_repr('organization_id', 'user_id')
class RepositoryAccess(db.Model): repository_id = db.Column(GUID, db.ForeignKey('repository.id'), primary_key=True) user_id = db.Column(GUID, db.ForeignKey('user.id'), primary_key=True) repository = db.relationship('Repository', innerjoin=True, uselist=False) user = db.relationship('User', innerjoin=True, uselist=False) __tablename__ = 'repository_access' __repr__ = model_repr('repository_id', 'user_id')
class ItemStat(db.Model): id = db.Column(GUID, primary_key=True, default=GUID.default_value) item_id = db.Column(GUID, nullable=False) name = db.Column(db.String(64), nullable=False) value = db.Column(db.Integer, nullable=False) __tablename__ = "itemstat" __table_args__ = (db.UniqueConstraint("item_id", "name", name="unq_itemstat_name"),) __repr__ = model_repr("item_id", "name")
class ApiTokenRepositoryAccess(db.Model): repository_id = db.Column(GUID, db.ForeignKey('repository.id'), primary_key=True) api_token_id = db.Column(GUID, db.ForeignKey('api_token.id'), primary_key=True) repository = db.relationship('Repository', innerjoin=True, uselist=False) api_token = db.relationship('ApiToken', innerjoin=True, uselist=False) __tablename__ = 'api_token_repository_access' __repr__ = model_repr('repository_id', 'api_token_id')
class ItemSequence(db.Model): parent_id = db.Column(GUID, nullable=False, primary_key=True) value = db.Column(db.Integer, default=0, server_default="0", nullable=False, primary_key=True) __tablename__ = "itemsequence" __repr__ = model_repr("parent_id", "value")
class ItemSequence(db.Model): parent_id = db.Column(GUID, nullable=False, primary_key=True) value = db.Column(db.Integer, default=0, server_default='0', nullable=False, primary_key=True) __tablename__ = 'itemsequence' __repr__ = model_repr('parent_id', 'value')
class Source(RepositoryBoundMixin, StandardAttributes, db.Model): """ A source represents the canonical parameters that a build is running against. """ patch_id = db.Column(GUID, db.ForeignKey("patch.id", ondelete="CASCADE"), unique=True, nullable=True) revision_sha = db.Column(db.String(40), nullable=False) data = db.Column(JSONEncodedDict, nullable=True) author_id = db.Column(GUID, db.ForeignKey("author.id", ondelete="SET NULL"), index=True, nullable=True) author = db.relationship("Author") patch = db.relationship("Patch") revision = db.relationship( "Revision", foreign_keys="[Source.repository_id, Source.revision_sha]", viewonly=True, ) __tablename__ = "source" __table_args__ = ( db.ForeignKeyConstraint( ("repository_id", "revision_sha"), ("revision.repository_id", "revision.sha"), ), db.Index("idx_source_repo_sha", "repository_id", "revision_sha"), db.UniqueConstraint("repository_id", "revision_sha", "patch_id", name="unq_source_revision"), ) __repr__ = model_repr("repository_id", "revision_sha", "patch_id") def is_commit(self): return self.patch_id is None and self.revision_sha def generate_diff(self): if self.patch: return self.patch.diff try: vcs = self.repository.get_vcs() except UnknownRepositoryBackend: return None try: return vcs.export(self.revision_sha) except Exception: # TODO pass
class Artifact(RepositoryBoundMixin, StandardAttributes, db.Model): job_id = db.Column(GUID, db.ForeignKey( 'job.id', ondelete='CASCADE'), nullable=False) testcase_id = db.Column(GUID, db.ForeignKey( 'testcase.id', ondelete='CASCADE'), nullable=True) name = db.Column(db.String(length=256), nullable=False) type = db.Column(db.String(length=64), nullable=True) file = db.Column( File(path='artifacts'), nullable=False, # TODO(dcramer): this is super hacky but not sure a better way to # do it with SQLAlchemy default=lambda: FileData({}, default_path='artifacts') ) status = db.Column(Enum(Status), nullable=False, default=Status.unknown) date_started = db.Column(db.TIMESTAMP(timezone=True), nullable=True) date_updated = db.Column(db.TIMESTAMP( timezone=True), nullable=True, onupdate=timezone.now) date_finished = db.Column(db.TIMESTAMP(timezone=True), nullable=True) job = db.relationship('Job', innerjoin=True, uselist=False) testcase = db.relationship('TestCase', uselist=False) __tablename__ = 'artifact' def save_base64_content(self, base64): content = b64decode(base64) self.file.save( BytesIO( content), '{0}/{1}_{2}'.format(self.job_id.hex, self.id.hex, self.name) )
class ItemOption(db.Model): id = db.Column(GUID, primary_key=True, default=GUID.default_value) item_id = db.Column(GUID, nullable=False) name = db.Column(db.String(64), nullable=False) value = db.Column(db.Text, nullable=False) __tablename__ = 'itemoption' __table_args__ = (db.UniqueConstraint('item_id', 'name', name='unq_itemoption_name'), ) __repr__ = model_repr('item_id', 'name')
class RepositoryAccess(RepositoryMixin, db.Model): user_id = db.Column( GUID, db.ForeignKey("user.id", ondelete="CASCADE"), primary_key=True ) permission = db.Column( Enum(Permission), nullable=False, default=Permission.read, server_default="1" ) user = db.relationship("User", innerjoin=True, uselist=False) __tablename__ = "repository_access" __repr__ = model_repr("repository_id", "user_id")
class Bundle(RepositoryBoundMixin, StandardAttributes, db.Model): job_id = db.Column(GUID, db.ForeignKey('job.id', ondelete='CASCADE'), nullable=False) name = db.Column(db.Text, nullable=False) job = db.relationship('Job') assets = db.relationship('BundleAsset', lazy='subquery') __tablename__ = 'bundle' __table_args__ = (db.UniqueConstraint('job_id', 'name', name='unq_bundle'), ) __repr__ = model_repr('repository_id', 'job_id', 'name')
class Author(OrganizationBoundMixin, db.Model): """ The author of a source. Generally used for things like commit authors. This is different than User, which indicates a known authenticatable user. """ id = db.Column(GUID, primary_key=True, default=GUID.default_value) name = db.Column(db.String(128), nullable=False) email = db.Column(db.String(128), nullable=True) __tablename__ = 'author' __table_args__ = (db.UniqueConstraint('organization_id', 'email', name='unq_author_email'), ) __repr__ = model_repr('organization_id', 'name', 'email')
class Bundle(RepositoryBoundMixin, StandardAttributes, db.Model): job_id = db.Column(GUID, db.ForeignKey("job.id", ondelete="CASCADE"), nullable=False) name = db.Column(db.Text, nullable=False) job = db.relationship("Job") assets = db.relationship("BundleAsset", lazy="subquery") __tablename__ = "bundle" __table_args__ = (db.UniqueConstraint("job_id", "name", name="unq_bundle"), ) __repr__ = model_repr("repository_id", "job_id", "name")
class Revision(db.Model): # XXX(dcramer): the primary_key doesnt include repo_id at the moment, which is wrong, but # we need to deal w/ that in a followup change repository_id = db.Column( GUID, db.ForeignKey("repository.id", ondelete="CASCADE"), primary_key=True ) sha = db.Column(db.String(40), primary_key=True) author_id = db.Column( GUID, db.ForeignKey("author.id", ondelete="SET NULL"), index=True, nullable=True ) committer_id = db.Column( GUID, db.ForeignKey("author.id", ondelete="SET NULL"), index=True, nullable=True ) message = db.Column(db.Text, nullable=True) parents = db.Column(ARRAY(db.String(40)), nullable=True) # TODO: remove this column, we dont use it and its wrong branches = db.Column(ARRAY(db.String(128)), nullable=True) date_created = db.Column( db.TIMESTAMP(timezone=True), default=timezone.now, server_default=func.now(), nullable=False, ) date_committed = db.Column( db.TIMESTAMP(timezone=True), default=timezone.now, server_default=func.now(), nullable=False, ) authors = db.relationship( "Author", secondary=revision_author_table, backref="revisions" ) committer = db.relationship("Author", foreign_keys=[committer_id]) repository = db.relationship("Repository", foreign_keys=[repository_id]) query_class = RepositoryBoundQuery __tablename__ = "revision" # XXX(dcramer): the primary_key doesnt include repo_id at the moment, which is wrong, but # we need to deal w/ that in a followup change __table_args__ = (db.UniqueConstraint("repository_id", "sha", name="unq_revision"),) __repr__ = model_repr("repository_id", "sha", "subject") @property def subject(self): return self.message.splitlines()[0] def generate_diff(self): from zeus.vcs import vcs_client try: return vcs_client.export(self.repository_id, self.sha) except Exception: current_app.logger.exception("generate_diff failure")
class ApiTokenRepositoryAccess(db.Model): repository_id = db.Column( GUID, db.ForeignKey("repository.id", ondelete="CASCADE"), primary_key=True ) api_token_id = db.Column( GUID, db.ForeignKey("api_token.id", ondelete="CASCADE"), primary_key=True ) permission = db.Column(Enum(Permission), nullable=False, default=Permission.read) repository = db.relationship("Repository", innerjoin=True, uselist=False) api_token = db.relationship("ApiToken", innerjoin=True, uselist=False) __tablename__ = "api_token_repository_access" __repr__ = model_repr("repository_id", "api_token_id")
class Author(RepositoryBoundMixin, db.Model): """ The author of a source. Generally used for things like commit authors. This is different than User, which indicates a known authenticatable user. """ id = db.Column(GUID, primary_key=True, default=GUID.default_value) name = db.Column(db.String(128), nullable=False) email = db.Column(db.String(128), nullable=True) __tablename__ = "author" __table_args__ = (db.UniqueConstraint("repository_id", "email", name="unq_author_email"), ) __repr__ = model_repr("repository_id", "name", "email")
class Repository(OrganizationBoundMixin, StandardAttributes, db.Model): """ Represents a single repository. """ query_class = RepositoryAccessBoundQuery provider = db.Column(StrEnum(RepositoryProvider), default=RepositoryProvider.native, nullable=False) external_id = db.Column(db.String(64)) url = db.Column(db.String(200), nullable=False) backend = db.Column(Enum(RepositoryBackend), default=RepositoryBackend.unknown, nullable=False) status = db.Column(Enum(RepositoryStatus), default=RepositoryStatus.inactive, nullable=False) data = db.Column(JSONEncodedDict, nullable=True) last_update = db.Column(db.TIMESTAMP(timezone=True), nullable=True) last_update_attempt = db.Column(db.TIMESTAMP(timezone=True), nullable=True) options = db.relationship( 'ItemOption', foreign_keys='[ItemOption.item_id]', primaryjoin='ItemOption.item_id == Repository.id', lazy='subquery', viewonly=True, uselist=True) __tablename__ = 'repository' __table_args__ = (db.UniqueConstraint('organization_id', 'provider', 'external_id', name='unq_external_id'), ) __repr__ = model_repr('url', 'provider', 'external_id') def get_vcs(self): from zeus.models import ItemOption from zeus.vcs.git import GitVcs options = dict( db.session.query(ItemOption.name, ItemOption.value).filter( ItemOption.item_id == self.id, ItemOption.name.in_([ 'auth.username', ]))) kwargs = { 'path': os.path.join(current_app.config['REPO_ROOT'], self.id.hex), 'url': self.url, 'username': options.get('auth.username'), } if self.backend == RepositoryBackend.git: return GitVcs(**kwargs) else: raise NotImplementedError('Invalid backend: {}'.format( self.backend))
class Source(RepositoryBoundMixin, StandardAttributes, db.Model): """ A source represents the canonical parameters that a build is running against. """ patch_id = db.Column(GUID, db.ForeignKey('patch.id'), unique=True, nullable=True) revision_sha = db.Column(db.String(40), nullable=False) data = db.Column(JSONEncodedDict, nullable=True) author_id = db.Column(GUID, db.ForeignKey('author.id'), index=True) author = db.relationship('Author') patch = db.relationship('Patch', ) revision = db.relationship( 'Revision', foreign_keys='[Source.repository_id, Source.revision_sha]', viewonly=True) __tablename__ = 'source' __table_args__ = ( db.ForeignKeyConstraint(('repository_id', 'revision_sha'), ('revision.repository_id', 'revision.sha')), db.Index('idx_source_repo_sha', 'repository_id', 'revision_sha'), db.UniqueConstraint( 'repository_id', 'revision_sha', 'patch_id', name='unq_source_revision', ), ) __repr__ = model_repr('repository_id', 'revision_sha', 'patch_id') def is_commit(self): return self.patch_id is None and self.revision_sha def generate_diff(self): if self.patch: return self.patch.diff vcs = self.repository.get_vcs() if vcs: try: return vcs.export(self.revision_sha) except Exception: pass return None
class FailureReason(RepositoryBoundMixin, StandardAttributes, db.Model): __tablename__ = "failurereason" __table_args__ = (db.UniqueConstraint("job_id", "reason", name="unq_failurereason_key"), ) class Reason(enum.Enum): failing_tests = "failing_tests" missing_tests = "missing_tests" job_id = db.Column(GUID, db.ForeignKey("job.id", ondelete="CASCADE"), nullable=False) reason = db.Column(StrEnum(Reason), nullable=False) job = db.relationship("Job")
class Patch(RepositoryBoundMixin, StandardAttributes, db.Model): parent_revision_sha = db.Column(db.String(40), nullable=False) diff = db.deferred(db.Column(db.Text, nullable=False)) parent_revision = db.relationship( 'Revision', foreign_keys='[Patch.repository_id, Patch.parent_revision_sha]', viewonly=True) __tablename__ = 'patch' __table_args__ = ( db.ForeignKeyConstraint(('repository_id', 'parent_revision_sha'), ('revision.repository_id', 'revision.sha')), db.Index('idx_repo_sha', 'repository_id', 'parent_revision_sha'), ) __repr__ = model_repr('repository_id', 'parent_revision_sha')
class FailureReason(RepositoryBoundMixin, StandardAttributes, db.Model): __tablename__ = 'failurereason' __table_args__ = (db.UniqueConstraint('job_id', 'reason', name='unq_failurereason_key'), ) class Reason(enum.Enum): failing_tests = 'failing_tests' missing_tests = 'missing_tests' job_id = db.Column(GUID, db.ForeignKey('job.id', ondelete="CASCADE"), nullable=False) reason = db.Column(StrEnum(Reason), nullable=False) job = db.relationship('Job')
def key(cls): return db.Column( db.String(64), default=lambda: ApiTokenMixin.generate_token(), unique=True, nullable=False, )
def date_created(cls): return db.Column( db.TIMESTAMP(timezone=True), default=timezone.now, server_default=func.now(), nullable=False, )
def repository_id(cls): return db.Column( GUID, db.ForeignKey("repository.id", ondelete="CASCADE"), nullable=False, index=True, )
class User(StandardAttributes, db.Model): """ Actors within Zeus. """ email = db.Column(db.String(128), unique=True, nullable=False) __tablename__ = 'user' __repr__ = model_repr('email')