Another huge batch of registry v2 changes
Add patch support and resumeable sha Implement all actual registry methods Add a simple database generation option
This commit is contained in:
parent
5ba3521e67
commit
e1b3e9e6ae
29 changed files with 1095 additions and 430 deletions
|
@ -9,6 +9,10 @@ class BlobDoesNotExist(DataModelException):
|
|||
pass
|
||||
|
||||
|
||||
class InvalidBlobUpload(DataModelException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidEmailAddressException(DataModelException):
|
||||
pass
|
||||
|
||||
|
@ -65,6 +69,10 @@ class InvalidTeamMemberException(DataModelException):
|
|||
pass
|
||||
|
||||
|
||||
class InvalidManifestException(DataModelException):
|
||||
pass
|
||||
|
||||
|
||||
class TooManyLoginAttemptsException(Exception):
|
||||
def __init__(self, message, retry_after):
|
||||
super(TooManyLoginAttemptsException, self).__init__(message)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from uuid import uuid4
|
||||
|
||||
from data.model import tag, _basequery, BlobDoesNotExist, db_transaction
|
||||
from data.model import tag, _basequery, BlobDoesNotExist, InvalidBlobUpload, db_transaction
|
||||
from data.database import (Repository, Namespace, ImageStorage, Image, ImageStorageLocation,
|
||||
ImageStoragePlacement)
|
||||
ImageStoragePlacement, BlobUpload)
|
||||
|
||||
|
||||
def get_repo_blob_by_digest(namespace, repo_name, blob_digest):
|
||||
|
@ -15,9 +15,9 @@ def get_repo_blob_by_digest(namespace, repo_name, blob_digest):
|
|||
.join(ImageStorage)
|
||||
.join(Image)
|
||||
.join(Repository)
|
||||
.join(Namespace)
|
||||
.join(Namespace, on=(Namespace.id == Repository.namespace_user))
|
||||
.where(Repository.name == repo_name, Namespace.username == namespace,
|
||||
ImageStorage.checksum == blob_digest))
|
||||
ImageStorage.checksum == blob_digest, ImageStorage.uploading == False))
|
||||
if not placements:
|
||||
raise BlobDoesNotExist('Blob does not exist with digest: {0}'.format(blob_digest))
|
||||
|
||||
|
@ -26,24 +26,45 @@ def get_repo_blob_by_digest(namespace, repo_name, blob_digest):
|
|||
|
||||
return found
|
||||
|
||||
def store_blob_record_and_temp_link(namespace, repo_name, blob_digest, location_name,
|
||||
|
||||
def store_blob_record_and_temp_link(namespace, repo_name, blob_digest, location_obj,
|
||||
link_expiration_s):
|
||||
""" Store a record of the blob and temporarily link it to the specified repository.
|
||||
"""
|
||||
random_image_name = str(uuid4())
|
||||
with db_transaction:
|
||||
with db_transaction():
|
||||
repo = _basequery.get_existing_repository(namespace, repo_name)
|
||||
|
||||
try:
|
||||
storage = ImageStorage.get(checksum=blob_digest)
|
||||
location = ImageStorageLocation.get(name=location_name)
|
||||
ImageStoragePlacement.get(storage=storage, location=location)
|
||||
ImageStoragePlacement.get(storage=storage, location=location_obj)
|
||||
except ImageStorage.DoesNotExist:
|
||||
storage = ImageStorage.create(checksum=blob_digest)
|
||||
storage = ImageStorage.create(checksum=blob_digest, uploading=False)
|
||||
ImageStoragePlacement.create(storage=storage, location=location_obj)
|
||||
except ImageStoragePlacement.DoesNotExist:
|
||||
ImageStoragePlacement.create(storage=storage, location=location)
|
||||
ImageStoragePlacement.create(storage=storage, location=location_obj)
|
||||
|
||||
# Create a temporary link into the repository, to be replaced by the v1 metadata later
|
||||
# and create a temporary tag to reference it
|
||||
image = Image.create(storage=storage, docker_image_id=random_image_name, repository=repo)
|
||||
tag.create_temporary_hidden_tag(repo, image, link_expiration_s)
|
||||
|
||||
|
||||
def get_blob_upload(namespace, repo_name, upload_uuid):
|
||||
""" Load the upload which is already in progress.
|
||||
"""
|
||||
try:
|
||||
return (BlobUpload
|
||||
.select()
|
||||
.join(Repository)
|
||||
.join(Namespace, on=(Namespace.id == Repository.namespace_user))
|
||||
.where(Repository.name == repo_name, Namespace.username == namespace,
|
||||
BlobUpload.uuid == upload_uuid)
|
||||
.get())
|
||||
except BlobUpload.DoesNotExist:
|
||||
raise InvalidBlobUpload()
|
||||
|
||||
|
||||
def initiate_upload(namespace, repo_name, uuid, location_name):
|
||||
repo = _basequery.get_existing_repository(namespace, repo_name)
|
||||
location = ImageStorageLocation.get(name=location_name)
|
||||
return BlobUpload.create(repository=repo, location=location, uuid=uuid)
|
||||
|
|
|
@ -4,7 +4,8 @@ import dateutil.parser
|
|||
from peewee import JOIN_LEFT_OUTER, fn
|
||||
from datetime import datetime
|
||||
|
||||
from data.model import DataModelException, db_transaction, _basequery, storage
|
||||
from data.model import (DataModelException, db_transaction, _basequery, storage,
|
||||
InvalidImageException)
|
||||
from data.database import (Image, Repository, ImageStoragePlacement, Namespace, ImageStorage,
|
||||
ImageStorageLocation, RepositoryPermission, db_for_update)
|
||||
|
||||
|
@ -247,7 +248,7 @@ def find_create_or_link_image(docker_image_id, repo_obj, username, translations,
|
|||
return repo_image
|
||||
|
||||
logger.debug('Creating new storage for docker id: %s', docker_image_id)
|
||||
new_storage = storage.create_storage(preferred_location)
|
||||
new_storage = storage.create_v1_storage(preferred_location)
|
||||
|
||||
return Image.create(docker_image_id=docker_image_id,
|
||||
repository=repo_obj, storage=new_storage,
|
||||
|
@ -255,7 +256,7 @@ def find_create_or_link_image(docker_image_id, repo_obj, username, translations,
|
|||
|
||||
|
||||
def set_image_metadata(docker_image_id, namespace_name, repository_name, created_date_str, comment,
|
||||
command, parent=None):
|
||||
command, v1_json_metadata, parent=None):
|
||||
with db_transaction():
|
||||
query = (Image
|
||||
.select(Image, ImageStorage)
|
||||
|
@ -273,7 +274,7 @@ def set_image_metadata(docker_image_id, namespace_name, repository_name, created
|
|||
|
||||
# We cleanup any old checksum in case it's a retry after a fail
|
||||
fetched.storage.checksum = None
|
||||
fetched.storage.created = datetime.now()
|
||||
fetched.created = datetime.now()
|
||||
|
||||
if created_date_str is not None:
|
||||
try:
|
||||
|
@ -282,14 +283,14 @@ def set_image_metadata(docker_image_id, namespace_name, repository_name, created
|
|||
# parse raises different exceptions, so we cannot use a specific kind of handler here.
|
||||
pass
|
||||
|
||||
fetched.storage.comment = comment
|
||||
fetched.storage.command = command
|
||||
fetched.comment = comment
|
||||
fetched.command = command
|
||||
fetched.v1_json_metadata = v1_json_metadata
|
||||
|
||||
if parent:
|
||||
fetched.ancestors = '%s%s/' % (parent.ancestors, parent.id)
|
||||
|
||||
fetched.save()
|
||||
fetched.storage.save()
|
||||
return fetched
|
||||
|
||||
|
||||
|
@ -334,8 +335,65 @@ def set_image_size(docker_image_id, namespace_name, repository_name, image_size,
|
|||
return image
|
||||
|
||||
|
||||
def get_image(repo, dockerfile_id):
|
||||
def get_image(repo, docker_image_id):
|
||||
try:
|
||||
return Image.get(Image.docker_image_id == dockerfile_id, Image.repository == repo)
|
||||
return Image.get(Image.docker_image_id == docker_image_id, Image.repository == repo)
|
||||
except Image.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def get_repo_image_by_storage_checksum(namespace, repository_name, storage_checksum):
|
||||
try:
|
||||
return (Image
|
||||
.select()
|
||||
.join(ImageStorage)
|
||||
.switch(Image)
|
||||
.join(Repository)
|
||||
.join(Namespace, on=(Namespace.id == Repository.namespace_user))
|
||||
.where(Repository.name == repository_name, Namespace.username == namespace,
|
||||
ImageStorage.checksum == storage_checksum, ImageStorage.uploading == False)
|
||||
.get())
|
||||
except Image.DoesNotExist:
|
||||
msg = 'Image with storage checksum {0} does not exist in repo {1}/{2}'.format(storage_checksum,
|
||||
namespace,
|
||||
repository_name)
|
||||
raise InvalidImageException(msg)
|
||||
|
||||
|
||||
def synthesize_v1_image(namespace, repository_name, storage_checksum, docker_image_id,
|
||||
created_date_str, comment, command, v1_json_metadata, parent_docker_id):
|
||||
""" Find an existing image with this docker image id, and if none exists, write one with the
|
||||
specified metadata.
|
||||
"""
|
||||
|
||||
repo = _basequery.get_existing_repository(namespace, repository_name)
|
||||
# Sometimes the manifest may reference an image that already exists
|
||||
|
||||
found = get_image(repo, docker_image_id)
|
||||
if found is not None:
|
||||
# The image already exists, nothing to do
|
||||
return found
|
||||
|
||||
the_bits = storage.get_repo_storage_by_checksum(namespace, repository_name, storage_checksum)
|
||||
|
||||
ancestors = '/'
|
||||
if parent_docker_id is not None:
|
||||
parent = get_repo_image(namespace, repository_name, parent_docker_id)
|
||||
if parent is None:
|
||||
msg = 'Parent not found with docker image id {0} in repo {1}/{2}'.format(parent_docker_id,
|
||||
namespace,
|
||||
repository_name)
|
||||
raise InvalidImageException(msg)
|
||||
ancestors = '{0}{1}/'.format(parent.ancestors, parent.id)
|
||||
|
||||
created = None
|
||||
if created_date_str is not None:
|
||||
try:
|
||||
created = dateutil.parser.parse(created_date_str).replace(tzinfo=None)
|
||||
except:
|
||||
# parse raises different exceptions, so we cannot use a specific kind of handler here.
|
||||
pass
|
||||
|
||||
return Image.create(docker_image_id=docker_image_id, ancestors=ancestors, comment=comment,
|
||||
command=command, v1_json_metadata=v1_json_metadata, created=created,
|
||||
storage=the_bits, repository=repo)
|
||||
|
|
|
@ -5,7 +5,7 @@ from peewee import JOIN_LEFT_OUTER, fn
|
|||
from data.model import config, db_transaction, InvalidImageException
|
||||
from data.database import (ImageStorage, Image, DerivedImageStorage, ImageStoragePlacement,
|
||||
ImageStorageLocation, ImageStorageTransformation, ImageStorageSignature,
|
||||
ImageStorageSignatureKind)
|
||||
ImageStorageSignatureKind, Repository, Namespace)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -18,7 +18,7 @@ def find_or_create_derived_storage(source, transformation_name, preferred_locati
|
|||
|
||||
logger.debug('Creating storage dervied from source: %s', source.uuid)
|
||||
trans = ImageStorageTransformation.get(name=transformation_name)
|
||||
new_storage = create_storage(preferred_location)
|
||||
new_storage = create_v1_storage(preferred_location)
|
||||
DerivedImageStorage.create(source=source, derivative=new_storage, transformation=trans)
|
||||
return new_storage
|
||||
|
||||
|
@ -117,8 +117,8 @@ def garbage_collect_storage(storage_id_whitelist):
|
|||
config.store.remove({location_name}, image_path)
|
||||
|
||||
|
||||
def create_storage(location_name):
|
||||
storage = ImageStorage.create()
|
||||
def create_v1_storage(location_name):
|
||||
storage = ImageStorage.create(cas_path=False)
|
||||
location = ImageStorageLocation.get(name=location_name)
|
||||
ImageStoragePlacement.create(location=location, storage=storage)
|
||||
storage.locations = {location_name}
|
||||
|
@ -138,10 +138,9 @@ def lookup_storage_signature(storage, signature_kind):
|
|||
kind = ImageStorageSignatureKind.get(name=signature_kind)
|
||||
try:
|
||||
return (ImageStorageSignature
|
||||
.select()
|
||||
.where(ImageStorageSignature.storage == storage,
|
||||
ImageStorageSignature.kind == kind)
|
||||
.get())
|
||||
.select()
|
||||
.where(ImageStorageSignature.storage == storage, ImageStorageSignature.kind == kind)
|
||||
.get())
|
||||
except ImageStorageSignature.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
@ -149,12 +148,12 @@ def lookup_storage_signature(storage, signature_kind):
|
|||
def find_derived_storage(source, transformation_name):
|
||||
try:
|
||||
found = (ImageStorage
|
||||
.select(ImageStorage, DerivedImageStorage)
|
||||
.join(DerivedImageStorage, on=(ImageStorage.id == DerivedImageStorage.derivative))
|
||||
.join(ImageStorageTransformation)
|
||||
.where(DerivedImageStorage.source == source,
|
||||
ImageStorageTransformation.name == transformation_name)
|
||||
.get())
|
||||
.select(ImageStorage, DerivedImageStorage)
|
||||
.join(DerivedImageStorage, on=(ImageStorage.id == DerivedImageStorage.derivative))
|
||||
.join(ImageStorageTransformation)
|
||||
.where(DerivedImageStorage.source == source,
|
||||
ImageStorageTransformation.name == transformation_name)
|
||||
.get())
|
||||
|
||||
found.locations = {placement.location.name for placement in found.imagestorageplacement_set}
|
||||
return found
|
||||
|
@ -176,16 +175,17 @@ def delete_derived_storage_by_uuid(storage_uuid):
|
|||
image_storage.delete_instance(recursive=True)
|
||||
|
||||
|
||||
def get_storage_by_uuid(storage_uuid):
|
||||
placements = list(ImageStoragePlacement
|
||||
.select(ImageStoragePlacement, ImageStorage, ImageStorageLocation)
|
||||
.join(ImageStorageLocation)
|
||||
.switch(ImageStoragePlacement)
|
||||
.join(ImageStorage)
|
||||
.where(ImageStorage.uuid == storage_uuid))
|
||||
def _get_storage(query_modifier):
|
||||
query = (ImageStoragePlacement
|
||||
.select(ImageStoragePlacement, ImageStorage, ImageStorageLocation)
|
||||
.join(ImageStorageLocation)
|
||||
.switch(ImageStoragePlacement)
|
||||
.join(ImageStorage))
|
||||
|
||||
placements = list(query_modifier(query))
|
||||
|
||||
if not placements:
|
||||
raise InvalidImageException('No storage found with uuid: %s', storage_uuid)
|
||||
raise InvalidImageException()
|
||||
|
||||
found = placements[0].storage
|
||||
found.locations = {placement.location.name for placement in placements}
|
||||
|
@ -193,3 +193,26 @@ def get_storage_by_uuid(storage_uuid):
|
|||
return found
|
||||
|
||||
|
||||
def get_storage_by_uuid(storage_uuid):
|
||||
def filter_to_uuid(query):
|
||||
return query.where(ImageStorage.uuid == storage_uuid)
|
||||
|
||||
try:
|
||||
return _get_storage(filter_to_uuid)
|
||||
except InvalidImageException:
|
||||
raise InvalidImageException('No storage found with uuid: %s', storage_uuid)
|
||||
|
||||
|
||||
def get_repo_storage_by_checksum(namespace, repository_name, checksum):
|
||||
def filter_to_repo_and_checksum(query):
|
||||
return (query
|
||||
.join(Image)
|
||||
.join(Repository)
|
||||
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
|
||||
.where(Repository.name == repository_name, Namespace.username == namespace,
|
||||
ImageStorage.checksum == checksum))
|
||||
|
||||
try:
|
||||
return _get_storage(filter_to_repo_and_checksum)
|
||||
except InvalidImageException:
|
||||
raise InvalidImageException('No storage found with checksum {0}'.format(checksum))
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from uuid import uuid4
|
||||
|
||||
from data.model import image, db_transaction, DataModelException, _basequery
|
||||
from data.database import (RepositoryTag, Repository, Image, ImageStorage, Namespace,
|
||||
from data.model import (image, db_transaction, DataModelException, _basequery,
|
||||
InvalidManifestException)
|
||||
from data.database import (RepositoryTag, Repository, Image, ImageStorage, Namespace, TagManifest,
|
||||
get_epoch_timestamp, db_for_update)
|
||||
|
||||
|
||||
|
@ -36,8 +37,8 @@ def list_repository_tags(namespace_name, repository_name, include_hidden=False,
|
|||
return query
|
||||
|
||||
|
||||
def create_or_update_tag(namespace_name, repository_name, tag_name,
|
||||
tag_docker_image_id, reversion=False):
|
||||
def create_or_update_tag(namespace_name, repository_name, tag_name, tag_docker_image_id,
|
||||
reversion=False):
|
||||
try:
|
||||
repo = _basequery.get_existing_repository(namespace_name, repository_name)
|
||||
except Repository.DoesNotExist:
|
||||
|
@ -160,3 +161,55 @@ def revert_tag(repo_obj, tag_name, docker_image_id):
|
|||
return create_or_update_tag(repo_obj.namespace_user.username, repo_obj.name, tag_name,
|
||||
docker_image_id, reversion=True)
|
||||
|
||||
|
||||
def store_tag_manifest(namespace, repo_name, tag_name, docker_image_id, manifest_digest,
|
||||
manifest_data):
|
||||
tag = create_or_update_tag(namespace, repo_name, tag_name, docker_image_id)
|
||||
return TagManifest.create(tag=tag, digest=manifest_digest, json_data=manifest_data)
|
||||
|
||||
|
||||
def _get_active_tag(namespace, repo_name, tag_name):
|
||||
return _tag_alive(RepositoryTag
|
||||
.select()
|
||||
.join(Image)
|
||||
.join(Repository)
|
||||
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
|
||||
.where(RepositoryTag.name == tag_name, Repository.name == repo_name,
|
||||
Namespace.username == namespace)).get()
|
||||
|
||||
|
||||
def associate_generated_tag_manifest(namespace, repo_name, tag_name, manifest_digest,
|
||||
manifest_data):
|
||||
tag = _get_active_tag(namespace, repo_name, tag_name)
|
||||
return TagManifest.create(tag=tag, digest=manifest_digest, json_data=manifest_data)
|
||||
|
||||
|
||||
def load_tag_manifest(namespace, repo_name, tag_name):
|
||||
try:
|
||||
return (_load_repo_manifests(namespace, repo_name)
|
||||
.where(RepositoryTag.name == tag_name)
|
||||
.get())
|
||||
except TagManifest.DoesNotExist:
|
||||
msg = 'Manifest not found for tag {0} in repo {1}/{2}'.format(tag_name, namespace, repo_name)
|
||||
raise InvalidManifestException(msg)
|
||||
|
||||
|
||||
def load_manifest_by_digest(namespace, repo_name, digest):
|
||||
try:
|
||||
return (_load_repo_manifests(namespace, repo_name)
|
||||
.where(TagManifest.digest == digest)
|
||||
.get())
|
||||
except TagManifest.DoesNotExist:
|
||||
msg = 'Manifest not found with digest {0} in repo {1}/{2}'.format(digest, namespace, repo_name)
|
||||
raise InvalidManifestException(msg)
|
||||
|
||||
|
||||
def _load_repo_manifests(namespace, repo_name):
|
||||
return (TagManifest
|
||||
.select(TagManifest, RepositoryTag)
|
||||
.join(RepositoryTag)
|
||||
.join(Image)
|
||||
.join(Repository)
|
||||
.join(Namespace, on=(Namespace.id == Repository.namespace_user))
|
||||
.where(Repository.name == repo_name, Namespace.username == namespace))
|
||||
|
||||
|
|
Reference in a new issue