update interfaces to use ABC

This commit is contained in:
Jimmy Zelinskie 2016-09-23 17:50:09 -04:00
parent a1a930b833
commit 44eca10c05
14 changed files with 467 additions and 508 deletions

View file

@ -1,7 +1,9 @@
from abc import ABCMeta, abstractmethod
from collections import namedtuple from collections import namedtuple
import data.model from six import add_metaclass
import data.model
class ServiceKey(namedtuple('ServiceKey', ['name', 'kid', 'service', 'jwk', 'metadata', class ServiceKey(namedtuple('ServiceKey', ['name', 'kid', 'service', 'jwk', 'metadata',
@ -22,47 +24,48 @@ class ServiceKeyDoesNotExist(ServiceKeyException):
pass pass
# TODO(jzelinskie): make this interface support superuser API # TODO(jzelinskie): maybe make this interface support superuser API
@add_metaclass(ABCMeta)
class KeyServerDataInterface(object): class KeyServerDataInterface(object):
""" """
Interface that represents all data store interactions required by a JWT key service. Interface that represents all data store interactions required by a JWT key service.
""" """
@classmethod @abstractmethod
def list_service_keys(cls, service): def list_service_keys(self, service):
""" """
Returns a list of service keys or an empty list if the service does not exist. Returns a list of service keys or an empty list if the service does not exist.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_service_key(cls, signer_kid, service=None, alive_only=None, approved_only=None): def get_service_key(self, signer_kid, service=None, alive_only=None, approved_only=None):
""" """
Returns a service kid with the given kid or raises ServiceKeyNotFound. Returns a service kid with the given kid or raises ServiceKeyNotFound.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_service_key(cls, name, kid, service, jwk, metadata, expiration_date, def create_service_key(self, name, kid, service, jwk, metadata, expiration_date,
rotation_duration=None): rotation_duration=None):
""" """
Stores a service key. Stores a service key.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def replace_service_key(cls, old_kid, kid, jwk, metadata, expiration_date): def replace_service_key(self, old_kid, kid, jwk, metadata, expiration_date):
""" """
Replaces a service with a new key or raises ServiceKeyNotFound. Replaces a service with a new key or raises ServiceKeyNotFound.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def delete_service_key(cls, kid): def delete_service_key(self, kid):
""" """
Deletes and returns a service key with the given kid or raises ServiceKeyNotFound. Deletes and returns a service key with the given kid or raises ServiceKeyNotFound.
""" """
raise NotImplementedError() pass
class PreOCIModel(KeyServerDataInterface): class PreOCIModel(KeyServerDataInterface):
@ -70,53 +73,52 @@ class PreOCIModel(KeyServerDataInterface):
PreOCIModel implements the data model for JWT key service using a database schema before it was PreOCIModel implements the data model for JWT key service using a database schema before it was
changed to support the OCI specification. changed to support the OCI specification.
""" """
@classmethod def list_service_keys(self, service):
def _db_key_to_servicekey(cls, key):
"""
Converts the database model of a service key into a ServiceKey.
"""
return ServiceKey(
name=key.name,
kid=key.kid,
service=key.service,
jwk=key.jwk,
metadata=key.metadata,
created_date=key.created_date,
expiration_date=key.expiration_date,
rotation_duration=key.rotation_duration,
approval=key.approval,
)
@classmethod
def list_service_keys(cls, service):
return data.model.service_keys.list_service_keys(service) return data.model.service_keys.list_service_keys(service)
@classmethod def get_service_key(self, signer_kid, service=None, alive_only=True, approved_only=True):
def get_service_key(cls, signer_kid, service=None, alive_only=True, approved_only=True):
try: try:
key = data.model.service_keys.get_service_key(signer_kid, service, alive_only, approved_only) key = data.model.service_keys.get_service_key(signer_kid, service, alive_only, approved_only)
return cls._db_key_to_servicekey(key) return _db_key_to_servicekey(key)
except data.model.ServiceKeyDoesNotExist: except data.model.ServiceKeyDoesNotExist:
raise ServiceKeyDoesNotExist() raise ServiceKeyDoesNotExist()
@classmethod def create_service_key(self, name, kid, service, jwk, metadata, expiration_date,
def create_service_key(cls, name, kid, service, jwk, metadata, expiration_date,
rotation_duration=None): rotation_duration=None):
key = data.model.service_keys.create_service_key(name, kid, service, jwk, metadata, key = data.model.service_keys.create_service_key(name, kid, service, jwk, metadata,
expiration_date, rotation_duration) expiration_date, rotation_duration)
return cls._db_key_to_servicekey(key) return _db_key_to_servicekey(key)
@classmethod def replace_service_key(self, old_kid, kid, jwk, metadata, expiration_date):
def replace_service_key(cls, old_kid, kid, jwk, metadata, expiration_date):
try: try:
data.model.service_keys.replace_service_key(old_kid, kid, jwk, metadata, expiration_date) data.model.service_keys.replace_service_key(old_kid, kid, jwk, metadata, expiration_date)
except data.model.ServiceKeyDoesNotExist: except data.model.ServiceKeyDoesNotExist:
raise ServiceKeyDoesNotExist() raise ServiceKeyDoesNotExist()
@classmethod def delete_service_key(self, kid):
def delete_service_key(cls, kid):
try: try:
key = data.model.service_keys.delete_service_key(kid) key = data.model.service_keys.delete_service_key(kid)
return cls._db_key_to_servicekey(key) return _db_key_to_servicekey(key)
except data.model.ServiceKeyDoesNotExist: except data.model.ServiceKeyDoesNotExist:
raise ServiceKeyDoesNotExist() raise ServiceKeyDoesNotExist()
pre_oci_model = PreOCIModel()
def _db_key_to_servicekey(key):
"""
Converts the Pre-OCI database model of a service key into a ServiceKey.
"""
return ServiceKey(
name=key.name,
kid=key.kid,
service=key.service,
jwk=key.jwk,
metadata=key.metadata,
created_date=key.created_date,
expiration_date=key.expiration_date,
rotation_duration=key.rotation_duration,
approval=key.approval,
)

View file

@ -1,7 +1,11 @@
from abc import ABCMeta, abstractmethod
from collections import namedtuple
from six import add_metaclass
from app import app, storage as store from app import app, storage as store
from data import model from data import model
from data.model import db_transaction from data.model import db_transaction
from collections import namedtuple
from util.morecollections import AttrDict from util.morecollections import AttrDict
@ -12,211 +16,216 @@ class Repository(namedtuple('Repository', ['id', 'name', 'namespace_name', 'desc
""" """
@add_metaclass(ABCMeta)
class DockerRegistryV1DataInterface(object): class DockerRegistryV1DataInterface(object):
""" """
Interface that represents all data store interactions required by a Docker Registry v1. Interface that represents all data store interactions required by a Docker Registry v1.
""" """
@classmethod @abstractmethod
def placement_locations_docker_v1(cls, namespace_name, repo_name, image_id): def placement_locations_docker_v1(self, namespace_name, repo_name, image_id):
""" """
Returns all the placements for the image with the given V1 Docker ID, found under the given Returns all the placements for the image with the given V1 Docker ID, found under the given
repository or None if no image was found. repository or None if no image was found.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def placement_locations_and_path_docker_v1(cls, namespace_name, repo_name, image_id): def placement_locations_and_path_docker_v1(self, namespace_name, repo_name, image_id):
""" """
Returns all the placements for the image with the given V1 Docker ID, found under the given Returns all the placements for the image with the given V1 Docker ID, found under the given
repository or None if no image was found. repository or None if no image was found.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def docker_v1_metadata(cls, namespace_name, repo_name, image_id): def docker_v1_metadata(self, namespace_name, repo_name, image_id):
""" """
Returns various pieces of metadata associated with an image with the given V1 Docker ID, Returns various pieces of metadata associated with an image with the given V1 Docker ID,
including the checksum and its V1 JSON metadata. including the checksum and its V1 JSON metadata.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def update_docker_v1_metadata(cls, namespace_name, repo_name, image_id, created_date_str, comment, def update_docker_v1_metadata(self, namespace_name, repo_name, image_id, created_date_str,
command, compat_json, parent_image_id=None): comment, command, compat_json, parent_image_id=None):
""" """
Updates various pieces of V1 metadata associated with a particular image. Updates various pieces of V1 metadata associated with a particular image.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def storage_exists(cls, namespace_name, repo_name, image_id): def storage_exists(self, namespace_name, repo_name, image_id):
""" """
Returns whether storage already exists for the image with the V1 Docker ID under the given Returns whether storage already exists for the image with the V1 Docker ID under the given
repository. repository.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def store_docker_v1_checksums(cls, namespace_name, repo_name, image_id, checksum, content_checksum): def store_docker_v1_checksums(self, namespace_name, repo_name, image_id, checksum,
content_checksum):
""" """
Stores the various V1 checksums for the image with the V1 Docker ID. Stores the various V1 checksums for the image with the V1 Docker ID.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def is_image_uploading(cls, namespace_name, repo_name, image_id): def is_image_uploading(self, namespace_name, repo_name, image_id):
""" """
Returns whether the image with the V1 Docker ID is currently marked as uploading. Returns whether the image with the V1 Docker ID is currently marked as uploading.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def update_image_uploading(cls, namespace_name, repo_name, image_id, is_uploading): def update_image_uploading(self, namespace_name, repo_name, image_id, is_uploading):
""" Marks the image with the V1 Docker ID with the given uploading status. """ """
raise NotImplementedError() Marks the image with the V1 Docker ID with the given uploading status.
"""
pass
@classmethod @abstractmethod
def update_image_sizes(cls, namespace_name, repo_name, image_id, size, uncompressed_size): def update_image_sizes(self, namespace_name, repo_name, image_id, size, uncompressed_size):
""" """
Updates the sizing information for the image with the given V1 Docker ID. Updates the sizing information for the image with the given V1 Docker ID.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_image_size(cls, namespace_name, repo_name, image_id): def get_image_size(self, namespace_name, repo_name, image_id):
""" """
Returns the wire size of the image with the given Docker V1 ID. Returns the wire size of the image with the given Docker V1 ID.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_bittorrent_pieces(cls, namespace_name, repo_name, image_id, pieces_bytes): def create_bittorrent_pieces(self, namespace_name, repo_name, image_id, pieces_bytes):
""" """
Saves the BitTorrent piece hashes for the image with the given Docker V1 ID. Saves the BitTorrent piece hashes for the image with the given Docker V1 ID.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def image_ancestry(cls, namespace_name, repo_name, image_id): def image_ancestry(self, namespace_name, repo_name, image_id):
""" """
Returns a list containing the full ancestry of Docker V1 IDs, in order, for the image with the Returns a list containing the full ancestry of Docker V1 IDs, in order, for the image with the
given Docker V1 ID. given Docker V1 ID.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def repository_exists(cls, namespace_name, repo_name): def repository_exists(self, namespace_name, repo_name):
""" """
Returns whether the repository with the given name and namespace exists. Returns whether the repository with the given name and namespace exists.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_or_link_image(cls, username, namespace_name, repo_name, image_id, storage_location): def create_or_link_image(self, username, namespace_name, repo_name, image_id, storage_location):
""" """
Adds the given image to the given repository, by either linking to an existing image visible to Adds the given image to the given repository, by either linking to an existing image visible to
the user with the given username, or creating a new one if no existing image matches. the user with the given username, or creating a new one if no existing image matches.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_temp_hidden_tag(cls, namespace_name, repo_name, image_id, expiration): def create_temp_hidden_tag(self, namespace_name, repo_name, image_id, expiration):
""" """
Creates a hidden tag under the matching namespace pointing to the image with the given V1 Docker Creates a hidden tag under the matching namespace pointing to the image with the given V1 Docker
ID. ID.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def list_tags(cls, namespace_name, repo_name): def list_tags(self, namespace_name, repo_name):
""" """
Returns all the tags defined in the repository with the given namespace and name. Returns all the tags defined in the repository with the given namespace and name.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_or_update_tag(cls, namespace_name, repo_name, image_id, tag_name): def create_or_update_tag(self, namespace_name, repo_name, image_id, tag_name):
""" """
Creates or updates a tag under the matching repository to point to the image with the given Creates or updates a tag under the matching repository to point to the image with the given
Docker V1 ID. Docker V1 ID.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def find_image_id_by_tag(cls, namespace_name, repo_name, tag_name): def find_image_id_by_tag(self, namespace_name, repo_name, tag_name):
""" """
Returns the Docker V1 image ID for the HEAD image for the tag with the given name under the Returns the Docker V1 image ID for the HEAD image for the tag with the given name under the
matching repository, or None if none. matching repository, or None if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def delete_tag(cls, namespace_name, repo_name, tag_name): def delete_tag(self, namespace_name, repo_name, tag_name):
""" Deletes the given tag from the given repository. """ """
raise NotImplementedError() Deletes the given tag from the given repository.
"""
pass
@classmethod @abstractmethod
def load_token(cls, token): def load_token(self, token):
""" """
Loads the data associated with the given (deprecated) access token, and, if Loads the data associated with the given (deprecated) access token, and, if
found returns True. found returns True.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def verify_robot(cls, username, token): def verify_robot(self, username, token):
""" """
Returns True if the given robot username and token match an existing robot Returns True if the given robot username and token match an existing robot
account. account.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def change_user_password(cls, user, new_password): def change_user_password(self, user, new_password):
""" """
Changes the password associated with the given user. Changes the password associated with the given user.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_repository(cls, namespace_name, repo_name): def get_repository(self, namespace_name, repo_name):
""" """
Returns the repository with the given name under the given namespace or None Returns the repository with the given name under the given namespace or None
if none. if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_repository(cls, namespace_name, repo_name, user=None): def create_repository(self, namespace_name, repo_name, user=None):
""" """
Creates a new repository under the given namespace with the given name, for Creates a new repository under the given namespace with the given name, for
the given user. the given user.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def repository_is_public(cls, namespace_name, repo_name): def repository_is_public(self, namespace_name, repo_name):
""" """
Returns whether the repository with the given name under the given namespace Returns whether the repository with the given name under the given namespace
is public. If no matching repository was found, returns False. is public. If no matching repository was found, returns False.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def validate_oauth_token(cls, token): def validate_oauth_token(self, token):
""" Returns whether the given OAuth token validates. """ """ Returns whether the given OAuth token validates. """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_sorted_matching_repositories(cls, search_term, only_public, can_read, limit): def get_sorted_matching_repositories(self, search_term, only_public, can_read, limit):
""" """
Returns a sorted list of repositories matching the given search term. Returns a sorted list of repositories matching the given search term.
can_read is a callback that will be invoked for each repository found, to can_read is a callback that will be invoked for each repository found, to
filter results to only those visible to the current user (if any). filter results to only those visible to the current user (if any).
""" """
raise NotImplementedError() pass
class PreOCIModel(DockerRegistryV1DataInterface): class PreOCIModel(DockerRegistryV1DataInterface):
@ -224,22 +233,19 @@ class PreOCIModel(DockerRegistryV1DataInterface):
PreOCIModel implements the data model for the v1 Docker Registry protocol using a database schema PreOCIModel implements the data model for the v1 Docker Registry protocol using a database schema
before it was changed to support the OCI specification. before it was changed to support the OCI specification.
""" """
@classmethod def placement_locations_docker_v1(self, namespace_name, repo_name, image_id):
def placement_locations_docker_v1(cls, namespace_name, repo_name, image_id):
repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id)
if repo_image is None or repo_image.storage is None: if repo_image is None or repo_image.storage is None:
return None return None
return repo_image.storage.locations return repo_image.storage.locations
@classmethod def placement_locations_and_path_docker_v1(self, namespace_name, repo_name, image_id):
def placement_locations_and_path_docker_v1(cls, namespace_name, repo_name, image_id):
repo_image = model.image.get_repo_image_extended(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image_extended(namespace_name, repo_name, image_id)
if not repo_image or repo_image.storage is None: if not repo_image or repo_image.storage is None:
return None, None return None, None
return repo_image.storage.locations, model.storage.get_layer_path(repo_image.storage) return repo_image.storage.locations, model.storage.get_layer_path(repo_image.storage)
@classmethod def docker_v1_metadata(self, namespace_name, repo_name, image_id):
def docker_v1_metadata(cls, namespace_name, repo_name, image_id):
repo_image = model.image.get_repo_image(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image(namespace_name, repo_name, image_id)
if repo_image is None: if repo_image is None:
return None return None
@ -252,9 +258,8 @@ class PreOCIModel(DockerRegistryV1DataInterface):
'compat_json': repo_image.v1_json_metadata, 'compat_json': repo_image.v1_json_metadata,
}) })
@classmethod def update_docker_v1_metadata(self, namespace_name, repo_name, image_id, created_date_str,
def update_docker_v1_metadata(cls, namespace_name, repo_name, image_id, created_date_str, comment, comment, command, compat_json, parent_image_id=None):
command, compat_json, parent_image_id=None):
parent_image = None parent_image = None
if parent_image_id is not None: if parent_image_id is not None:
parent_image = model.image.get_repo_image(namespace_name, repo_name, parent_image_id) parent_image = model.image.get_repo_image(namespace_name, repo_name, parent_image_id)
@ -262,8 +267,7 @@ class PreOCIModel(DockerRegistryV1DataInterface):
model.image.set_image_metadata(image_id, namespace_name, repo_name, created_date_str, comment, model.image.set_image_metadata(image_id, namespace_name, repo_name, created_date_str, comment,
command, compat_json, parent=parent_image) command, compat_json, parent=parent_image)
@classmethod def storage_exists(self, namespace_name, repo_name, image_id):
def storage_exists(cls, namespace_name, repo_name, image_id):
repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id)
if repo_image is None or repo_image.storage is None: if repo_image is None or repo_image.storage is None:
return False return False
@ -274,8 +278,8 @@ class PreOCIModel(DockerRegistryV1DataInterface):
layer_path = model.storage.get_layer_path(repo_image.storage) layer_path = model.storage.get_layer_path(repo_image.storage)
return store.exists(repo_image.storage.locations, layer_path) return store.exists(repo_image.storage.locations, layer_path)
@classmethod def store_docker_v1_checksums(self, namespace_name, repo_name, image_id, checksum,
def store_docker_v1_checksums(cls, namespace_name, repo_name, image_id, checksum, content_checksum): content_checksum):
repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id)
if repo_image is None or repo_image.storage is None: if repo_image is None or repo_image.storage is None:
return return
@ -286,15 +290,13 @@ class PreOCIModel(DockerRegistryV1DataInterface):
repo_image.storage.save() repo_image.storage.save()
repo_image.save() repo_image.save()
@classmethod def is_image_uploading(self, namespace_name, repo_name, image_id):
def is_image_uploading(cls, namespace_name, repo_name, image_id):
repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id)
if repo_image is None or repo_image.storage is None: if repo_image is None or repo_image.storage is None:
return False return False
return repo_image.storage.uploading return repo_image.storage.uploading
@classmethod def update_image_uploading(self, namespace_name, repo_name, image_id, is_uploading):
def update_image_uploading(cls, namespace_name, repo_name, image_id, is_uploading):
repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id)
if repo_image is None or repo_image.storage is None: if repo_image is None or repo_image.storage is None:
return return
@ -303,20 +305,17 @@ class PreOCIModel(DockerRegistryV1DataInterface):
repo_image.storage.save() repo_image.storage.save()
return repo_image.storage return repo_image.storage
@classmethod def update_image_sizes(self, namespace_name, repo_name, image_id, size, uncompressed_size):
def update_image_sizes(cls, namespace_name, repo_name, image_id, size, uncompressed_size):
model.storage.set_image_storage_metadata(image_id, namespace_name, repo_name, size, model.storage.set_image_storage_metadata(image_id, namespace_name, repo_name, size,
uncompressed_size) uncompressed_size)
@classmethod def get_image_size(self, namespace_name, repo_name, image_id):
def get_image_size(cls, namespace_name, repo_name, image_id):
repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id)
if repo_image is None or repo_image.storage is None: if repo_image is None or repo_image.storage is None:
return None return None
return repo_image.storage.image_size return repo_image.storage.image_size
@classmethod def create_bittorrent_pieces(self, namespace_name, repo_name, image_id, pieces_bytes):
def create_bittorrent_pieces(cls, namespace_name, repo_name, image_id, pieces_bytes):
repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image_and_storage(namespace_name, repo_name, image_id)
if repo_image is None or repo_image.storage is None: if repo_image is None or repo_image.storage is None:
return return
@ -324,8 +323,7 @@ class PreOCIModel(DockerRegistryV1DataInterface):
model.storage.save_torrent_info(repo_image.storage, app.config['BITTORRENT_PIECE_SIZE'], model.storage.save_torrent_info(repo_image.storage, app.config['BITTORRENT_PIECE_SIZE'],
pieces_bytes) pieces_bytes)
@classmethod def image_ancestry(self, namespace_name, repo_name, image_id):
def image_ancestry(cls, namespace_name, repo_name, image_id):
try: try:
image = model.image.get_image_by_id(namespace_name, repo_name, image_id) image = model.image.get_image_by_id(namespace_name, repo_name, image_id)
except model.InvalidImageException: except model.InvalidImageException:
@ -336,18 +334,15 @@ class PreOCIModel(DockerRegistryV1DataInterface):
ancestry_docker_ids.extend([parent.docker_image_id for parent in parents]) ancestry_docker_ids.extend([parent.docker_image_id for parent in parents])
return ancestry_docker_ids return ancestry_docker_ids
@classmethod def repository_exists(self, namespace_name, repo_name):
def repository_exists(cls, namespace_name, repo_name):
repo = model.repository.get_repository(namespace_name, repo_name) repo = model.repository.get_repository(namespace_name, repo_name)
return repo is not None return repo is not None
@classmethod def create_or_link_image(self, username, namespace_name, repo_name, image_id, storage_location):
def create_or_link_image(cls, username, namespace_name, repo_name, image_id, storage_location):
repo = model.repository.get_repository(namespace_name, repo_name) repo = model.repository.get_repository(namespace_name, repo_name)
model.image.find_create_or_link_image(image_id, repo, username, {}, storage_location) model.image.find_create_or_link_image(image_id, repo, username, {}, storage_location)
@classmethod def create_temp_hidden_tag(self, namespace_name, repo_name, image_id, expiration):
def create_temp_hidden_tag(cls, namespace_name, repo_name, image_id, expiration):
repo_image = model.image.get_repo_image(namespace_name, repo_name, image_id) repo_image = model.image.get_repo_image(namespace_name, repo_name, image_id)
if repo_image is None: if repo_image is None:
return return
@ -355,16 +350,13 @@ class PreOCIModel(DockerRegistryV1DataInterface):
repo = repo_image.repository repo = repo_image.repository
model.tag.create_temporary_hidden_tag(repo, repo_image, expiration) model.tag.create_temporary_hidden_tag(repo, repo_image, expiration)
@classmethod def list_tags(self, namespace_name, repo_name):
def list_tags(cls, namespace_name, repo_name):
return model.tag.list_repository_tags(namespace_name, repo_name) return model.tag.list_repository_tags(namespace_name, repo_name)
@classmethod def create_or_update_tag(self, namespace_name, repo_name, image_id, tag_name):
def create_or_update_tag(cls, namespace_name, repo_name, image_id, tag_name):
model.tag.create_or_update_tag(namespace_name, repo_name, tag_name, image_id) model.tag.create_or_update_tag(namespace_name, repo_name, tag_name, image_id)
@classmethod def find_image_id_by_tag(self, namespace_name, repo_name, tag_name):
def find_image_id_by_tag(cls, namespace_name, repo_name, tag_name):
try: try:
tag_image = model.tag.get_tag_image(namespace_name, repo_name, tag_name) tag_image = model.tag.get_tag_image(namespace_name, repo_name, tag_name)
except model.DataModelException: except model.DataModelException:
@ -372,61 +364,55 @@ class PreOCIModel(DockerRegistryV1DataInterface):
return tag_image.docker_image_id return tag_image.docker_image_id
@classmethod def delete_tag(self, namespace_name, repo_name, tag_name):
def delete_tag(cls, namespace_name, repo_name, tag_name):
model.tag.delete_tag(namespace_name, repo_name, tag_name) model.tag.delete_tag(namespace_name, repo_name, tag_name)
@classmethod def load_token(self, token):
def load_token(cls, token):
try: try:
model.token.load_token_data(token) model.token.load_token_data(token)
return True return True
except model.InvalidTokenException: except model.InvalidTokenException:
return False return False
@classmethod def verify_robot(self, username, token):
def verify_robot(cls, username, token):
try: try:
return bool(model.user.verify_robot(username, token)) return bool(model.user.verify_robot(username, token))
except model.InvalidRobotException: except model.InvalidRobotException:
return False return False
@classmethod def change_user_password(self, user, new_password):
def change_user_password(cls, user, new_password):
model.user.change_password(user, new_password) model.user.change_password(user, new_password)
@classmethod def get_repository(self, namespace_name, repo_name):
def _repository_for_repo(cls, repo):
""" Returns a Repository object representing the repo data model instance given. """
return Repository(
id=repo.id,
name=repo.name,
namespace_name=repo.namespace_user.username,
description=repo.description,
is_public=model.repository.is_repository_public(repo)
)
@classmethod
def get_repository(cls, namespace_name, repo_name):
repo = model.repository.get_repository(namespace_name, repo_name) repo = model.repository.get_repository(namespace_name, repo_name)
if repo is None: if repo is None:
return None return None
return cls._repository_for_repo(repo) return _repository_for_repo(repo)
@classmethod def create_repository(self, namespace_name, repo_name, user=None):
def create_repository(cls, namespace_name, repo_name, user=None):
model.repository.create_repository(namespace_name, repo_name, user) model.repository.create_repository(namespace_name, repo_name, user)
@classmethod def repository_is_public(self, namespace_name, repo_name):
def repository_is_public(cls, namespace_name, repo_name):
return model.repository.repository_is_public(namespace_name, repo_name) return model.repository.repository_is_public(namespace_name, repo_name)
@classmethod def validate_oauth_token(self, token):
def validate_oauth_token(cls, token):
return bool(model.oauth.validate_access_token(token)) return bool(model.oauth.validate_access_token(token))
@classmethod def get_sorted_matching_repositories(self, search_term, only_public, can_read, limit):
def get_sorted_matching_repositories(cls, search_term, only_public, can_read, limit):
repos = model.repository.get_sorted_matching_repositories(search_term, only_public, can_read, repos = model.repository.get_sorted_matching_repositories(search_term, only_public, can_read,
limit=limit) limit=limit)
return [cls._repository_for_repo(repo) for repo in repos] return [_repository_for_repo(repo) for repo in repos]
def _repository_for_repo(repo):
""" Returns a Repository object representing the Pre-OCI data model instance of a repository. """
return Repository(
id=repo.id,
name=repo.name,
namespace_name=repo.namespace_user.username,
description=repo.description,
is_public=model.repository.is_repository_public(repo)
)
pre_oci_model = PreOCIModel()

View file

@ -1,10 +1,12 @@
from abc import ABCMeta, abstractmethod
from collections import namedtuple from collections import namedtuple
from namedlist import namedlist from namedlist import namedlist
from peewee import IntegrityError from peewee import IntegrityError
from six import add_metaclass
from data import model, database from data import model, database
from data.model import DataModelException, TagAlreadyCreatedException from data.model import DataModelException
from image.docker.v1 import DockerV1Metadata from image.docker.v1 import DockerV1Metadata
_MEDIA_TYPE = "application/vnd.docker.distribution.manifest.v1+prettyjws" _MEDIA_TYPE = "application/vnd.docker.distribution.manifest.v1+prettyjws"
@ -54,153 +56,153 @@ class Label(namedtuple('Label', ['key', 'value', 'source_type', 'media_type'])):
""" """
@add_metaclass(ABCMeta)
class DockerRegistryV2DataInterface(object): class DockerRegistryV2DataInterface(object):
""" """
Interface that represents all data store interactions required by a Docker Registry v1. Interface that represents all data store interactions required by a Docker Registry v1.
""" """
@classmethod @abstractmethod
def create_repository(cls, namespace_name, repo_name, creating_user=None): def create_repository(self, namespace_name, repo_name, creating_user=None):
""" """
Creates a new repository under the specified namespace with the given name. The user supplied is Creates a new repository under the specified namespace with the given name. The user supplied is
the user creating the repository, if any. the user creating the repository, if any.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def repository_is_public(cls, namespace_name, repo_name): def repository_is_public(self, namespace_name, repo_name):
""" """
Returns true if the repository with the given name under the given namespace has public Returns true if the repository with the given name under the given namespace has public
visibility. visibility.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_repository(cls, namespace_name, repo_name): def get_repository(self, namespace_name, repo_name):
""" """
Returns a repository tuple for the repository with the given name under the given namespace. Returns a repository tuple for the repository with the given name under the given namespace.
Returns None if no such repository was found. Returns None if no such repository was found.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def has_active_tag(cls, namespace_name, repo_name, tag_name): def has_active_tag(self, namespace_name, repo_name, tag_name):
""" """
Returns whether there is an active tag for the tag with the given name under the matching Returns whether there is an active tag for the tag with the given name under the matching
repository, if any, or none if none. repository, if any, or none if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_manifest_by_tag(cls, namespace_name, repo_name, tag_name): def get_manifest_by_tag(self, namespace_name, repo_name, tag_name):
""" """
Returns the current manifest for the tag with the given name under the matching repository, if Returns the current manifest for the tag with the given name under the matching repository, if
any, or None if none. any, or None if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_manifest_by_digest(cls, namespace_name, repo_name, digest): def get_manifest_by_digest(self, namespace_name, repo_name, digest):
""" """
Returns the manifest matching the given digest under the matching repository, if any, or None if Returns the manifest matching the given digest under the matching repository, if any, or None if
none. none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def delete_manifest_by_digest(cls, namespace_name, repo_name, digest): def delete_manifest_by_digest(self, namespace_name, repo_name, digest):
""" """
Deletes the manifest with the associated digest (if any) and returns all removed tags that Deletes the manifest with the associated digest (if any) and returns all removed tags that
pointed to that manifest. If the manifest was not found, returns an empty list. pointed to that manifest. If the manifest was not found, returns an empty list.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_docker_v1_metadata_by_tag(cls, namespace_name, repo_name, tag_name): def get_docker_v1_metadata_by_tag(self, namespace_name, repo_name, tag_name):
""" """
Returns the Docker V1 metadata associated with the tag with the given name under the matching Returns the Docker V1 metadata associated with the tag with the given name under the matching
repository, if any. If none, returns None. repository, if any. If none, returns None.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_docker_v1_metadata_by_image_id(cls, namespace_name, repo_name, docker_image_ids): def get_docker_v1_metadata_by_image_id(self, namespace_name, repo_name, docker_image_ids):
""" """
Returns a map of Docker V1 metadata for each given image ID, matched under the repository with Returns a map of Docker V1 metadata for each given image ID, matched under the repository with
the given namespace and name. Returns an empty map if the matching repository was not found. the given namespace and name. Returns an empty map if the matching repository was not found.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_parents_docker_v1_metadata(cls, namespace_name, repo_name, docker_image_id): def get_parents_docker_v1_metadata(self, namespace_name, repo_name, docker_image_id):
""" """
Returns an ordered list containing the Docker V1 metadata for each parent of the image with the Returns an ordered list containing the Docker V1 metadata for each parent of the image with the
given docker ID under the matching repository. Returns an empty list if the image was not found. given docker ID under the matching repository. Returns an empty list if the image was not found.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_manifest_and_update_tag(cls, namespace_name, repo_name, tag_name, manifest_digest, def create_manifest_and_update_tag(self, namespace_name, repo_name, tag_name, manifest_digest,
manifest_bytes): manifest_bytes):
""" """
Creates a new manifest with the given digest and byte data, and assigns the tag with the given Creates a new manifest with the given digest and byte data, and assigns the tag with the given
name under the matching repository to it. name under the matching repository to it.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def synthesize_v1_image(cls, repository, storage, image_id, created, comment, command, def synthesize_v1_image(self, repository, storage, image_id, created, comment, command,
compat_json, parent_image_id): compat_json, parent_image_id):
""" """
Synthesizes a V1 image under the specified repository, pointing to the given storage and returns Synthesizes a V1 image under the specified repository, pointing to the given storage and returns
the V1 metadata for the synthesized image. the V1 metadata for the synthesized image.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def save_manifest(cls, namespace_name, repo_name, tag_name, leaf_layer_docker_id, manifest_digest, def save_manifest(self, namespace_name, repo_name, tag_name, leaf_layer_docker_id,
manifest_bytes): manifest_digest, manifest_bytes):
""" """
Saves a manifest pointing to the given leaf image, with the given manifest, under the matching Saves a manifest pointing to the given leaf image, with the given manifest, under the matching
repository as a tag with the given name. repository as a tag with the given name.
Returns a boolean whether or not the tag was newly created or not. Returns a boolean whether or not the tag was newly created or not.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def repository_tags(cls, namespace_name, repo_name, limit, offset): def repository_tags(self, namespace_name, repo_name, limit, offset):
""" """
Returns the active tags under the repository with the given name and namespace. Returns the active tags under the repository with the given name and namespace.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_visible_repositories(cls, username, limit, offset): def get_visible_repositories(self, username, limit, offset):
""" """
Returns the repositories visible to the user with the given username, if any. Returns the repositories visible to the user with the given username, if any.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_blob_upload(cls, namespace_name, repo_name, upload_uuid, location_name, def create_blob_upload(self, namespace_name, repo_name, upload_uuid, location_name, storage_metadata):
storage_metadata):
""" """
Creates a blob upload under the matching repository with the given UUID and metadata. Creates a blob upload under the matching repository with the given UUID and metadata.
Returns whether the matching repository exists. Returns whether the matching repository exists.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def blob_upload_by_uuid(cls, namespace_name, repo_name, upload_uuid): def blob_upload_by_uuid(self, namespace_name, repo_name, upload_uuid):
""" """
Searches for a blob upload with the given UUID under the given repository and returns it or None Searches for a blob upload with the given UUID under the given repository and returns it or None
if none. if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def update_blob_upload(cls, blob_upload): def update_blob_upload(self, blob_upload):
""" """
Saves any changes to the blob upload object given to the backing data store. Saves any changes to the blob upload object given to the backing data store.
Fields that can change: Fields that can change:
@ -212,61 +214,53 @@ class DockerRegistryV2DataInterface(object):
- chunk_count - chunk_count
- sha_state - sha_state
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def delete_blob_upload(cls, namespace_name, repo_name, uuid): def delete_blob_upload(self, namespace_name, repo_name, uuid):
""" """
Deletes the blob upload with the given uuid under the matching repository. If none, does Deletes the blob upload with the given uuid under the matching repository. If none, does
nothing. nothing.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_blob_and_temp_tag(cls, namespace_name, repo_name, blob_digest, blob_upload, def create_blob_and_temp_tag(self, namespace_name, repo_name, blob_digest, blob_upload,
expiration_sec): expiration_sec):
""" """
Creates a blob and links a temporary tag with the specified expiration to it under the matching Creates a blob and links a temporary tag with the specified expiration to it under the matching
repository. repository.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def lookup_blobs_by_digest(cls, namespace_name, repo_name, digests): def get_blob_by_digest(self, namespace_name, repo_name, digest):
"""
Returns all the blobs with matching digests found under the matching repository. If the
repository doesn't exist, returns {}.
"""
raise NotImplementedError()
@classmethod
def get_blob_by_digest(cls, namespace_name, repo_name, digest):
""" """
Returns the blob with the given digest under the matching repository or None if none. Returns the blob with the given digest under the matching repository or None if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def save_bittorrent_pieces(cls, blob, piece_size, piece_bytes): def save_bittorrent_pieces(self, blob, piece_size, piece_bytes):
""" """
Saves the BitTorrent piece hashes for the given blob. Saves the BitTorrent piece hashes for the given blob.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def create_manifest_labels(cls, namespace_name, repo_name, manifest_digest, labels): def create_manifest_labels(self, namespace_name, repo_name, manifest_digest, labels):
""" """
Creates a new labels for the provided manifest. Creates a new labels for the provided manifest.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_blob_path(cls, blob): def get_blob_path(self, blob):
""" """
Once everything is moved over, this could be in util.registry and not even touch the database. Once everything is moved over, this could be in util.registry and not even touch the database.
""" """
raise NotImplementedError() pass
class PreOCIModel(DockerRegistryV2DataInterface): class PreOCIModel(DockerRegistryV2DataInterface):
@ -274,58 +268,40 @@ class PreOCIModel(DockerRegistryV2DataInterface):
PreOCIModel implements the data model for the v2 Docker Registry protocol using a database schema PreOCIModel implements the data model for the v2 Docker Registry protocol using a database schema
before it was changed to support the OCI specification. before it was changed to support the OCI specification.
""" """
@classmethod def create_repository(self, namespace_name, repo_name, creating_user=None):
def create_repository(cls, namespace_name, repo_name, creating_user=None):
return model.repository.create_repository(namespace_name, repo_name, creating_user) return model.repository.create_repository(namespace_name, repo_name, creating_user)
@classmethod def repository_is_public(self, namespace_name, repo_name):
def repository_is_public(cls, namespace_name, repo_name):
return model.repository.repository_is_public(namespace_name, repo_name) return model.repository.repository_is_public(namespace_name, repo_name)
@classmethod def get_repository(self, namespace_name, repo_name):
def _repository_for_repo(cls, repo):
""" Returns a Repository object representing the repo data model instance given. """
return Repository(
id=repo.id,
name=repo.name,
namespace_name=repo.namespace_user.username,
description=repo.description,
is_public=model.repository.is_repository_public(repo)
)
@classmethod
def get_repository(cls, namespace_name, repo_name):
repo = model.repository.get_repository(namespace_name, repo_name) repo = model.repository.get_repository(namespace_name, repo_name)
if repo is None: if repo is None:
return None return None
return cls._repository_for_repo(repo) return _repository_for_repo(repo)
@classmethod def has_active_tag(self, namespace_name, repo_name, tag_name):
def has_active_tag(cls, namespace_name, repo_name, tag_name):
try: try:
model.tag.get_active_tag(namespace_name, repo_name, tag_name) model.tag.get_active_tag(namespace_name, repo_name, tag_name)
return True return True
except database.RepositoryTag.DoesNotExist: except database.RepositoryTag.DoesNotExist:
return False return False
@classmethod def get_manifest_by_tag(self, namespace_name, repo_name, tag_name):
def get_manifest_by_tag(cls, namespace_name, repo_name, tag_name):
try: try:
manifest = model.tag.load_tag_manifest(namespace_name, repo_name, tag_name) manifest = model.tag.load_tag_manifest(namespace_name, repo_name, tag_name)
return ManifestJSON(digest=manifest.digest, json=manifest.json_data, media_type=_MEDIA_TYPE) return ManifestJSON(digest=manifest.digest, json=manifest.json_data, media_type=_MEDIA_TYPE)
except model.InvalidManifestException: except model.InvalidManifestException:
return None return None
@classmethod def get_manifest_by_digest(self, namespace_name, repo_name, digest):
def get_manifest_by_digest(cls, namespace_name, repo_name, digest):
try: try:
manifest = model.tag.load_manifest_by_digest(namespace_name, repo_name, digest) manifest = model.tag.load_manifest_by_digest(namespace_name, repo_name, digest)
return ManifestJSON(digest=digest, json=manifest.json_data, media_type=_MEDIA_TYPE) return ManifestJSON(digest=digest, json=manifest.json_data, media_type=_MEDIA_TYPE)
except model.InvalidManifestException: except model.InvalidManifestException:
return None return None
@classmethod def delete_manifest_by_digest(self, namespace_name, repo_name, digest):
def delete_manifest_by_digest(cls, namespace_name, repo_name, digest):
def _tag_view(tag): def _tag_view(tag):
return Tag( return Tag(
name=tag.name, name=tag.name,
@ -339,56 +315,31 @@ class PreOCIModel(DockerRegistryV2DataInterface):
tags = model.tag.delete_manifest_by_digest(namespace_name, repo_name, digest) tags = model.tag.delete_manifest_by_digest(namespace_name, repo_name, digest)
return [_tag_view(tag) for tag in tags] return [_tag_view(tag) for tag in tags]
@classmethod def get_docker_v1_metadata_by_tag(self, namespace_name, repo_name, tag_name):
def _docker_v1_metadata(cls, namespace_name, repo_name, repo_image):
"""
Returns a DockerV1Metadata object for the given image under the repository with the given
namespace and name. Note that the namespace and name are passed here as an optimization, and are
*not checked* against the image.
"""
return DockerV1Metadata(
namespace_name=namespace_name,
repo_name=repo_name,
image_id=repo_image.docker_image_id,
checksum=repo_image.v1_checksum,
content_checksum=repo_image.storage.content_checksum,
compat_json=repo_image.v1_json_metadata,
created=repo_image.created,
comment=repo_image.comment,
command=repo_image.command,
# TODO: make sure this isn't needed anywhere, as it is expensive to lookup
parent_image_id=None,
)
@classmethod
def get_docker_v1_metadata_by_tag(cls, namespace_name, repo_name, tag_name):
try: try:
repo_img = model.tag.get_tag_image(namespace_name, repo_name, tag_name, include_storage=True) repo_img = model.tag.get_tag_image(namespace_name, repo_name, tag_name, include_storage=True)
return cls._docker_v1_metadata(namespace_name, repo_name, repo_img) return _docker_v1_metadata(namespace_name, repo_name, repo_img)
except DataModelException: except DataModelException:
return None return None
@classmethod def get_docker_v1_metadata_by_image_id(self, namespace_name, repo_name, docker_image_ids):
def get_docker_v1_metadata_by_image_id(cls, namespace_name, repo_name, docker_image_ids):
repo = model.repository.get_repository(namespace_name, repo_name) repo = model.repository.get_repository(namespace_name, repo_name)
if repo is None: if repo is None:
return {} return {}
images_query = model.image.lookup_repository_images(repo, docker_image_ids) images_query = model.image.lookup_repository_images(repo, docker_image_ids)
return {image.docker_image_id: cls._docker_v1_metadata(namespace_name, repo_name, image) return {image.docker_image_id: _docker_v1_metadata(namespace_name, repo_name, image)
for image in images_query} for image in images_query}
@classmethod def get_parents_docker_v1_metadata(self, namespace_name, repo_name, docker_image_id):
def get_parents_docker_v1_metadata(cls, namespace_name, repo_name, docker_image_id):
repo_image = model.image.get_repo_image(namespace_name, repo_name, docker_image_id) repo_image = model.image.get_repo_image(namespace_name, repo_name, docker_image_id)
if repo_image is None: if repo_image is None:
return [] return []
parents = model.image.get_parent_images(namespace_name, repo_name, repo_image) parents = model.image.get_parent_images(namespace_name, repo_name, repo_image)
return [cls._docker_v1_metadata(namespace_name, repo_name, image) for image in parents] return [_docker_v1_metadata(namespace_name, repo_name, image) for image in parents]
@classmethod def create_manifest_and_update_tag(self, namespace_name, repo_name, tag_name, manifest_digest,
def create_manifest_and_update_tag(cls, namespace_name, repo_name, tag_name, manifest_digest,
manifest_bytes): manifest_bytes):
try: try:
model.tag.associate_generated_tag_manifest(namespace_name, repo_name, tag_name, model.tag.associate_generated_tag_manifest(namespace_name, repo_name, tag_name,
@ -397,8 +348,7 @@ class PreOCIModel(DockerRegistryV2DataInterface):
# It's already there! # It's already there!
pass pass
@classmethod def synthesize_v1_image(self, repository, storage, image_id, created, comment, command,
def synthesize_v1_image(cls, repository, storage, image_id, created, comment, command,
compat_json, parent_image_id): compat_json, parent_image_id):
repo = model.repository.get_repository(repository.namespace_name, repository.name) repo = model.repository.get_repository(repository.namespace_name, repository.name)
if repo is None: if repo is None:
@ -417,18 +367,16 @@ class PreOCIModel(DockerRegistryV2DataInterface):
repo_image = model.image.synthesize_v1_image(repo, storage_obj, image_id, created, comment, repo_image = model.image.synthesize_v1_image(repo, storage_obj, image_id, created, comment,
command, compat_json, parent_image) command, compat_json, parent_image)
return cls._docker_v1_metadata(repo.namespace_user.username, repo.name, repo_image) return _docker_v1_metadata(repo.namespace_user.username, repo.name, repo_image)
@classmethod def save_manifest(self, namespace_name, repo_name, tag_name, leaf_layer_docker_id,
def save_manifest(cls, namespace_name, repo_name, tag_name, leaf_layer_docker_id, manifest_digest, manifest_digest, manifest_bytes):
manifest_bytes):
(_, newly_created) = model.tag.store_tag_manifest(namespace_name, repo_name, tag_name, (_, newly_created) = model.tag.store_tag_manifest(namespace_name, repo_name, tag_name,
leaf_layer_docker_id, manifest_digest, leaf_layer_docker_id, manifest_digest,
manifest_bytes) manifest_bytes)
return newly_created return newly_created
@classmethod def repository_tags(self, namespace_name, repo_name, limit, offset):
def repository_tags(cls, namespace_name, repo_name, limit, offset):
def _tag_view(tag): def _tag_view(tag):
return Tag( return Tag(
name=tag.name, name=tag.name,
@ -443,15 +391,12 @@ class PreOCIModel(DockerRegistryV2DataInterface):
tags_query = tags_query.limit(limit).offset(offset) tags_query = tags_query.limit(limit).offset(offset)
return [_tag_view(tag) for tag in tags_query] return [_tag_view(tag) for tag in tags_query]
@classmethod def get_visible_repositories(self, username, limit, offset):
def get_visible_repositories(cls, username, limit, offset):
query = model.repository.get_visible_repositories(username, include_public=(username is None)) query = model.repository.get_visible_repositories(username, include_public=(username is None))
query = query.limit(limit).offset(offset) query = query.limit(limit).offset(offset)
return [cls._repository_for_repo(repo) for repo in query] return [_repository_for_repo(repo) for repo in query]
@classmethod def create_blob_upload(self, namespace_name, repo_name, upload_uuid, location_name, storage_metadata):
def create_blob_upload(cls, namespace_name, repo_name, upload_uuid, location_name,
storage_metadata):
try: try:
model.blob.initiate_upload(namespace_name, repo_name, upload_uuid, location_name, model.blob.initiate_upload(namespace_name, repo_name, upload_uuid, location_name,
storage_metadata) storage_metadata)
@ -459,8 +404,7 @@ class PreOCIModel(DockerRegistryV2DataInterface):
except database.Repository.DoesNotExist: except database.Repository.DoesNotExist:
return False return False
@classmethod def blob_upload_by_uuid(self, namespace_name, repo_name, upload_uuid):
def blob_upload_by_uuid(cls, namespace_name, repo_name, upload_uuid):
try: try:
found = model.blob.get_blob_upload(namespace_name, repo_name, upload_uuid) found = model.blob.get_blob_upload(namespace_name, repo_name, upload_uuid)
except model.InvalidBlobUpload: except model.InvalidBlobUpload:
@ -480,8 +424,7 @@ class PreOCIModel(DockerRegistryV2DataInterface):
storage_metadata=found.storage_metadata, storage_metadata=found.storage_metadata,
) )
@classmethod def update_blob_upload(self, blob_upload):
def update_blob_upload(cls, blob_upload):
# Lookup the blob upload object. # Lookup the blob upload object.
try: try:
blob_upload_record = model.blob.get_blob_upload(blob_upload.repo_namespace_name, blob_upload_record = model.blob.get_blob_upload(blob_upload.repo_namespace_name,
@ -498,21 +441,18 @@ class PreOCIModel(DockerRegistryV2DataInterface):
blob_upload_record.sha_state = blob_upload.sha_state blob_upload_record.sha_state = blob_upload.sha_state
blob_upload_record.save() blob_upload_record.save()
@classmethod def delete_blob_upload(self, namespace_name, repo_name, uuid):
def delete_blob_upload(cls, namespace_name, repo_name, uuid):
try: try:
found = model.blob.get_blob_upload(namespace_name, repo_name, uuid) found = model.blob.get_blob_upload(namespace_name, repo_name, uuid)
found.delete_instance() found.delete_instance()
except model.InvalidBlobUpload: except model.InvalidBlobUpload:
return return
@classmethod def create_blob_and_temp_tag(self, namespace_name, repo_name, blob_digest, blob_upload,
def create_blob_and_temp_tag(cls, namespace_name, repo_name, blob_digest, blob_upload,
expiration_sec): expiration_sec):
location_obj = model.storage.get_image_location_for_name(blob_upload.location_name) location_obj = model.storage.get_image_location_for_name(blob_upload.location_name)
blob_record = model.blob.store_blob_record_and_temp_link(namespace_name, repo_name, blob_record = model.blob.store_blob_record_and_temp_link(namespace_name, repo_name,
blob_digest, blob_digest, location_obj.id,
location_obj.id,
blob_upload.byte_count, blob_upload.byte_count,
expiration_sec, expiration_sec,
blob_upload.uncompressed_byte_count) blob_upload.uncompressed_byte_count)
@ -523,8 +463,7 @@ class PreOCIModel(DockerRegistryV2DataInterface):
locations=[blob_upload.location_name], locations=[blob_upload.location_name],
) )
@classmethod def lookup_blobs_by_digest(self, namespace_name, repo_name, digests):
def lookup_blobs_by_digest(cls, namespace_name, repo_name, digests):
def _blob_view(blob_record): def _blob_view(blob_record):
return Blob( return Blob(
uuid=blob_record.uuid, uuid=blob_record.uuid,
@ -539,8 +478,7 @@ class PreOCIModel(DockerRegistryV2DataInterface):
query = model.storage.lookup_repo_storages_by_content_checksum(repo, digests) query = model.storage.lookup_repo_storages_by_content_checksum(repo, digests)
return {storage.content_checksum: _blob_view(storage) for storage in query} return {storage.content_checksum: _blob_view(storage) for storage in query}
@classmethod def get_blob_by_digest(self, namespace_name, repo_name, digest):
def get_blob_by_digest(cls, namespace_name, repo_name, digest):
try: try:
blob_record = model.blob.get_repo_blob_by_digest(namespace_name, repo_name, digest) blob_record = model.blob.get_repo_blob_by_digest(namespace_name, repo_name, digest)
return Blob( return Blob(
@ -552,13 +490,11 @@ class PreOCIModel(DockerRegistryV2DataInterface):
except model.BlobDoesNotExist: except model.BlobDoesNotExist:
return None return None
@classmethod def save_bittorrent_pieces(self, blob, piece_size, piece_bytes):
def save_bittorrent_pieces(cls, blob, piece_size, piece_bytes):
blob_record = model.storage.get_storage_by_uuid(blob.uuid) blob_record = model.storage.get_storage_by_uuid(blob.uuid)
model.storage.save_torrent_info(blob_record, piece_size, piece_bytes) model.storage.save_torrent_info(blob_record, piece_size, piece_bytes)
@classmethod def create_manifest_labels(self, namespace_name, repo_name, manifest_digest, labels):
def create_manifest_labels(cls, namespace_name, repo_name, manifest_digest, labels):
if not labels: if not labels:
# No point in doing anything more. # No point in doing anything more.
return return
@ -568,7 +504,42 @@ class PreOCIModel(DockerRegistryV2DataInterface):
model.label.create_manifest_label(tag_manifest, label.key, label.value, label.source_type, model.label.create_manifest_label(tag_manifest, label.key, label.value, label.source_type,
label.media_type) label.media_type)
@classmethod def get_blob_path(self, blob):
def get_blob_path(cls, blob):
blob_record = model.storage.get_storage_by_uuid(blob.uuid) blob_record = model.storage.get_storage_by_uuid(blob.uuid)
return model.storage.get_layer_path(blob_record) return model.storage.get_layer_path(blob_record)
def _docker_v1_metadata(namespace_name, repo_name, repo_image):
"""
Returns a DockerV1Metadata object for the given Pre-OCI repo_image under the
repository with the given namespace and name. Note that the namespace and
name are passed here as an optimization, and are *not checked* against the
image.
"""
return DockerV1Metadata(
namespace_name=namespace_name,
repo_name=repo_name,
image_id=repo_image.docker_image_id,
checksum=repo_image.v1_checksum,
content_checksum=repo_image.storage.content_checksum,
compat_json=repo_image.v1_json_metadata,
created=repo_image.created,
comment=repo_image.comment,
command=repo_image.command,
# TODO: make sure this isn't needed anywhere, as it is expensive to lookup
parent_image_id=None,
)
def _repository_for_repo(repo):
""" Returns a Repository object representing the Pre-OCI data model repo instance given. """
return Repository(
id=repo.id,
name=repo.name,
namespace_name=repo.namespace_user.username,
description=repo.description,
is_public=model.repository.is_repository_public(repo)
)
pre_oci_model = PreOCIModel()

View file

@ -1,8 +1,13 @@
import json
from abc import ABCMeta, abstractmethod
from collections import namedtuple from collections import namedtuple
from six import add_metaclass
from data import model from data import model
from image.docker.v1 import DockerV1Metadata from image.docker.v1 import DockerV1Metadata
import json
class DerivedImage(namedtuple('DerivedImage', ['ref', 'blob', 'internal_source_image_db_id'])): class DerivedImage(namedtuple('DerivedImage', ['ref', 'blob', 'internal_source_image_db_id'])):
""" """
@ -31,105 +36,106 @@ class TorrentInfo(namedtuple('TorrentInfo', ['piece_length', 'pieces'])):
""" """
@add_metaclass(ABCMeta)
class VerbsDataInterface(object): class VerbsDataInterface(object):
""" """
Interface that represents all data store interactions required by the registry's custom HTTP Interface that represents all data store interactions required by the registry's custom HTTP
verbs. verbs.
""" """
@classmethod @abstractmethod
def repository_is_public(cls, namespace_name, repo_name): def repository_is_public(self, namespace_name, repo_name):
""" """
Returns a boolean for whether the repository with the given name and namespace is public. Returns a boolean for whether the repository with the given name and namespace is public.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_manifest_layers_with_blobs(cls, repo_image): def get_manifest_layers_with_blobs(self, repo_image):
""" """
Returns the full set of manifest layers and their associated blobs starting at the given Returns the full set of manifest layers and their associated blobs starting at the given
repository image and working upwards to the root image. repository image and working upwards to the root image.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_blob_path(cls, blob): def get_blob_path(self, blob):
""" """
Returns the storage path for the given blob. Returns the storage path for the given blob.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_derived_image_signature(cls, derived_image, signer_name): def get_derived_image_signature(self, derived_image, signer_name):
""" """
Returns the signature associated with the derived image and a specific signer or None if none. Returns the signature associated with the derived image and a specific signer or None if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def set_derived_image_signature(cls, derived_image, signer_name, signature): def set_derived_image_signature(self, derived_image, signer_name, signature):
""" """
Sets the calculated signature for the given derived image and signer to that specified. Sets the calculated signature for the given derived image and signer to that specified.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def delete_derived_image(cls, derived_image): def delete_derived_image(self, derived_image):
""" """
Deletes a derived image and all of its storage. Deletes a derived image and all of its storage.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def set_blob_size(cls, blob, size): def set_blob_size(self, blob, size):
""" """
Sets the size field on a blob to the value specified. Sets the size field on a blob to the value specified.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_repo_blob_by_digest(cls, namespace_name, repo_name, digest): def get_repo_blob_by_digest(self, namespace_name, repo_name, digest):
""" """
Returns the blob with the given digest under the matching repository or None if none. Returns the blob with the given digest under the matching repository or None if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_torrent_info(cls, blob): def get_torrent_info(self, blob):
""" """
Returns the torrent information associated with the given blob or None if none. Returns the torrent information associated with the given blob or None if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def set_torrent_info(cls, blob, piece_length, pieces): def set_torrent_info(self, blob, piece_length, pieces):
""" """
Sets the torrent infomation associated with the given blob to that specified. Sets the torrent infomation associated with the given blob to that specified.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def lookup_derived_image(cls, repo_image, verb, varying_metadata=None): def lookup_derived_image(self, repo_image, verb, varying_metadata=None):
""" """
Looks up the derived image for the given repository image, verb and optional varying metadata Looks up the derived image for the given repository image, verb and optional varying metadata
and returns it or None if none. and returns it or None if none.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def lookup_or_create_derived_image(cls, repo_image, verb, location, varying_metadata=None): def lookup_or_create_derived_image(self, repo_image, verb, location, varying_metadata=None):
""" """
Looks up the derived image for the given repository image, verb and optional varying metadata Looks up the derived image for the given repository image, verb and optional varying metadata
and returns it. If none exists, a new derived image is created. and returns it. If none exists, a new derived image is created.
""" """
raise NotImplementedError() pass
@classmethod @abstractmethod
def get_tag_image(cls, namespace_name, repo_name, tag_name): def get_tag_image(self, namespace_name, repo_name, tag_name):
""" """
Returns the image associated with the live tag with the given name under the matching repository Returns the image associated with the live tag with the given name under the matching repository
or None if none. or None if none.
""" """
raise NotImplementedError() pass
class PreOCIModel(VerbsDataInterface): class PreOCIModel(VerbsDataInterface):
@ -138,35 +144,10 @@ class PreOCIModel(VerbsDataInterface):
before it was changed to support the OCI specification. before it was changed to support the OCI specification.
""" """
@classmethod def repository_is_public(self, namespace_name, repo_name):
def repository_is_public(cls, namespace_name, repo_name):
return model.repository.repository_is_public(namespace_name, repo_name) return model.repository.repository_is_public(namespace_name, repo_name)
@classmethod def get_manifest_layers_with_blobs(self, repo_image):
def _docker_v1_metadata(cls, namespace_name, repo_name, repo_image):
"""
Returns a DockerV1Metadata object for the given image under the repository with the given
namespace and name. Note that the namespace and name are passed here as an optimization, and are
*not checked* against the image. Also note that we only fill in the localized data needed by
verbs.
"""
return DockerV1Metadata(
namespace_name=namespace_name,
repo_name=repo_name,
image_id=repo_image.docker_image_id,
checksum=repo_image.v1_checksum,
compat_json=repo_image.v1_json_metadata,
created=repo_image.created,
comment=repo_image.comment,
command=repo_image.command,
# Note: These are not needed in verbs and are expensive to load, so we just skip them.
content_checksum=None,
parent_image_id=None,
)
@classmethod
def get_manifest_layers_with_blobs(cls, repo_image):
repo_image_record = model.image.get_image_by_id(repo_image.repository.namespace_name, repo_image_record = model.image.get_image_by_id(repo_image.repository.namespace_name,
repo_image.repository.name, repo_image.repository.name,
repo_image.image_id) repo_image.image_id)
@ -186,16 +167,15 @@ class PreOCIModel(VerbsDataInterface):
yield ImageWithBlob( yield ImageWithBlob(
image_id=parent.docker_image_id, image_id=parent.docker_image_id,
blob=cls._blob(parent.storage), blob=_blob(parent.storage),
repository=repo_image.repository, repository=repo_image.repository,
compat_metadata=metadata, compat_metadata=metadata,
v1_metadata=cls._docker_v1_metadata(repo_image.repository.namespace_name, v1_metadata=_docker_v1_metadata(repo_image.repository.namespace_name,
repo_image.repository.name, parent), repo_image.repository.name, parent),
internal_db_id=parent.id, internal_db_id=parent.id,
) )
@classmethod def get_derived_image_signature(self, derived_image, signer_name):
def get_derived_image_signature(cls, derived_image, signer_name):
storage = model.storage.get_storage_by_uuid(derived_image.blob.uuid) storage = model.storage.get_storage_by_uuid(derived_image.blob.uuid)
signature_entry = model.storage.lookup_storage_signature(storage, signer_name) signature_entry = model.storage.lookup_storage_signature(storage, signer_name)
if signature_entry is None: if signature_entry is None:
@ -203,41 +183,35 @@ class PreOCIModel(VerbsDataInterface):
return signature_entry.signature return signature_entry.signature
@classmethod def set_derived_image_signature(self, derived_image, signer_name, signature):
def set_derived_image_signature(cls, derived_image, signer_name, signature):
storage = model.storage.get_storage_by_uuid(derived_image.blob.uuid) storage = model.storage.get_storage_by_uuid(derived_image.blob.uuid)
signature_entry = model.storage.find_or_create_storage_signature(storage, signer_name) signature_entry = model.storage.find_or_create_storage_signature(storage, signer_name)
signature_entry.signature = signature signature_entry.signature = signature
signature_entry.uploading = False signature_entry.uploading = False
signature_entry.save() signature_entry.save()
@classmethod def delete_derived_image(self, derived_image):
def delete_derived_image(cls, derived_image):
model.image.delete_derived_storage_by_uuid(derived_image.blob.uuid) model.image.delete_derived_storage_by_uuid(derived_image.blob.uuid)
@classmethod def set_blob_size(self, blob, size):
def set_blob_size(cls, blob, size):
storage_entry = model.storage.get_storage_by_uuid(blob.uuid) storage_entry = model.storage.get_storage_by_uuid(blob.uuid)
storage_entry.image_size = size storage_entry.image_size = size
storage_entry.uploading = False storage_entry.uploading = False
storage_entry.save() storage_entry.save()
@classmethod def get_blob_path(self, blob):
def get_blob_path(cls, blob):
blob_record = model.storage.get_storage_by_uuid(blob.uuid) blob_record = model.storage.get_storage_by_uuid(blob.uuid)
return model.storage.get_layer_path(blob_record) return model.storage.get_layer_path(blob_record)
@classmethod def get_repo_blob_by_digest(self, namespace_name, repo_name, digest):
def get_repo_blob_by_digest(cls, namespace_name, repo_name, digest):
try: try:
blob_record = model.blob.get_repo_blob_by_digest(namespace_name, repo_name, digest) blob_record = model.blob.get_repo_blob_by_digest(namespace_name, repo_name, digest)
except model.BlobDoesNotExist: except model.BlobDoesNotExist:
return None return None
return cls._blob(blob_record) return _blob(blob_record)
@classmethod def get_torrent_info(self, blob):
def get_torrent_info(cls, blob):
blob_record = model.storage.get_storage_by_uuid(blob.uuid) blob_record = model.storage.get_storage_by_uuid(blob.uuid)
try: try:
@ -250,51 +224,24 @@ class PreOCIModel(VerbsDataInterface):
piece_length=torrent_info.piece_length, piece_length=torrent_info.piece_length,
) )
@classmethod def set_torrent_info(self, blob, piece_length, pieces):
def set_torrent_info(cls, blob, piece_length, pieces):
blob_record = model.storage.get_storage_by_uuid(blob.uuid) blob_record = model.storage.get_storage_by_uuid(blob.uuid)
model.storage.save_torrent_info(blob_record, piece_length, pieces) model.storage.save_torrent_info(blob_record, piece_length, pieces)
@classmethod def lookup_derived_image(self, repo_image, verb, varying_metadata=None):
def lookup_derived_image(cls, repo_image, verb, varying_metadata=None):
blob_record = model.image.find_derived_storage_for_image(repo_image.internal_db_id, verb, blob_record = model.image.find_derived_storage_for_image(repo_image.internal_db_id, verb,
varying_metadata) varying_metadata)
if blob_record is None: if blob_record is None:
return None return None
return cls._derived_image(blob_record, repo_image) return _derived_image(blob_record, repo_image)
@classmethod def lookup_or_create_derived_image(self, repo_image, verb, location, varying_metadata=None):
def _derived_image(cls, blob_record, repo_image): blob_record = model.image.find_or_create_derived_storage(repo_image.internal_db_id, verb,
return DerivedImage( location, varying_metadata)
ref=repo_image.internal_db_id, return _derived_image(blob_record, repo_image)
blob=cls._blob(blob_record),
internal_source_image_db_id=repo_image.internal_db_id,
)
@classmethod def get_tag_image(self, namespace_name, repo_name, tag_name):
def _blob(cls, blob_record):
if hasattr(blob_record, 'locations'):
locations = blob_record.locations
else:
locations = model.storage.get_storage_locations(blob_record.uuid)
return Blob(
uuid=blob_record.uuid,
size=blob_record.image_size,
uncompressed_size=blob_record.uncompressed_size,
uploading=blob_record.uploading,
locations=locations,
)
@classmethod
def lookup_or_create_derived_image(cls, repo_image, verb, location, varying_metadata=None):
blob_record = model.image.find_or_create_derived_storage(repo_image.internal_db_id, verb, location,
varying_metadata)
return cls._derived_image(blob_record, repo_image)
@classmethod
def get_tag_image(cls, namespace_name, repo_name, tag_name):
try: try:
found = model.tag.get_tag_image(namespace_name, repo_name, tag_name, include_storage=True) found = model.tag.get_tag_image(namespace_name, repo_name, tag_name, include_storage=True)
except model.DataModelException: except model.DataModelException:
@ -308,15 +255,68 @@ class PreOCIModel(VerbsDataInterface):
return ImageWithBlob( return ImageWithBlob(
image_id=found.docker_image_id, image_id=found.docker_image_id,
blob=cls._blob(found.storage), blob=_blob(found.storage),
repository=RepositoryReference( repository=RepositoryReference(
namespace_name=namespace_name, namespace_name=namespace_name,
name=repo_name, name=repo_name,
id=found.repository_id, id=found.repository_id,
), ),
compat_metadata=metadata, compat_metadata=metadata,
v1_metadata=cls._docker_v1_metadata(namespace_name, repo_name, found), v1_metadata=_docker_v1_metadata(namespace_name, repo_name, found),
internal_db_id=found.id, internal_db_id=found.id,
) )
pre_oci_model = PreOCIModel()
def _docker_v1_metadata(namespace_name, repo_name, repo_image):
"""
Returns a DockerV1Metadata object for the given Pre-OCI repo_image under the
repository with the given namespace and name. Note that the namespace and
name are passed here as an optimization, and are *not checked* against the
image.
"""
return DockerV1Metadata(
namespace_name=namespace_name,
repo_name=repo_name,
image_id=repo_image.docker_image_id,
checksum=repo_image.v1_checksum,
compat_json=repo_image.v1_json_metadata,
created=repo_image.created,
comment=repo_image.comment,
command=repo_image.command,
# Note: These are not needed in verbs and are expensive to load, so we just skip them.
content_checksum=None,
parent_image_id=None,
)
def _derived_image(blob_record, repo_image):
"""
Returns a DerivedImage object for the given Pre-OCI data model blob and repo_image instance.
"""
return DerivedImage(
ref=repo_image.internal_db_id,
blob=_blob(blob_record),
internal_source_image_db_id=repo_image.internal_db_id,
)
def _blob(blob_record):
"""
Returns a Blob object for the given Pre-OCI data model blob instance.
"""
if hasattr(blob_record, 'locations'):
locations = blob_record.locations
else:
locations = model.storage.get_storage_locations(blob_record.uuid)
return Blob(
uuid=blob_record.uuid,
size=blob_record.image_size,
uncompressed_size=blob_record.uncompressed_size,
uploading=blob_record.uploading,
locations=locations,
)

View file

@ -5,7 +5,7 @@ from flask import Blueprint, jsonify, abort, request, make_response
from jwt import get_unverified_header from jwt import get_unverified_header
from app import app from app import app
from data.interfaces.key_server import PreOCIModel as model, ServiceKeyDoesNotExist from data.interfaces.key_server import pre_oci_model as model, ServiceKeyDoesNotExist
from data.model.log import log_action from data.model.log import log_action
from util.security import jwtutil from util.security import jwtutil

View file

@ -6,7 +6,7 @@ from functools import wraps
from flask import request, make_response, jsonify, session from flask import request, make_response, jsonify, session
from data.interfaces.v1 import PreOCIModel as model from data.interfaces.v1 import pre_oci_model as model
from app import authentication, userevents, metric_queue from app import authentication, userevents, metric_queue
from auth.auth import process_auth, generate_signed_token from auth.auth import process_auth, generate_signed_token
from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token

View file

@ -14,7 +14,7 @@ from auth.permissions import (ReadRepositoryPermission,
ModifyRepositoryPermission) ModifyRepositoryPermission)
from auth.registry_jwt_auth import get_granted_username from auth.registry_jwt_auth import get_granted_username
from data import model, database from data import model, database
from data.interfaces.v1 import PreOCIModel as model from data.interfaces.v1 import pre_oci_model as model
from digest import checksums from digest import checksums
from endpoints.v1 import v1_bp from endpoints.v1 import v1_bp
from endpoints.decorators import anon_protect from endpoints.decorators import anon_protect

View file

@ -9,7 +9,7 @@ from auth.auth import process_auth
from auth.permissions import (ReadRepositoryPermission, from auth.permissions import (ReadRepositoryPermission,
ModifyRepositoryPermission) ModifyRepositoryPermission)
from data import model from data import model
from data.interfaces.v1 import PreOCIModel as model from data.interfaces.v1 import pre_oci_model as model
from endpoints.common import parse_repository_name from endpoints.common import parse_repository_name
from endpoints.decorators import anon_protect from endpoints.decorators import anon_protect
from endpoints.v1 import v1_bp from endpoints.v1 import v1_bp

View file

@ -8,7 +8,7 @@ import resumablehashlib
from app import storage, app from app import storage, app
from auth.registry_jwt_auth import process_registry_jwt_auth from auth.registry_jwt_auth import process_registry_jwt_auth
from data import database from data import database
from data.interfaces.v2 import PreOCIModel as model from data.interfaces.v2 import pre_oci_model as model
from digest import digest_tools from digest import digest_tools
from endpoints.common import parse_repository_name from endpoints.common import parse_repository_name
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write, get_input_stream from endpoints.v2 import v2_bp, require_repo_read, require_repo_write, get_input_stream

View file

@ -3,7 +3,7 @@ from flask import jsonify
from auth.registry_jwt_auth import process_registry_jwt_auth, get_granted_entity from auth.registry_jwt_auth import process_registry_jwt_auth, get_granted_entity
from endpoints.decorators import anon_protect from endpoints.decorators import anon_protect
from endpoints.v2 import v2_bp, paginate from endpoints.v2 import v2_bp, paginate
from data.interfaces.v2 import PreOCIModel as model from data.interfaces.v2 import pre_oci_model as model
@v2_bp.route('/_catalog', methods=['GET']) @v2_bp.route('/_catalog', methods=['GET'])
@process_registry_jwt_auth() @process_registry_jwt_auth()

View file

@ -8,7 +8,7 @@ import features
from app import docker_v2_signing_key, app, metric_queue from app import docker_v2_signing_key, app, metric_queue
from auth.registry_jwt_auth import process_registry_jwt_auth from auth.registry_jwt_auth import process_registry_jwt_auth
from data.interfaces.v2 import PreOCIModel as model, Label from data.interfaces.v2 import pre_oci_model as model, Label
from digest import digest_tools from digest import digest_tools
from endpoints.common import parse_repository_name from endpoints.common import parse_repository_name
from endpoints.decorators import anon_protect from endpoints.decorators import anon_protect

View file

@ -5,7 +5,7 @@ from endpoints.common import parse_repository_name
from endpoints.v2 import v2_bp, require_repo_read, paginate from endpoints.v2 import v2_bp, require_repo_read, paginate
from endpoints.v2.errors import NameUnknown from endpoints.v2.errors import NameUnknown
from endpoints.decorators import anon_protect from endpoints.decorators import anon_protect
from data.interfaces.v2 import PreOCIModel as model from data.interfaces.v2 import pre_oci_model as model
@v2_bp.route('/<repopath:repository>/tags/list', methods=['GET']) @v2_bp.route('/<repopath:repository>/tags/list', methods=['GET'])
@parse_repository_name() @parse_repository_name()

View file

@ -11,7 +11,7 @@ from auth.permissions import (ModifyRepositoryPermission, ReadRepositoryPermissi
CreateRepositoryPermission) CreateRepositoryPermission)
from endpoints.v2 import v2_bp from endpoints.v2 import v2_bp
from endpoints.decorators import anon_protect from endpoints.decorators import anon_protect
from data.interfaces.v2 import PreOCIModel as model from data.interfaces.v2 import pre_oci_model as model
from util.cache import no_cache from util.cache import no_cache
from util.names import parse_namespace_repository, REPOSITORY_NAME_REGEX from util.names import parse_namespace_repository, REPOSITORY_NAME_REGEX
from util.security.registry_jwt import generate_bearer_token, build_context_and_subject from util.security.registry_jwt import generate_bearer_token, build_context_and_subject

View file

@ -10,7 +10,7 @@ from auth.auth import process_auth
from auth.auth_context import get_authenticated_user from auth.auth_context import get_authenticated_user
from auth.permissions import ReadRepositoryPermission from auth.permissions import ReadRepositoryPermission
from data import database from data import database
from data.interfaces.verbs import PreOCIModel as model from data.interfaces.verbs import pre_oci_model as model
from endpoints.common import route_show_if, parse_repository_name from endpoints.common import route_show_if, parse_repository_name
from endpoints.decorators import anon_protect from endpoints.decorators import anon_protect
from endpoints.trackhelper import track_and_log from endpoints.trackhelper import track_and_log