diff --git a/.travis/publish_client_pypi.sh b/.travis/publish_client_pypi.sh index b29796647f..800f65919c 100755 --- a/.travis/publish_client_pypi.sh +++ b/.travis/publish_client_pypi.sh @@ -12,7 +12,7 @@ if [[ $DESCRIPTION == 'tags/'$REPORTED_VERSION ]]; then export VERSION=${REPORTED_VERSION} else export EPOCH="$(date +%s)" - export VERSION=${REPORTED_VERSION}.dev.${EPOCH} + export VERSION=${REPORTED_VERSION}.${EPOCH} fi export response=$(curl --write-out %{http_code} --silent --output /dev/null https://pypi.org/project/pulp-file-client/$VERSION/) diff --git a/.travis/script.sh b/.travis/script.sh index a00e109d0f..dca60d29b0 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -42,7 +42,7 @@ fi if [ "$TEST" = 'bindings' ]; then COMMIT_MSG=$(git show HEAD^2 -s) - export PULP_BINDINGS_PR_NUMBER=$(echo $COMMIT_MSG | grep -oP 'Required\ PR:\ https\:\/\/github\.com\/pulp\/pulp-swagger-codegen\/pull\/(\d+)' | awk -F'/' '{print $7}') + export PULP_BINDINGS_PR_NUMBER=$(echo $COMMIT_MSG | grep -oP 'Required\ PR:\ https\:\/\/github\.com\/pulp\/pulp-openapi-generator\/pull\/(\d+)' | awk -F'/' '{print $7}') cd .. git clone https://github.com/pulp/pulp-openapi-generator.git diff --git a/.travis/test_bindings.py b/.travis/test_bindings.py index e69de29bb2..2d7acbd91e 100644 --- a/.travis/test_bindings.py +++ b/.travis/test_bindings.py @@ -0,0 +1,166 @@ +from pulpcore.client.pulpcore import (ApiClient as CoreApiClient, ArtifactsApi, Configuration, + Repository, RepositoriesApi, RepositoriesVersionsApi, + TasksApi, UploadsApi) +from pulpcore.client.pulp_file import (ApiClient as FileApiClient, ContentFilesApi, + FileContent, DistributionsFileApi, + FileDistribution, PublicationsFileApi, + RemotesFileApi, FileRemote, RepositorySyncURL, + FilePublication) +from pprint import pprint +from time import sleep +import hashlib +import os +import requests +from tempfile import NamedTemporaryFile + + +def monitor_task(task_href): + """Polls the Task API until the task is in a completed state. + + Prints the task details and a success or failure message. Exits on failure. + + Args: + task_href(str): The href of the task to monitor + + Returns: + list[str]: List of hrefs that identify resource created by the task + + """ + completed = ['completed', 'failed', 'canceled'] + task = tasks.read(task_href) + while task.state not in completed: + sleep(2) + task = tasks.read(task_href) + pprint(task) + if task.state == 'completed': + print("The task was successfful.") + return task.created_resources + else: + print("The task did not finish successfully.") + exit() + + +def upload_file_in_chunks(file_path): + """Uploads a file using the Uploads API + + The file located at 'file_path' is uploaded in chunks of 200kb. + + Args: + file_path (str): path to the file being uploaded to Pulp + + Returns: + upload object + """ + with open(file_path, 'rb') as full_file: + size = os.stat(file_path).st_size + chunk_size = 200000 + offset = 0 + md5hasher = hashlib.new('md5') + while True: + chunk = full_file.read(chunk_size) + if not chunk: + break + actual_chunk_size = len(chunk) + content_range = 'bytes {start}-{end}/{size}'.format(start=offset, + end=offset+actual_chunk_size-1, + size=size) + with NamedTemporaryFile() as file_chunk: + file_chunk.write(chunk) + if not offset: + upload = uploads.start_upload(file=file_chunk.name, content_range=content_range) + else: + upload = uploads.continue_upload(upload_href=upload.href, + file=file_chunk.name, + content_range=content_range) + offset += chunk_size + md5hasher.update(chunk) + uploads.finish_upload(upload_href=upload.href, md5=md5hasher.hexdigest()) + return upload + + +# Configure HTTP basic authorization: basic +configuration = Configuration() +configuration.username = 'admin' +configuration.password = 'admin' +configuration.safe_chars_for_path_param = '/' + +core_client = CoreApiClient(configuration) +file_client = FileApiClient(configuration) + +# Create api clients for all resource types +artifacts = ArtifactsApi(core_client) +repositories = RepositoriesApi(core_client) +repoversions = RepositoriesVersionsApi(core_client) +filecontent = ContentFilesApi(file_client) +filedistributions = DistributionsFileApi(core_client) +filepublications = PublicationsFileApi(file_client) +fileremotes = RemotesFileApi(file_client) +tasks = TasksApi(core_client) +uploads = UploadsApi(core_client) + + +# Test creating an Artifact from a 1mb file uploaded in 200kb chunks +with NamedTemporaryFile() as downloaded_file: + response = requests.get( + 'https://repos.fedorapeople.org/repos/pulp/pulp/demo_repos/test_bandwidth_repo/' + 'pulp-large_1mb_test-packageA-0.1.1-1.fc14.noarch.rpm') + response.raise_for_status() + downloaded_file.write(response.content) + upload = upload_file_in_chunks(downloaded_file.name) + pprint(upload) + artifact = artifacts.create(upload=upload.href) + pprint(artifact) + +# Create a File Remote +remote_url = 'https://repos.fedorapeople.org/pulp/pulp/demo_repos/test_file_repo/PULP_MANIFEST' +remote_data = FileRemote(name='bar25', url=remote_url) +file_remote = fileremotes.create(remote_data) +pprint(file_remote) + +# Create a Repository +repository_data = Repository(name='foo25') +repository = repositories.create(repository_data) +pprint(repository) + +# Sync a Repository +repository_sync_data = RepositorySyncURL(repository=repository.href) +sync_response = fileremotes.sync(file_remote.href, repository_sync_data) + +pprint(sync_response) + +# Monitor the sync task +created_resources = monitor_task(sync_response.task) + +repository_version_1 = repoversions.read(created_resources[0]) +pprint(repository_version_1) + +# Create an artifact from a local file +artifact = artifacts.create(file='test_bindings.py') +pprint(artifact) + +# Create a FileContent from the artifact +file_data = FileContent(relative_path='foo.tar.gz', artifact=artifact.href) +filecontent = filecontent.create(file_data) +pprint(filecontent) + +# Add the new FileContent to a repository version +repo_version_data = {'add_content_units': [filecontent.href]} +repo_version_response = repoversions.create(repository.href, repo_version_data) + +# Monitor the repo version creation task +created_resources = monitor_task(repo_version_response.task) + +repository_version_2 = repoversions.read(created_resources[0]) +pprint(repository_version_2) + +# Create a publication from the latest version of the repository +publish_data = FilePublication(repository=repository.href) +publish_response = filepublications.create(publish_data) + +# Monitor the publish task +created_resources = monitor_task(publish_response.task) +publication_href = created_resources[0] + +distribution_data = FileDistribution(name='baz25', base_path='foo25', publication=publication_href) +distribution = filedistributions.create(distribution_data) +pprint(distribution) diff --git a/pulpcore/app/openapigenerator.py b/pulpcore/app/openapigenerator.py index 7cb378ac58..ea16230293 100644 --- a/pulpcore/app/openapigenerator.py +++ b/pulpcore/app/openapigenerator.py @@ -259,8 +259,12 @@ def get_operation(self, operation_keys): parameters = body + query parameters = filter_none(parameters) parameters = self.add_manual_parameters(parameters) - - operation_id = self.get_operation_id(operation_keys) + if 'bindings' in self.request.query_params: + operation_id = self.overrides.get('operation_id', '') + if not operation_id: + operation_id = operation_keys[-1] + else: + operation_id = self.get_operation_id(operation_keys) summary, description = self.get_summary_and_description() security = self.get_security() assert security is None or isinstance(security, list), "security must be a list of " \ @@ -312,3 +316,28 @@ def get_summary(self, operation_keys): return f'Delete {article} {resource}' elif operation == 'partial_update': return f'Partially update {article} {resource}' + + def get_tags(self, operation_keys): + """Get a list of tags for this operation. + + Tags determine how operations relate with each other, and in the UI each tag will show as + a group containing the operations that use it. If not provided in overrides, a list of one + tag will be returned. The single tag is built from operation keys. + + Args: + operation_keys (tuple[str]): an array of keys derived from the pathdescribing the + hierarchical layout of this view in the API; e.g. ``('snippets', 'list')``, + ``('snippets', 'retrieve')``, etc. + + Returns: + list[str] of tags + """ + tags = self.overrides.get('tags') + if not tags: + if len(operation_keys) > 2: + if len(operation_keys) > 3: + del operation_keys[-3] + operation_keys[0] = "{key}:".format(key=operation_keys[0]) + tags = [' '.join(operation_keys[:-1])] + + return tags diff --git a/pulpcore/app/viewsets/upload.py b/pulpcore/app/viewsets/upload.py index c95ecfcd49..e2f3e83c8e 100644 --- a/pulpcore/app/viewsets/upload.py +++ b/pulpcore/app/viewsets/upload.py @@ -24,7 +24,7 @@ class UploadViewSet(GenericViewSet, ChunkedUploadView, CreateModelMixin, Destroy 'within the file.') @swagger_auto_schema(operation_summary="Start an Upload", - operation_id="uploads_create", + operation_id="start_upload", request_body=UploadPUTSerializer, manual_parameters=[content_range_parameter], responses={200: UploadSerializer}) @@ -35,7 +35,7 @@ def put_create(self, *args, **kwargs): return super().put(*args, **kwargs) @swagger_auto_schema(operation_summary="Continue an Upload", - operation_id="uploads_update", + operation_id="continue_upload", request_body=UploadPUTSerializer, manual_parameters=[content_range_parameter], responses={200: UploadSerializer}) @@ -46,7 +46,7 @@ def put_update(self, *args, **kwargs): return super().put(*args, **kwargs) @swagger_auto_schema(operation_summary="Finish an Upload", - operation_id="uploads_finish", + operation_id="finish_upload", request_body=UploadFinishSerializer, responses={200: UploadSerializer}) def post(self, *args, **kwargs): @@ -58,7 +58,7 @@ def post(self, *args, **kwargs): return super().post(*args, **kwargs) @swagger_auto_schema(operation_summary="Create an Upload", - operation_id="uploads_create_and_check", + operation_id="create_and_check_upload", request_body=UploadPOSTSerializer, responses={200: UploadSerializer}) def post_create(self, *args, **kwargs):