import string import logging from random import SystemRandom from datetime import datetime from peewee import * from peewee import create_model_tables from app import app logger = logging.getLogger(__name__) db = app.config['DB_DRIVER'](app.config['DB_NAME'], **app.config['DB_CONNECTION_ARGS']) def close_db(exc): if not db.is_closed(): logger.debug('Disconnecting from database.') db.close() app.teardown_request(close_db) class BaseModel(Model): class Meta: database = db class User(BaseModel): username = CharField(unique=True, index=True) password_hash = CharField(null=True) email = CharField(unique=True, index=True) verified = BooleanField(default=False) stripe_id = CharField(index=True, null=True) organization = BooleanField(default=False, index=True) class TeamRole(BaseModel): name = CharField(index=True) class Team(BaseModel): name = CharField(index=True) organization = ForeignKeyField(User, index=True) role = ForeignKeyField(TeamRole) description = TextField(default='') class Meta: database = db indexes = ( # A team name must be unique within an organization (('name', 'organization'), True), ) class TeamMember(BaseModel): user = ForeignKeyField(User, index=True) team = ForeignKeyField(Team, index=True) class Meta: database = db indexes = ( # A user may belong to a team only once (('user', 'team'), True), ) class LoginService(BaseModel): name = CharField(unique=True, index=True) class FederatedLogin(BaseModel): user = ForeignKeyField(User, index=True) service = ForeignKeyField(LoginService, index=True) service_ident = CharField() class Meta: database = db indexes = ( # create a unique index on service and the local service id (('service', 'service_ident'), True), # a user may only have one federated login per service (('service', 'user'), True), ) class Visibility(BaseModel): name = CharField(index=True) class Repository(BaseModel): namespace = CharField() name = CharField() visibility = ForeignKeyField(Visibility) description = TextField(null=True) class Meta: database = db indexes = ( # create a unique index on namespace and name (('namespace', 'name'), True), ) class Role(BaseModel): name = CharField(index=True) class RepositoryPermission(BaseModel): team = ForeignKeyField(Team, index=True, null=True) user = ForeignKeyField(User, index=True, null=True) repository = ForeignKeyField(Repository, index=True) role = ForeignKeyField(Role) class Meta: database = db indexes = ( (('team', 'repository'), True), (('user', 'repository'), True), ) def random_string_generator(length=16): def random_string(): random = SystemRandom() return ''.join([random.choice(string.ascii_uppercase + string.digits) for x in range(length)]) return random_string class AccessToken(BaseModel): friendly_name = CharField(null=True) code = CharField(default=random_string_generator(length=64), unique=True, index=True) repository = ForeignKeyField(Repository) created = DateTimeField(default=datetime.now) role = ForeignKeyField(Role) temporary = BooleanField(default=True) class EmailConfirmation(BaseModel): code = CharField(default=random_string_generator(), unique=True, index=True) user = ForeignKeyField(User) pw_reset = BooleanField(default=False) email_confirm = BooleanField(default=False) created = DateTimeField(default=datetime.now) class Image(BaseModel): # This class is intentionally denormalized. Even though images are supposed # to be globally unique we can't treat them as such for permissions and # security reasons. So rather than Repository <-> Image being many to many # each image now belongs to exactly one repository. docker_image_id = CharField() checksum = CharField(null=True) created = DateTimeField(null=True) comment = TextField(null=True) repository = ForeignKeyField(Repository) # '/' separated list of ancestory ids, e.g. /1/2/6/7/10/ ancestors = CharField(index=True, default='/', max_length=64535) class Meta: database = db indexes = ( # we don't really want duplicates (('repository', 'docker_image_id'), True), ) class RepositoryTag(BaseModel): name = CharField() image = ForeignKeyField(Image) repository = ForeignKeyField(Repository) class Meta: database = db indexes = ( (('repository', 'name'), True), ) class RepositoryBuild(BaseModel): repository = ForeignKeyField(Repository) access_token = ForeignKeyField(AccessToken) resource_key = CharField() tag = CharField() build_node_id = IntegerField(null=True) phase = CharField(default='waiting') status_url = CharField(null=True) class QueueItem(BaseModel): queue_name = CharField(index=True) body = TextField() available_after = DateTimeField(default=datetime.now, index=True) available = BooleanField(default=True, index=True) processing_expires = DateTimeField(null=True, index=True) def initialize_db(): create_model_tables([User, Repository, Image, AccessToken, Role, RepositoryPermission, Visibility, RepositoryTag, EmailConfirmation, FederatedLogin, LoginService, QueueItem, RepositoryBuild, Team, TeamMember, TeamRole]) Role.create(name='admin') Role.create(name='write') Role.create(name='read') TeamRole.create(name='admin') TeamRole.create(name='creator') TeamRole.create(name='member') Visibility.create(name='public') Visibility.create(name='private') LoginService.create(name='github')