diff --git a/endpoints/v1/__init__.py b/endpoints/v1/__init__.py index 18ef430c4..11a08fb85 100644 --- a/endpoints/v1/__init__.py +++ b/endpoints/v1/__init__.py @@ -28,6 +28,8 @@ def ping(): return response -from endpoints.v1 import index -from endpoints.v1 import registry -from endpoints.v1 import tag +from endpoints.v1 import ( + index, + registry, + tag, +) diff --git a/endpoints/v1/index.py b/endpoints/v1/index.py index 3708178f3..d2889fd3e 100644 --- a/endpoints/v1/index.py +++ b/endpoints/v1/index.py @@ -13,11 +13,11 @@ from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission, ReadRepositoryPermission, CreateRepositoryPermission, repository_read_grant, repository_write_grant) from auth.signedgrant import generate_signed_token -from data.interfaces.v1 import pre_oci_model as model from endpoints.common import parse_repository_name from endpoints.decorators import anon_protect, anon_allowed from endpoints.notificationhelper import spawn_notification from endpoints.v1 import v1_bp +from endpoints.v1.models_pre_oci import pre_oci_model as model from util.audit import track_and_log from util.http import abort from util.names import REPOSITORY_NAME_REGEX diff --git a/endpoints/v1/models_interface.py b/endpoints/v1/models_interface.py new file mode 100644 index 000000000..f6b2660f3 --- /dev/null +++ b/endpoints/v1/models_interface.py @@ -0,0 +1,219 @@ +from abc import ABCMeta, abstractmethod +from collections import namedtuple + +from six import add_metaclass + + +class Repository(namedtuple('Repository', ['id', 'name', 'namespace_name', 'description', + 'is_public', 'kind'])): + """ + Repository represents a namespaced collection of tags. + :type id: int + :type name: string + :type namespace_name: string + :type description: string + :type is_public: bool + :type kind: string + """ + + +@add_metaclass(ABCMeta) +class DockerRegistryV1DataInterface(object): + """ + Interface that represents all data store interactions required by a Docker Registry v1. + """ + + @abstractmethod + 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 + repository or None if no image was found. + """ + pass + + @abstractmethod + 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, + including the checksum and its V1 JSON metadata. + """ + pass + + @abstractmethod + def update_docker_v1_metadata(self, namespace_name, repo_name, image_id, created_date_str, + comment, command, compat_json, parent_image_id=None): + """ + Updates various pieces of V1 metadata associated with a particular image. + """ + pass + + @abstractmethod + 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 + repository. + """ + pass + + @abstractmethod + 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. + """ + pass + + @abstractmethod + 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. + """ + pass + + @abstractmethod + 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. + """ + pass + + @abstractmethod + 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. + """ + pass + + @abstractmethod + def get_image_size(self, namespace_name, repo_name, image_id): + """ + Returns the wire size of the image with the given Docker V1 ID. + """ + pass + + @abstractmethod + 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. + """ + pass + + @abstractmethod + 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 + given Docker V1 ID. + """ + pass + + @abstractmethod + def repository_exists(self, namespace_name, repo_name): + """ + Returns whether the repository with the given name and namespace exists. + """ + pass + + @abstractmethod + 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 + the user with the given username, or creating a new one if no existing image matches. + """ + pass + + @abstractmethod + 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 + ID. + """ + pass + + @abstractmethod + def list_tags(self, namespace_name, repo_name): + """ + Returns all the tags defined in the repository with the given namespace and name. + """ + pass + + @abstractmethod + 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 + Docker V1 ID. + """ + pass + + @abstractmethod + 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 + matching repository, or None if none. + """ + pass + + @abstractmethod + def delete_tag(self, namespace_name, repo_name, tag_name): + """ + Deletes the given tag from the given repository. + """ + pass + + @abstractmethod + def load_token(self, token): + """ + Loads the data associated with the given (deprecated) access token, and, if + found returns True. + """ + pass + + @abstractmethod + def verify_robot(self, username, token): + """ + Returns True if the given robot username and token match an existing robot + account. + """ + pass + + @abstractmethod + def change_user_password(self, user, new_password): + """ + Changes the password associated with the given user. + """ + pass + + @abstractmethod + def get_repository(self, namespace_name, repo_name): + """ + Returns the repository with the given name under the given namespace or None + if none. + """ + pass + + @abstractmethod + def create_repository(self, namespace_name, repo_name, user=None): + """ + Creates a new repository under the given namespace with the given name, for + the given user. + """ + pass + + @abstractmethod + def repository_is_public(self, namespace_name, repo_name): + """ + Returns whether the repository with the given name under the given namespace + is public. If no matching repository was found, returns False. + """ + pass + + @abstractmethod + def validate_oauth_token(self, token): + """ Returns whether the given OAuth token validates. """ + pass + + @abstractmethod + def get_sorted_matching_repositories(self, search_term, filter_username=None, offset=0, limit=25): + """ + Returns a sorted list of repositories matching the given search term. + """ + pass diff --git a/data/interfaces/v1.py b/endpoints/v1/models_pre_oci.py similarity index 54% rename from data/interfaces/v1.py rename to endpoints/v1/models_pre_oci.py index abc3fb858..d326c1dfc 100644 --- a/data/interfaces/v1.py +++ b/endpoints/v1/models_pre_oci.py @@ -1,229 +1,9 @@ -from abc import ABCMeta, abstractmethod -from collections import namedtuple - -from six import add_metaclass - from app import app, storage as store from data import model -from data.model import db_transaction +from endpoints.v1.models_interface import DockerRegistryV1DataInterface, Repository from util.morecollections import AttrDict -class Repository(namedtuple('Repository', ['id', 'name', 'namespace_name', 'description', - 'is_public', 'kind'])): - """ - Repository represents a namespaced collection of tags. - :type id: int - :type name: string - :type namespace_name: string - :type description: string - :type is_public: bool - :type kind: string - """ - - -@add_metaclass(ABCMeta) -class DockerRegistryV1DataInterface(object): - """ - Interface that represents all data store interactions required by a Docker Registry v1. - """ - - @abstractmethod - 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 - repository or None if no image was found. - """ - pass - - @abstractmethod - 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, - including the checksum and its V1 JSON metadata. - """ - pass - - @abstractmethod - def update_docker_v1_metadata(self, namespace_name, repo_name, image_id, created_date_str, - comment, command, compat_json, parent_image_id=None): - """ - Updates various pieces of V1 metadata associated with a particular image. - """ - pass - - @abstractmethod - 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 - repository. - """ - pass - - @abstractmethod - 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. - """ - pass - - @abstractmethod - 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. - """ - pass - - @abstractmethod - 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. - """ - pass - - @abstractmethod - 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. - """ - pass - - @abstractmethod - def get_image_size(self, namespace_name, repo_name, image_id): - """ - Returns the wire size of the image with the given Docker V1 ID. - """ - pass - - @abstractmethod - 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. - """ - pass - - @abstractmethod - 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 - given Docker V1 ID. - """ - pass - - @abstractmethod - def repository_exists(self, namespace_name, repo_name): - """ - Returns whether the repository with the given name and namespace exists. - """ - pass - - @abstractmethod - 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 - the user with the given username, or creating a new one if no existing image matches. - """ - pass - - @abstractmethod - 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 - ID. - """ - pass - - @abstractmethod - def list_tags(self, namespace_name, repo_name): - """ - Returns all the tags defined in the repository with the given namespace and name. - """ - pass - - @abstractmethod - 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 - Docker V1 ID. - """ - pass - - @abstractmethod - 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 - matching repository, or None if none. - """ - pass - - @abstractmethod - def delete_tag(self, namespace_name, repo_name, tag_name): - """ - Deletes the given tag from the given repository. - """ - pass - - @abstractmethod - def load_token(self, token): - """ - Loads the data associated with the given (deprecated) access token, and, if - found returns True. - """ - pass - - @abstractmethod - def verify_robot(self, username, token): - """ - Returns True if the given robot username and token match an existing robot - account. - """ - pass - - @abstractmethod - def change_user_password(self, user, new_password): - """ - Changes the password associated with the given user. - """ - pass - - @abstractmethod - def get_repository(self, namespace_name, repo_name): - """ - Returns the repository with the given name under the given namespace or None - if none. - """ - pass - - @abstractmethod - def create_repository(self, namespace_name, repo_name, user=None): - """ - Creates a new repository under the given namespace with the given name, for - the given user. - """ - pass - - @abstractmethod - def repository_is_public(self, namespace_name, repo_name): - """ - Returns whether the repository with the given name under the given namespace - is public. If no matching repository was found, returns False. - """ - pass - - @abstractmethod - def validate_oauth_token(self, token): - """ Returns whether the given OAuth token validates. """ - pass - - @abstractmethod - def get_sorted_matching_repositories(self, search_term, filter_username=None, offset=0, limit=25): - """ - Returns a sorted list of repositories matching the given search term. - """ - pass - - class PreOCIModel(DockerRegistryV1DataInterface): """ PreOCIModel implements the data model for the v1 Docker Registry protocol using a database schema @@ -274,7 +54,7 @@ class PreOCIModel(DockerRegistryV1DataInterface): if repo_image is None or repo_image.storage is None: return - with db_transaction(): + with model.db_transaction(): repo_image.storage.content_checksum = content_checksum repo_image.v1_checksum = checksum repo_image.storage.save() diff --git a/endpoints/v1/registry.py b/endpoints/v1/registry.py index 845fd464a..ca5425c27 100644 --- a/endpoints/v1/registry.py +++ b/endpoints/v1/registry.py @@ -14,9 +14,9 @@ from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission) from auth.registry_jwt_auth import get_granted_username from data import model, database -from data.interfaces.v1 import pre_oci_model as model from digest import checksums from endpoints.v1 import v1_bp +from endpoints.v1.models_pre_oci import pre_oci_model as model from endpoints.decorators import anon_protect from util.http import abort, exact_abort from util.registry.filelike import SocketReader diff --git a/endpoints/v1/tag.py b/endpoints/v1/tag.py index 00f6d3bcb..4746247ed 100644 --- a/endpoints/v1/tag.py +++ b/endpoints/v1/tag.py @@ -8,10 +8,10 @@ from auth.decorators import process_auth from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission) from data import model -from data.interfaces.v1 import pre_oci_model as model from endpoints.common import parse_repository_name from endpoints.decorators import anon_protect from endpoints.v1 import v1_bp +from endpoints.v1.models_pre_oci import pre_oci_model as model from util.audit import track_and_log from util.names import TAG_ERROR, TAG_REGEX