Merge branch 'master' into git

This commit is contained in:
Jimmy Zelinskie 2015-04-20 10:58:49 -04:00
commit 93cd459460
27 changed files with 873 additions and 343 deletions

View file

@ -299,7 +299,7 @@ class Repository(BaseModel):
# Therefore, we define our own deletion order here and use the dependency system to verify it.
ordered_dependencies = [RepositoryAuthorizedEmail, RepositoryTag, Image, LogEntry,
RepositoryBuild, RepositoryBuildTrigger, RepositoryNotification,
RepositoryPermission, AccessToken, Star]
RepositoryPermission, AccessToken, Star, RepositoryActionCount]
for query, fk in self.dependencies(search_nullable=True):
model = fk.model_class
@ -498,6 +498,7 @@ class RepositoryTag(BaseModel):
lifetime_start_ts = IntegerField(default=get_epoch_timestamp)
lifetime_end_ts = IntegerField(null=True, index=True)
hidden = BooleanField(default=False)
reversion = BooleanField(default=False)
class Meta:
database = db
@ -561,6 +562,20 @@ class LogEntry(BaseModel):
metadata_json = TextField(default='{}')
class RepositoryActionCount(BaseModel):
repository = ForeignKeyField(Repository, index=True)
count = IntegerField()
date = DateField(index=True)
class Meta:
database = db
read_slaves = (read_slave,)
indexes = (
# create a unique index on repository and date
(('repository', 'date'), True),
)
class OAuthApplication(BaseModel):
client_id = CharField(index=True, default=random_string_generator(length=20))
client_secret = CharField(default=random_string_generator(length=40))
@ -646,4 +661,4 @@ all_models = [User, Repository, Image, AccessToken, Role, RepositoryPermission,
ExternalNotificationEvent, ExternalNotificationMethod, RepositoryNotification,
RepositoryAuthorizedEmail, ImageStorageTransformation, DerivedImageStorage,
TeamMemberInvite, ImageStorageSignature, ImageStorageSignatureKind,
AccessTokenKind, Star]
AccessTokenKind, Star, RepositoryActionCount]

View file

@ -0,0 +1,29 @@
"""Add revert_tag log entry kind
Revision ID: 1c3decf6b9c4
Revises: 4ce2169efd3b
Create Date: 2015-04-16 17:14:11.154856
"""
# revision identifiers, used by Alembic.
revision = '1c3decf6b9c4'
down_revision = '4ce2169efd3b'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
op.bulk_insert(tables.logentrykind,
[
{'id': 47, 'name':'revert_tag'},
])
def downgrade(tables):
op.execute(
(tables.logentrykind.delete()
.where(tables.logentrykind.c.name == op.inline_literal('revert_tag')))
)

View file

@ -8,7 +8,7 @@ Create Date: 2015-03-19 14:23:52.604505
# revision identifiers, used by Alembic.
revision = '214350b6a8b1'
down_revision = '2b4dc0818a5e'
down_revision = '67eb43c778b'
from alembic import op
import sqlalchemy as sa

View file

@ -0,0 +1,36 @@
"""Add RepositoryActionCount table
Revision ID: 30c044b75632
Revises: 2b4dc0818a5e
Create Date: 2015-04-13 13:21:18.159602
"""
# revision identifiers, used by Alembic.
revision = '30c044b75632'
down_revision = '2b4dc0818a5e'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.create_table('repositoryactioncount',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('repository_id', sa.Integer(), nullable=False),
sa.Column('count', sa.Integer(), nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.ForeignKeyConstraint(['repository_id'], ['repository.id'], name=op.f('fk_repositoryactioncount_repository_id_repository')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_repositoryactioncount'))
)
op.create_index('repositoryactioncount_date', 'repositoryactioncount', ['date'], unique=False)
op.create_index('repositoryactioncount_repository_id', 'repositoryactioncount', ['repository_id'], unique=False)
op.create_index('repositoryactioncount_repository_id_date', 'repositoryactioncount', ['repository_id', 'date'], unique=True)
### end Alembic commands ###
def downgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.drop_table('repositoryactioncount')
### end Alembic commands ###

View file

@ -0,0 +1,26 @@
"""Add reversion column to the tags table
Revision ID: 4ce2169efd3b
Revises: 30c044b75632
Create Date: 2015-04-16 17:10:16.039835
"""
# revision identifiers, used by Alembic.
revision = '4ce2169efd3b'
down_revision = '30c044b75632'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.add_column('repositorytag', sa.Column('reversion', sa.Boolean(), nullable=False))
### end Alembic commands ###
def downgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.drop_column('repositorytag', 'reversion')
### end Alembic commands ###

View file

@ -0,0 +1,26 @@
"""add index for repository+datetime to logentry
Revision ID: 67eb43c778b
Revises: 1c3decf6b9c4
Create Date: 2015-04-19 16:00:39.126289
"""
# revision identifiers, used by Alembic.
revision = '67eb43c778b'
down_revision = '1c3decf6b9c4'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.create_index('logentry_repository_id_datetime', 'logentry', ['repository_id', 'datetime'], unique=False)
### end Alembic commands ###
def downgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.drop_index('logentry_repository_id_datetime', table_name='logentry')
### end Alembic commands ###

View file

@ -18,7 +18,7 @@ from data.database import (User, Repository, Image, AccessToken, Role, Repositor
DerivedImageStorage, ImageStorageTransformation, random_string_generator,
db, BUILD_PHASE, QuayUserField, ImageStorageSignature, QueueItem,
ImageStorageSignatureKind, validate_database_url, db_for_update,
AccessTokenKind, Star, get_epoch_timestamp)
AccessTokenKind, Star, get_epoch_timestamp, RepositoryActionCount)
from peewee import JOIN_LEFT_OUTER, fn
from util.validation import (validate_username, validate_email, validate_password,
INVALID_PASSWORD_MESSAGE)
@ -995,20 +995,19 @@ def get_sorted_matching_repositories(prefix, only_public, checker, limit=10):
""" Returns repositories matching the given prefix string and passing the given checker
function.
"""
last_week = datetime.now() - timedelta(weeks=1)
results = []
existing_ids = []
def get_search_results(search_clause, with_count):
def get_search_results(search_clause, with_count=False):
if len(results) >= limit:
return
selected = [Repository, Namespace]
select_items = [Repository, Namespace]
if with_count:
selected.append(fn.Count(LogEntry.id).alias('count'))
select_items.append(fn.Sum(RepositoryActionCount.count).alias('count'))
query = (Repository.select(*selected)
query = (Repository.select(*select_items)
.join(Namespace, JOIN_LEFT_OUTER, on=(Namespace.id == Repository.namespace_user))
.switch(Repository)
.where(search_clause)
@ -1021,9 +1020,10 @@ def get_sorted_matching_repositories(prefix, only_public, checker, limit=10):
query = query.where(~(Repository.id << existing_ids))
if with_count:
query = (query.join(LogEntry, JOIN_LEFT_OUTER)
.where(LogEntry.datetime >= last_week)
.order_by(fn.Count(LogEntry.id).desc()))
query = (query.switch(Repository)
.join(RepositoryActionCount)
.where(RepositoryActionCount.date >= last_week)
.order_by(fn.Sum(RepositoryActionCount.count).desc()))
for result in query:
if len(results) >= limit:
@ -1042,13 +1042,13 @@ def get_sorted_matching_repositories(prefix, only_public, checker, limit=10):
existing_ids.append(result.id)
# For performance reasons, we conduct the repo name and repo namespace searches on their
# own, and with and without counts on their own. This also affords us the ability to give
# higher precedence to repository names matching over namespaces, which is semantically correct.
get_search_results((Repository.name ** (prefix + '%')), with_count=True)
get_search_results((Repository.name ** (prefix + '%')), with_count=False)
# own. This also affords us the ability to give higher precedence to repository names matching
# over namespaces, which is semantically correct.
get_search_results(Repository.name ** (prefix + '%'), with_count=True)
get_search_results(Repository.name ** (prefix + '%'), with_count=False)
get_search_results((Namespace.username ** (prefix + '%')), with_count=True)
get_search_results((Namespace.username ** (prefix + '%')), with_count=False)
get_search_results(Namespace.username ** (prefix + '%'), with_count=True)
get_search_results(Namespace.username ** (prefix + '%'), with_count=False)
return results
@ -1762,13 +1762,18 @@ def _tag_alive(query, now_ts=None):
(RepositoryTag.lifetime_end_ts > now_ts))
def list_repository_tag_history(repository, limit=100):
def list_repository_tag_history(repository, limit=100, specific_tag=None):
query = (RepositoryTag
.select(RepositoryTag, Image)
.join(Image)
.where(RepositoryTag.repository == repository)
.where(RepositoryTag.hidden == False)
.order_by(RepositoryTag.lifetime_start_ts.desc())
.limit(limit))
if specific_tag:
query = query.where(RepositoryTag.name == specific_tag)
return query
@ -1990,7 +1995,7 @@ def get_parent_images(namespace_name, repository_name, image_obj):
def create_or_update_tag(namespace_name, repository_name, tag_name,
tag_docker_image_id):
tag_docker_image_id, reversion=False):
try:
repo = _get_repository(namespace_name, repository_name)
except Repository.DoesNotExist:
@ -2015,7 +2020,7 @@ def create_or_update_tag(namespace_name, repository_name, tag_name,
raise DataModelException('Invalid image with id: %s' % tag_docker_image_id)
return RepositoryTag.create(repository=repo, image=image, name=tag_name,
lifetime_start_ts=now_ts)
lifetime_start_ts=now_ts, reversion=reversion)
def delete_tag(namespace_name, repository_name, tag_name):
now_ts = get_epoch_timestamp()
@ -2823,3 +2828,22 @@ def repository_is_starred(user, repository):
return True
except Star.DoesNotExist:
return False
def revert_tag(repository, tag_name, docker_image_id):
""" Reverts a tag to a specific image ID. """
# Verify that the image ID already existed under this repository under the
# tag.
try:
(RepositoryTag.select()
.join(Image)
.where(RepositoryTag.repository == repository)
.where(RepositoryTag.name == tag_name)
.where(Image.docker_image_id == docker_image_id)
.get())
except RepositoryTag.DoesNotExist:
raise DataModelException('Cannot revert to unknown or invalid image')
return create_or_update_tag(repository.namespace_user.username, repository.name,
tag_name, docker_image_id, reversion=True)

View file

@ -1,7 +1,7 @@
from sqlalchemy import (Table, MetaData, Column, ForeignKey, Integer, String, Boolean, Text,
DateTime, BigInteger, Index)
DateTime, Date, BigInteger, Index)
from peewee import (PrimaryKeyField, CharField, BooleanField, DateTimeField, TextField,
ForeignKeyField, BigIntegerField, IntegerField)
ForeignKeyField, BigIntegerField, IntegerField, DateField)
OPTIONS_TO_COPY = [
@ -42,6 +42,8 @@ def gen_sqlalchemy_metadata(peewee_model_list):
alchemy_type = Boolean
elif isinstance(field, DateTimeField):
alchemy_type = DateTime
elif isinstance(field, DateField):
alchemy_type = Date
elif isinstance(field, TextField):
alchemy_type = Text
elif isinstance(field, ForeignKeyField):