Sync v1 and v2 content when the registry support it. #119
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
|
||
from pulp_docker.common import constants, models | ||
from pulp_docker.plugins import registry | ||
from pulp_docker.plugins.importers import v1_sync | ||
|
||
|
||
_logger = logging.getLogger(__name__) | ||
|
@@ -50,6 +51,75 @@ def __init__(self, repo=None, conduit=None, config=None, | |
working_dir, constants.IMPORTER_TYPE_ID) | ||
self.description = _('Syncing Docker Repository') | ||
|
||
download_config = nectar_config.importer_config_to_nectar_config(config.flatten()) | ||
upstream_name = config.get(constants.CONFIG_KEY_UPSTREAM_NAME) | ||
url = config.get(importer_constants.KEY_FEED) | ||
|
||
# Create a Repository object to interact with. | ||
self.index_repository = registry.V2Repository( | ||
upstream_name, download_config, url, working_dir) | ||
self.v1_index_repository = registry.V1Repository(upstream_name, download_config, url, | ||
working_dir) | ||
|
||
v2_found = self.index_repository.api_version_check() | ||
v1_found = self.v1_index_repository.api_version_check() | ||
|
||
if v2_found: | ||
_logger.debug(_('v2 API found')) | ||
self.add_child(V2SyncStep(repo=repo, conduit=conduit, config=config, | ||
working_dir=working_dir)) | ||
if v1_found: | ||
_logger.debug(_('v1 API found')) | ||
self.add_child(v1_sync.SyncStep(repo=repo, conduit=conduit, config=config, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume that it is deliberate that neither v1/v2 can be turned off/on? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Austin wrote:
Indeed. Randy Barlow |
||
working_dir=working_dir)) | ||
if not any((v1_found, v2_found)): | ||
msg = _('This feed URL is not a Docker v1 or v2 endpoint: %(url)s'.format(url=url)) | ||
_logger.error(msg) | ||
raise ValueError(msg) | ||
|
||
def sync(self): | ||
""" | ||
actually initiate the sync | ||
|
||
:return: a final sync report | ||
:rtype: pulp.plugins.model.SyncReport | ||
""" | ||
self.process_lifecycle() | ||
return self._build_final_report() | ||
|
||
|
||
class V2SyncStep(PluginStep): | ||
""" | ||
This PluginStep is the primary entry point into a repository sync against a Docker v2 registry. | ||
""" | ||
# The sync will fail if these settings are not provided in the config | ||
required_settings = (constants.CONFIG_KEY_UPSTREAM_NAME, importer_constants.KEY_FEED) | ||
|
||
def __init__(self, repo=None, conduit=None, config=None, | ||
working_dir=None): | ||
""" | ||
This method initializes the SyncStep. It first validates the config to ensure that the | ||
required keys are present. It then constructs some needed items (such as a download config), | ||
and determines whether the feed URL is a Docker v2 registry or not. If it is, it | ||
instantiates child tasks that are appropriate for syncing a v2 registry, and if it is not it | ||
raises a NotImplementedError. | ||
|
||
:param repo: repository to sync | ||
:type repo: pulp.plugins.model.Repository | ||
:param conduit: sync conduit to use | ||
:type conduit: pulp.plugins.conduits.repo_sync.RepoSyncConduit | ||
:param config: config object for the sync | ||
:type config: pulp.plugins.config.PluginCallConfiguration | ||
:param working_dir: full path to the directory in which transient files | ||
should be stored before being moved into long-term | ||
storage. This should be deleted by the caller after | ||
step processing is complete. | ||
:type working_dir: basestring | ||
""" | ||
super(V2SyncStep, self).__init__(constants.SYNC_STEP_MAIN, repo, conduit, config, | ||
working_dir, constants.IMPORTER_TYPE_ID) | ||
self.description = _('Syncing Docker Repository') | ||
|
||
self._validate(config) | ||
download_config = nectar_config.importer_config_to_nectar_config(config.flatten()) | ||
upstream_name = config.get(constants.CONFIG_KEY_UPSTREAM_NAME) | ||
|
@@ -93,16 +163,6 @@ def generate_download_requests(self): | |
yield self.index_repository.create_blob_download_request(digest, | ||
self.get_working_dir()) | ||
|
||
def sync(self): | ||
""" | ||
actually initiate the sync | ||
|
||
:return: a final sync report | ||
:rtype: pulp.plugins.model.SyncReport | ||
""" | ||
self.process_lifecycle() | ||
return self._build_final_report() | ||
|
||
@classmethod | ||
def _validate(cls, config): | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ class V1Repository(object): | |
DOCKER_ENDPOINT_HEADER = 'x-docker-endpoints' | ||
IMAGES_PATH = '/v1/repositories/%s/images' | ||
TAGS_PATH = '/v1/repositories/%s/tags' | ||
API_VERSION_CHECK_PATH = '/v1/_ping' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API_V1_CHECK_PATH is better, this endpoint returns no information about v2. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Austin wrote:
Note that this is a class attribute of the V1Registry object, so v1 is Randy Barlow |
||
|
||
def __init__(self, name, download_config, registry_url, working_dir): | ||
""" | ||
|
@@ -104,6 +105,23 @@ def _parse_response_headers(self, headers): | |
if self.DOCKER_ENDPOINT_HEADER in headers: | ||
self.endpoint = headers[self.DOCKER_ENDPOINT_HEADER] | ||
|
||
def api_version_check(self): | ||
""" | ||
Make a call to the registry URL's /v1/_ping API call to determine if the registry supports | ||
API v1. | ||
|
||
:return: True if the v1 API is found, else False | ||
:rtype: bool | ||
""" | ||
_logger.debug('Determining if the registry URL can do v1 of the Docker API.') | ||
|
||
try: | ||
self._get_single_path(self.API_VERSION_CHECK_PATH) | ||
except IOError: | ||
return False | ||
|
||
return True | ||
|
||
def add_auth_header(self, request): | ||
""" | ||
Given a download request, add an Authorization header if we have an | ||
|
@@ -267,26 +285,30 @@ def api_version_check(self): | |
""" | ||
Make a call to the registry URL's /v2/ API call to determine if the registry supports API | ||
v2. If it does not, raise NotImplementedError. If it does, return. | ||
|
||
:return: True if the v2 API is found, else False | ||
:rtype: bool | ||
""" | ||
_logger.debug('Determining if the registry URL can do v2 of the Docker API.') | ||
exception = NotImplementedError('%s is not a Docker v2 registry.' % self.registry_url) | ||
|
||
try: | ||
headers, body = self._get_path(self.API_VERSION_CHECK_PATH) | ||
except IOError: | ||
raise exception | ||
return False | ||
|
||
try: | ||
version = headers['Docker-Distribution-API-Version'] | ||
if version != "registry/2.0": | ||
raise exception | ||
return False | ||
_logger.debug(_('The docker registry is using API version: %(v)s') % {'v': version}) | ||
except KeyError: | ||
# If the Docker-Distribution-API-Version header isn't present, we will assume that this | ||
# is a valid Docker 2.0 API server so that simple file-based webservers can serve as our | ||
# remote feed. | ||
pass | ||
|
||
return True | ||
|
||
def create_blob_download_request(self, digest, destination_dir): | ||
""" | ||
Return a DownloadRequest instance for the given blob digest. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to determine which docker api version here? For the use case of syncing both, if we could do it here, we could then avoid having v1 and v2 sync as child steps of the same step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@asmacdo Unfortunately, this would prevent us from having one progress report per sync operation, which would not be compatible with Pulp's API.