Merge branch 'master' into delete-setup-page
This commit is contained in:
commit
cd6b0a6f46
29 changed files with 550 additions and 142 deletions
|
@ -1435,7 +1435,7 @@ class TagManifest(BaseModel):
|
|||
class TagManifestToManifest(BaseModel):
|
||||
""" NOTE: Only used for the duration of the migrations. """
|
||||
tag_manifest = ForeignKeyField(TagManifest, index=True, unique=True)
|
||||
manifest = ForeignKeyField(Manifest, index=True, unique=True)
|
||||
manifest = ForeignKeyField(Manifest, index=True)
|
||||
broken = BooleanField(index=True, default=False)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
"""Remove unique from TagManifestToManifest
|
||||
|
||||
Revision ID: 13411de1c0ff
|
||||
Revises: 654e6df88b71
|
||||
Create Date: 2018-08-19 23:30:24.969549
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '13411de1c0ff'
|
||||
down_revision = '654e6df88b71'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
def upgrade(tables, tester):
|
||||
# Note: Because of a restriction in MySQL, we cannot simply remove the index and re-add
|
||||
# it without the unique=False, nor can we simply alter the index. To make it work, we'd have to
|
||||
# remove the primary key on the field, so instead we simply drop the table entirely and
|
||||
# recreate it with the modified index. The backfill will re-fill this in.
|
||||
op.drop_table('tagmanifesttomanifest')
|
||||
|
||||
op.create_table('tagmanifesttomanifest',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('tag_manifest_id', sa.Integer(), nullable=False),
|
||||
sa.Column('manifest_id', sa.Integer(), nullable=False),
|
||||
sa.Column('broken', sa.Boolean(), nullable=False, server_default=sa.sql.expression.false()),
|
||||
sa.ForeignKeyConstraint(['manifest_id'], ['manifest.id'], name=op.f('fk_tagmanifesttomanifest_manifest_id_manifest')),
|
||||
sa.ForeignKeyConstraint(['tag_manifest_id'], ['tagmanifest.id'], name=op.f('fk_tagmanifesttomanifest_tag_manifest_id_tagmanifest')),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_tagmanifesttomanifest'))
|
||||
)
|
||||
op.create_index('tagmanifesttomanifest_broken', 'tagmanifesttomanifest', ['broken'], unique=False)
|
||||
op.create_index('tagmanifesttomanifest_manifest_id', 'tagmanifesttomanifest', ['manifest_id'], unique=False)
|
||||
op.create_index('tagmanifesttomanifest_tag_manifest_id', 'tagmanifesttomanifest', ['tag_manifest_id'], unique=True)
|
||||
|
||||
tester.populate_table('tagmanifesttomanifest', [
|
||||
('manifest_id', tester.TestDataType.Foreign('manifest')),
|
||||
('tag_manifest_id', tester.TestDataType.Foreign('tagmanifest')),
|
||||
])
|
||||
|
||||
|
||||
def downgrade(tables, tester):
|
||||
pass
|
|
@ -721,3 +721,30 @@ def change_tag_expiration(tag, expiration_date):
|
|||
.execute())
|
||||
|
||||
return (tag.lifetime_end_ts, result > 0)
|
||||
|
||||
|
||||
def find_matching_tag(repo_id, tag_names):
|
||||
""" Finds the most recently pushed alive tag in the repository with one of the given names,
|
||||
if any.
|
||||
"""
|
||||
try:
|
||||
return (_tag_alive(RepositoryTag
|
||||
.select()
|
||||
.where(RepositoryTag.repository == repo_id,
|
||||
RepositoryTag.name << list(tag_names))
|
||||
.order_by(RepositoryTag.lifetime_start_ts.desc()))
|
||||
.get())
|
||||
except RepositoryTag.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def get_most_recent_tag(repo_id):
|
||||
""" Returns the most recently pushed alive tag in the repository, or None if none. """
|
||||
try:
|
||||
return (_tag_alive(RepositoryTag
|
||||
.select()
|
||||
.where(RepositoryTag.repository == repo_id)
|
||||
.order_by(RepositoryTag.lifetime_start_ts.desc()))
|
||||
.get())
|
||||
except RepositoryTag.DoesNotExist:
|
||||
return None
|
||||
|
|
3
data/registry_model/__init__.py
Normal file
3
data/registry_model/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from data.registry_model.registry_pre_oci_model import pre_oci_model
|
||||
|
||||
registry_model = pre_oci_model
|
20
data/registry_model/datatypes.py
Normal file
20
data/registry_model/datatypes.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from collections import namedtuple
|
||||
|
||||
class RepositoryReference(object):
|
||||
""" RepositoryReference is a reference to a repository, passed to registry interface methods. """
|
||||
def __init__(self, repo_id):
|
||||
self.repo_id = repo_id
|
||||
|
||||
@classmethod
|
||||
def for_repo_obj(cls, repo_obj):
|
||||
return RepositoryReference(repo_obj.id)
|
||||
|
||||
|
||||
class Tag(namedtuple('Tag', ['id', 'name'])):
|
||||
""" Tag represents a tag in a repository, which points to a manifest or image. """
|
||||
@classmethod
|
||||
def for_repository_tag(cls, repository_tag):
|
||||
if repository_tag is None:
|
||||
return None
|
||||
|
||||
return Tag(id=repository_tag.id, name=repository_tag.name)
|
21
data/registry_model/interface.py
Normal file
21
data/registry_model/interface.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from abc import ABCMeta, abstractmethod
|
||||
from six import add_metaclass
|
||||
|
||||
@add_metaclass(ABCMeta)
|
||||
class RegistryDataInterface(object):
|
||||
""" Interface for code to work with the registry data model. The registry data model consists
|
||||
of all tables that store registry-specific information, such as Manifests, Blobs, Images,
|
||||
and Labels.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def find_matching_tag(self, repository_ref, tag_names):
|
||||
""" Finds an alive tag in the repository matching one of the given tag names and returns it
|
||||
or None if none.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_most_recent_tag(self, repository_ref):
|
||||
""" Returns the most recently pushed alive tag in the repository, if any. If none, returns
|
||||
None.
|
||||
"""
|
27
data/registry_model/registry_pre_oci_model.py
Normal file
27
data/registry_model/registry_pre_oci_model.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from data import model
|
||||
from data.registry_model.interface import RegistryDataInterface
|
||||
from data.registry_model.datatypes import Tag
|
||||
|
||||
|
||||
class PreOCIModel(RegistryDataInterface):
|
||||
"""
|
||||
PreOCIModel implements the data model for the registry API using a database schema
|
||||
before it was changed to support the OCI specification.
|
||||
"""
|
||||
|
||||
def find_matching_tag(self, repository_ref, tag_names):
|
||||
""" Finds an alive tag in the repository matching one of the given tag names and returns it
|
||||
or None if none.
|
||||
"""
|
||||
found_tag = model.tag.find_matching_tag(repository_ref.repo_id, tag_names)
|
||||
return Tag.for_repository_tag(found_tag)
|
||||
|
||||
def get_most_recent_tag(self, repository_ref):
|
||||
""" Returns the most recently pushed alive tag in the repository, if any. If none, returns
|
||||
None.
|
||||
"""
|
||||
found_tag = model.tag.get_most_recent_tag(repository_ref.repo_id)
|
||||
return Tag.for_repository_tag(found_tag)
|
||||
|
||||
|
||||
pre_oci_model = PreOCIModel()
|
41
data/registry_model/test/test_pre_oci_model.py
Normal file
41
data/registry_model/test/test_pre_oci_model.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
import pytest
|
||||
|
||||
from data import model
|
||||
from data.registry_model.registry_pre_oci_model import PreOCIModel
|
||||
from data.registry_model.datatypes import RepositoryReference
|
||||
from test.fixtures import *
|
||||
|
||||
@pytest.fixture()
|
||||
def pre_oci_model(initialized_db):
|
||||
return PreOCIModel()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('names, expected', [
|
||||
(['unknown'], None),
|
||||
(['latest'], {'latest'}),
|
||||
(['latest', 'prod'], {'latest', 'prod'}),
|
||||
(['latest', 'prod', 'another'], {'latest', 'prod'}),
|
||||
(['foo', 'prod'], {'prod'}),
|
||||
])
|
||||
def test_find_matching_tag(names, expected, pre_oci_model):
|
||||
repo = model.repository.get_repository('devtable', 'simple')
|
||||
repository_ref = RepositoryReference.for_repo_obj(repo)
|
||||
found = pre_oci_model.find_matching_tag(repository_ref, names)
|
||||
if expected is None:
|
||||
assert found is None
|
||||
else:
|
||||
assert found.name in expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('repo_namespace, repo_name, expected', [
|
||||
('devtable', 'simple', {'latest'}),
|
||||
('buynlarge', 'orgrepo', {'latest', 'prod'}),
|
||||
])
|
||||
def test_get_most_recent_tag(repo_namespace, repo_name, expected, pre_oci_model):
|
||||
repo = model.repository.get_repository(repo_namespace, repo_name)
|
||||
repository_ref = RepositoryReference.for_repo_obj(repo)
|
||||
found = pre_oci_model.get_most_recent_tag(repository_ref)
|
||||
if expected is None:
|
||||
assert found is None
|
||||
else:
|
||||
assert found.name in expected
|
Reference in a new issue