Merge branch 'master' into git
This commit is contained in:
commit
93cd459460
27 changed files with 873 additions and 343 deletions
|
@ -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]
|
||||
|
|
|
@ -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')))
|
||||
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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 ###
|
|
@ -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 ###
|
|
@ -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 ###
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Reference in a new issue