From 2f7289f4a499b8a3bc6a6b6c9c451f36a4ff4425 Mon Sep 17 00:00:00 2001 From: Matthias Dellweg Date: Fri, 10 Feb 2023 17:59:30 +0100 Subject: [PATCH] Split out glue library as pulp-glue package fixes #628 --- .bumpversion.cfg | 4 + .ci/scripts/validate_commit_message.py | 2 +- .github/workflows/release.yml | 4 + CHANGES/628.feature | 1 + pulp-glue/README.md | 2 + pulp-glue/pulp_glue/ansible/__init__.py | 0 .../pulp_glue}/ansible/context.py | 4 +- pulp-glue/pulp_glue/ansible/py.typed | 0 pulp-glue/pulp_glue/common/__init__.py | 1 + pulp-glue/pulp_glue/common/context.py | 832 +++++++++++++++++ pulp-glue/pulp_glue/common/i18n.py | 15 + .../pulp_glue}/common/openapi.py | 6 +- pulp-glue/pulp_glue/common/py.typed | 0 pulp-glue/pulp_glue/container/__init__.py | 0 .../pulp_glue}/container/context.py | 4 +- pulp-glue/pulp_glue/container/py.typed | 0 pulp-glue/pulp_glue/core/__init__.py | 0 pulp-glue/pulp_glue/core/context.py | 506 +++++++++++ pulp-glue/pulp_glue/core/py.typed | 0 pulp-glue/pulp_glue/file/__init__.py | 0 .../pulp_glue}/file/context.py | 4 +- pulp-glue/pulp_glue/file/py.typed | 0 pulp-glue/pulp_glue/python/__init__.py | 0 .../pulp_glue}/python/context.py | 4 +- pulp-glue/pulp_glue/python/py.typed | 0 pulp-glue/pulp_glue/rpm/__init__.py | 0 .../pulp_glue}/rpm/context.py | 5 +- pulp-glue/pulp_glue/rpm/py.typed | 0 pulp-glue/setup.py | 49 + pulpcore/cli/ansible/__init__.py | 4 +- pulpcore/cli/ansible/content.py | 10 +- pulpcore/cli/ansible/distribution.py | 11 +- pulpcore/cli/ansible/remote.py | 6 +- pulpcore/cli/ansible/repository.py | 10 +- pulpcore/cli/common/__init__.py | 5 +- pulpcore/cli/common/config.py | 2 +- pulpcore/cli/common/context.py | 854 +----------------- pulpcore/cli/common/debug.py | 6 +- pulpcore/cli/common/generic.py | 5 +- pulpcore/cli/common/i18n.py | 16 +- pulpcore/cli/container/__init__.py | 4 +- pulpcore/cli/container/content.py | 12 +- pulpcore/cli/container/distribution.py | 14 +- pulpcore/cli/container/namespace.py | 4 +- pulpcore/cli/container/remote.py | 4 +- pulpcore/cli/container/repository.py | 26 +- pulpcore/cli/core/access_policy.py | 4 +- pulpcore/cli/core/artifact.py | 4 +- pulpcore/cli/core/content.py | 4 +- pulpcore/cli/core/content_guard.py | 12 +- pulpcore/cli/core/context.py | 507 +---------- pulpcore/cli/core/distribution.py | 2 +- pulpcore/cli/core/export.py | 8 +- pulpcore/cli/core/exporter.py | 8 +- pulpcore/cli/core/generic.py | 6 +- pulpcore/cli/core/group.py | 22 +- pulpcore/cli/core/importer.py | 4 +- pulpcore/cli/core/orphan.py | 5 +- pulpcore/cli/core/orphans.py | 3 +- pulpcore/cli/core/publication.py | 4 +- pulpcore/cli/core/remote.py | 2 +- pulpcore/cli/core/repository.py | 4 +- pulpcore/cli/core/role.py | 4 +- pulpcore/cli/core/show.py | 2 +- pulpcore/cli/core/signing_service.py | 4 +- pulpcore/cli/core/status.py | 2 +- pulpcore/cli/core/task.py | 6 +- pulpcore/cli/core/task_group.py | 6 +- pulpcore/cli/core/upload.py | 4 +- pulpcore/cli/core/user.py | 8 +- pulpcore/cli/core/worker.py | 4 +- pulpcore/cli/file/__init__.py | 4 +- pulpcore/cli/file/acs.py | 6 +- pulpcore/cli/file/content.py | 8 +- pulpcore/cli/file/distribution.py | 4 +- pulpcore/cli/file/publication.py | 6 +- pulpcore/cli/file/remote.py | 4 +- pulpcore/cli/file/repository.py | 16 +- pulpcore/cli/migration/__init__.py | 2 +- pulpcore/cli/migration/context.py | 4 +- pulpcore/cli/migration/plan.py | 2 +- pulpcore/cli/python/__init__.py | 2 +- pulpcore/cli/python/content.py | 8 +- pulpcore/cli/python/distribution.py | 14 +- pulpcore/cli/python/publication.py | 6 +- pulpcore/cli/python/remote.py | 6 +- pulpcore/cli/python/repository.py | 16 +- pulpcore/cli/rpm/__init__.py | 4 +- pulpcore/cli/rpm/acs.py | 6 +- pulpcore/cli/rpm/comps.py | 4 +- pulpcore/cli/rpm/content.py | 32 +- pulpcore/cli/rpm/distribution.py | 4 +- pulpcore/cli/rpm/publication.py | 6 +- pulpcore/cli/rpm/remote.py | 4 +- pulpcore/cli/rpm/repository.py | 18 +- setup.py | 3 +- test_requirements.txt | 1 + tests/test_help_pages.py | 2 +- 98 files changed, 1674 insertions(+), 1593 deletions(-) create mode 100644 CHANGES/628.feature create mode 100644 pulp-glue/README.md create mode 100644 pulp-glue/pulp_glue/ansible/__init__.py rename {pulpcore/cli => pulp-glue/pulp_glue}/ansible/context.py (97%) create mode 100644 pulp-glue/pulp_glue/ansible/py.typed create mode 100644 pulp-glue/pulp_glue/common/__init__.py create mode 100644 pulp-glue/pulp_glue/common/context.py create mode 100644 pulp-glue/pulp_glue/common/i18n.py rename {pulpcore/cli => pulp-glue/pulp_glue}/common/openapi.py (99%) create mode 100644 pulp-glue/pulp_glue/common/py.typed create mode 100644 pulp-glue/pulp_glue/container/__init__.py rename {pulpcore/cli => pulp-glue/pulp_glue}/container/context.py (98%) create mode 100644 pulp-glue/pulp_glue/container/py.typed create mode 100644 pulp-glue/pulp_glue/core/__init__.py create mode 100644 pulp-glue/pulp_glue/core/context.py create mode 100644 pulp-glue/pulp_glue/core/py.typed create mode 100644 pulp-glue/pulp_glue/file/__init__.py rename {pulpcore/cli => pulp-glue/pulp_glue}/file/context.py (97%) create mode 100644 pulp-glue/pulp_glue/file/py.typed create mode 100644 pulp-glue/pulp_glue/python/__init__.py rename {pulpcore/cli => pulp-glue/pulp_glue}/python/context.py (96%) create mode 100644 pulp-glue/pulp_glue/python/py.typed create mode 100644 pulp-glue/pulp_glue/rpm/__init__.py rename {pulpcore/cli => pulp-glue/pulp_glue}/rpm/context.py (98%) create mode 100644 pulp-glue/pulp_glue/rpm/py.typed create mode 100644 pulp-glue/setup.py diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ebe3396f2..2775d0d06 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -14,6 +14,10 @@ values = dev prod +[bumpversion:file:./pulp-glue/pulp_glue/common/__init__.py] + [bumpversion:file:./pulpcore/cli/common/__init__.py] +[bumpversion:file:./pulp-glue/setup.py] + [bumpversion:file:./setup.py] diff --git a/.ci/scripts/validate_commit_message.py b/.ci/scripts/validate_commit_message.py index 1c1c0524b..71aa6483d 100644 --- a/.ci/scripts/validate_commit_message.py +++ b/.ci/scripts/validate_commit_message.py @@ -17,7 +17,7 @@ ] NO_ISSUE = "[noissue]" CHANGELOG_EXTS = [ - f"{item['directory']}." for item in toml.load("pyproject.toml")["tool"]["towncrier"]["type"] + f".{item['directory']}" for item in toml.load("pyproject.toml")["tool"]["towncrier"]["type"] ] sha = sys.argv[1] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 874c50381..768a6d901 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,6 +25,10 @@ jobs: TWINE_USERNAME: pulp TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | + cd pulp-glue + python setup.py sdist bdist_wheel + twine upload dist/* + cd .. python setup.py sdist bdist_wheel twine upload dist/* diff --git a/CHANGES/628.feature b/CHANGES/628.feature new file mode 100644 index 000000000..30caabe20 --- /dev/null +++ b/CHANGES/628.feature @@ -0,0 +1 @@ +Added new client library `pulp-glue` as a spin off of the `pulp-cli`. diff --git a/pulp-glue/README.md b/pulp-glue/README.md new file mode 100644 index 000000000..fe5d72d17 --- /dev/null +++ b/pulp-glue/README.md @@ -0,0 +1,2 @@ +# Pulp Glue +## The version agnostic Pulp 3 client library in python diff --git a/pulp-glue/pulp_glue/ansible/__init__.py b/pulp-glue/pulp_glue/ansible/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pulpcore/cli/ansible/context.py b/pulp-glue/pulp_glue/ansible/context.py similarity index 97% rename from pulpcore/cli/ansible/context.py rename to pulp-glue/pulp_glue/ansible/context.py index 0a12466d2..6f4aab827 100644 --- a/pulpcore/cli/ansible/context.py +++ b/pulp-glue/pulp_glue/ansible/context.py @@ -1,6 +1,6 @@ from typing import IO, Any, ClassVar -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityDefinition, PluginRequirement, PulpContentContext, @@ -10,7 +10,7 @@ PulpRepositoryVersionContext, registered_repository_contexts, ) -from pulpcore.cli.common.i18n import get_translation +from pulp_glue.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulp-glue/pulp_glue/ansible/py.typed b/pulp-glue/pulp_glue/ansible/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/pulp-glue/pulp_glue/common/__init__.py b/pulp-glue/pulp_glue/common/__init__.py new file mode 100644 index 000000000..b10478020 --- /dev/null +++ b/pulp-glue/pulp_glue/common/__init__.py @@ -0,0 +1 @@ +__version__ = "0.17.0.dev" diff --git a/pulp-glue/pulp_glue/common/context.py b/pulp-glue/pulp_glue/common/context.py new file mode 100644 index 000000000..0da335e45 --- /dev/null +++ b/pulp-glue/pulp_glue/common/context.py @@ -0,0 +1,832 @@ +import datetime +import re +import sys +import time +from typing import Any, ClassVar, Dict, List, Mapping, NamedTuple, Optional, Set, Type, Union + +from packaging.version import parse as parse_version +from pulp_glue.common.i18n import get_translation +from pulp_glue.common.openapi import OpenAPI, OpenAPIError, UploadsMap +from requests import HTTPError + +translation = get_translation(__name__) +_ = translation.gettext + +DEFAULT_LIMIT = 25 +BATCH_SIZE = 25 +DATETIME_FORMATS = [ + "%Y-%m-%dT%H:%M:%S.%fZ", # Pulp format + "%Y-%m-%d", # intl. format + "%Y-%m-%d %H:%M:%S", # ... with time + "%Y-%m-%dT%H:%M:%S", # ... with time and T as a separator + "%y/%m/%d", # US format + "%y/%m/%d %h:%M:%S %p", # ... with time + "%y/%m/%dT%h:%M:%S %p", # ... with time and T as a separator + "%y/%m/%d %H:%M:%S", # ... with time 24h + "%y/%m/%dT%H:%M:%S", # ... with time 24h and T as a separator + "%x", # local format + "%x %X", # ... with time + "%xT%X", # ... with time and T as a separator +] + +href_regex = re.compile(r"\/([a-z0-9-_]+\/)+", flags=re.IGNORECASE) + + +class PreprocessedEntityDefinition(Dict[str, Any]): + pass + + +EntityDefinition = Union[Dict[str, Any], PreprocessedEntityDefinition] + + +class PluginRequirement(NamedTuple): + name: str + min: Optional[str] = None + max: Optional[str] = None + feature: Optional[str] = None + inverted: bool = False + + +class PulpException(Exception): + pass + + +class PulpNoWait(Exception): + pass + + +def _preprocess_value(value: Any) -> Any: + if isinstance(value, PulpEntityContext): + return value.pulp_href + if isinstance(value, Mapping): + return {k: _preprocess_value(v) for k, v in value.items()} + if isinstance(value, (list, tuple)): + return [_preprocess_value(item) for item in value] + return value + + +def preprocess_payload(payload: EntityDefinition) -> EntityDefinition: + if isinstance(payload, PreprocessedEntityDefinition): + return payload + + return PreprocessedEntityDefinition( + {key: _preprocess_value(value) for key, value in payload.items() if value is not None} + ) + + +class PulpContext: + """ + Abstract class for the global PulpContext object. + It is an abstraction layer for api access and output handling. + """ + + def echo(self, message: str, nl: bool = True, err: bool = False) -> None: + raise NotImplementedError("PulpContext is an abstract class.") + + def prompt(self, text: str, hide_input: bool = False) -> Any: + raise NotImplementedError("PulpContext is an abstract class.") + + def __init__( + self, + api_root: str, + api_kwargs: Dict[str, Any], + format: str, + background_tasks: bool, + timeout: int, + ) -> None: + self._api: Optional[OpenAPI] = None + self.api_path: str = api_root + "api/v3/" + self._api_kwargs = api_kwargs + self._needed_plugins: List[PluginRequirement] = [] + self.isatty: bool = sys.stdout.isatty() + + self.format: str = format + self.background_tasks: bool = background_tasks + self.timeout: int = timeout + self.start_time: Optional[datetime.datetime] = None + + def _patch_api_spec(self) -> None: + # A place for last minute fixes to the api_spec. + # WARNING: Operations are already indexed at this point. + api_spec = self.api.api_spec + if self.has_plugin(PluginRequirement("core", max="3.20.0")): + for method, path in self.api.operations.values(): + operation = api_spec["paths"][path][method] + if method == "get" and "parameters" in operation: + for parameter in operation["parameters"]: + if ( + parameter["name"] == "ordering" + and parameter["in"] == "query" + and "schema" in parameter + and parameter["schema"]["type"] == "string" + ): + parameter["schema"] = {"type": "array", "items": {"type": "string"}} + parameter["explode"] = False + parameter["style"] = "form" + if self.has_plugin(PluginRequirement("core", max="3.22.0.dev")): + for method, path in self.api.operations.values(): + operation = api_spec["paths"][path][method] + if method == "get" and "parameters" in operation: + for parameter in operation["parameters"]: + if ( + parameter["name"] in ["fields", "exclude_fields"] + and parameter["in"] == "query" + and "schema" in parameter + and parameter["schema"]["type"] == "string" + ): + parameter["schema"] = {"type": "array", "items": {"type": "string"}} + if self.has_plugin(PluginRequirement("file", max="1.11.0")): + operation = api_spec["paths"]["{file_file_alternate_content_source_href}refresh/"][ + "post" + ] + operation.pop("requestBody") + if self.has_plugin( + PluginRequirement("python", max="99.99.0.dev") + ): # TODO Add version bounds + python_remote_serializer = api_spec["components"]["schemas"]["python.PythonRemote"] + patched_python_remote_serializer = api_spec["components"]["schemas"][ + "Patchedpython.PythonRemote" + ] + for prop in ("includes", "excludes"): + python_remote_serializer["properties"][prop]["type"] = "array" + python_remote_serializer["properties"][prop]["items"] = {"type": "string"} + patched_python_remote_serializer["properties"][prop]["type"] = "array" + patched_python_remote_serializer["properties"][prop]["items"] = {"type": "string"} + + @property + def api(self) -> OpenAPI: + if self._api is None: + if self._api_kwargs.get("username") and not self._api_kwargs.get("password"): + self._api_kwargs["password"] = self.prompt("password", hide_input=True) + try: + self._api = OpenAPI(doc_path=f"{self.api_path}docs/api.json", **self._api_kwargs) + except OpenAPIError as e: + raise PulpException(str(e)) + # Rerun scheduled version checks + for plugin in self._needed_plugins: + self.needs_plugin(plugin) + self._patch_api_spec() + return self._api + + @property + def component_versions(self) -> Dict[str, str]: + result: Dict[str, str] = self.api.api_spec.get("info", {}).get("x-pulp-app-versions", {}) + return result + + def call( + self, + operation_id: str, + non_blocking: bool = False, + parameters: Optional[Dict[str, Any]] = None, + body: Optional[EntityDefinition] = None, + uploads: Optional[UploadsMap] = None, + validate_body: bool = True, + ) -> Any: + """ + Perform an API call for operation_id. + Wait for triggered tasks to finish if not background. + Returns the operation result, or the finished task. + If non_blocking, returns unfinished tasks. + """ + if parameters is not None: + parameters = preprocess_payload(parameters) + if body is not None: + body = preprocess_payload(body) + # ---- SNIP ---- + # In the future we want everything as part of the body. + if uploads: + self.echo( + _("Deprecated use of 'uploads' for operation '{operation_id}'").format( + operation_id=operation_id + ) + ) + body = body or {} + body.update(uploads) + # ---- SNIP ---- + try: + result = self.api.call( + operation_id, + parameters=parameters, + body=body, + validate_body=validate_body, + ) + except OpenAPIError as e: + raise PulpException(str(e)) + except HTTPError as e: + raise PulpException(str(e.response.text)) + # Asynchronous tasks seem to be reported by a dict containing only one key "task" + if isinstance(result, dict) and ["task"] == list(result.keys()): + task_href = result["task"] + result = self.api.call("tasks_read", parameters={"task_href": task_href}) + self.echo( + _("Started background task {task_href}").format(task_href=task_href), err=True + ) + if not non_blocking: + result = self.wait_for_task(result) + if isinstance(result, dict) and ["task_group"] == list(result.keys()): + task_group_href = result["task_group"] + result = self.api.call( + "task_groups_read", parameters={"task_group_href": task_group_href} + ) + self.echo( + _("Started background task group {task_group_href}").format( + task_group_href=task_group_href + ), + err=True, + ) + if not non_blocking: + result = self.wait_for_task_group(result) + return result + + @classmethod + def _check_task_finished(cls, task: EntityDefinition) -> bool: + task_href = task["pulp_href"] + + if task["state"] == "completed": + return True + elif task["state"] == "failed": + raise PulpException( + _("Task {task_href} failed: '{description}'").format( + task_href=task_href, + description=task["error"].get("description") or task["error"].get("reason"), + ) + ) + elif task["state"] == "canceled": + raise PulpException(_("Task {task_href} canceled").format(task_href=task_href)) + elif task["state"] in ["waiting", "running", "canceling"]: + return False + else: + raise NotImplementedError(_("Unknown task state: {state}").format(state=task["state"])) + + def _poll_task(self, task: EntityDefinition) -> EntityDefinition: + while True: + if self._check_task_finished(task): + self.echo("Done.", err=True) + return task + else: + if self.timeout: + assert isinstance(self.start_time, datetime.datetime) + if (datetime.datetime.now() - self.start_time).seconds > self.timeout: + raise PulpNoWait( + _("Waiting for task {task_href} timed out.").format( + task_href=task["pulp_href"] + ) + ) + time.sleep(1) + self.echo(".", nl=False, err=True) + task = self.api.call("tasks_read", parameters={"task_href": task["pulp_href"]}) + + def wait_for_task(self, task: EntityDefinition) -> Any: + """ + Wait for a task to finish and return the finished task object. + + Raise `PulpNoWait` on timeout, background, ctrl-c, if task failed or was canceled. + """ + self.start_time = datetime.datetime.now() + + if self.background_tasks: + raise PulpNoWait(_("Not waiting for task because --background was specified.")) + task_href = task["pulp_href"] + try: + return self._poll_task(task) + except KeyboardInterrupt: + raise PulpNoWait(_("Task {task_href} sent to background.").format(task_href=task_href)) + + def wait_for_task_group(self, task_group: EntityDefinition) -> Any: + """ + Wait for a task group to finish and return the finished task object. + + Raise `PulpNoWait` on timeout, background, ctrl-c, if tasks failed or were canceled. + """ + self.start_time = datetime.datetime.now() + + if self.background_tasks: + raise PulpNoWait("Not waiting for task group because --background was specified.") + try: + while True: + if task_group["all_tasks_dispatched"] is True: + for task in task_group["tasks"]: + task = self.api.call( + "tasks_read", parameters={"task_href": task["pulp_href"]} + ) + self.echo( + _("Waiting for task {task_href}").format(task_href=task["pulp_href"]), + err=True, + ) + self._poll_task(task) + return task_group + else: + if self.timeout: + assert isinstance(self.start_time, datetime.datetime) + if (datetime.datetime.now() - self.start_time).seconds > self.timeout: + raise PulpNoWait( + _("Waiting for task group {task_group_href} timed out.").format( + task_group_href=task_group["pulp_href"] + ) + ) + time.sleep(1) + self.echo(".", nl=False, err=True) + task_group = self.api.call( + "task_groups_read", parameters={"task_group_href": task_group["pulp_href"]} + ) + except KeyboardInterrupt: + raise PulpNoWait( + _("Task group {task_group_href} sent to background.").format( + task_group_href=task_group["pulp_href"] + ) + ) + + def has_plugin( + self, + plugin: PluginRequirement, + ) -> bool: + if not self.component_versions: + # Prior to 3.9 we do not have this information. + # Assume we have no plugin installed. + return not plugin.inverted + version: Optional[str] = self.component_versions.get(plugin.name) + if version is None: + return plugin.inverted + if plugin.min is not None: + if parse_version(version) < parse_version(plugin.min): + return plugin.inverted + if plugin.max is not None: + if parse_version(version) >= parse_version(plugin.max): + return plugin.inverted + return not plugin.inverted + + def needs_plugin( + self, + plugin: PluginRequirement, + ) -> None: + if self._api is not None: + if not self.has_plugin(plugin): + specifier = plugin.name + separator = "" + if plugin.min is not None: + specifier += f">={plugin.min}" + separator = "," + if plugin.max is not None: + specifier += f"{separator}<{plugin.max}" + feature = plugin.feature or _("this command") + if plugin.inverted: + msg = _( + "The server provides the pulp component '{specifier}'," + " which prevents the use of {feature}." + " See 'pulp status' for installed components." + ) + else: + msg = _( + "The server does not provide the pulp component '{specifier}'," + " which is needed to use {feature}." + " See 'pulp status' for installed components." + ) + raise PulpException(msg.format(specifier=specifier, feature=feature)) + else: + # Schedule for later checking + self._needed_plugins.append(plugin) + + +class PulpEntityContext: + """ + Base class for entity specific contexts. + This class provides the basic CRUD commands and ties its instances to the global + PulpContext for api access. + """ + + # Subclasses should provide appropriate values here + ENTITY: ClassVar[str] = _("entity") + ENTITIES: ClassVar[str] = _("entities") + HREF: ClassVar[str] + ID_PREFIX: ClassVar[str] + # Set of fields that can be cleared by sending 'null' + NULLABLES: ClassVar[Set[str]] = set() + # List of necessary plugin to operate such an entity in the server + NEEDS_PLUGINS: ClassVar[List[PluginRequirement]] = [] + # Subclasses can specify version dependent capabilities here + # e.g. `CAPABILITIES = { + # "feature1": [ + # PluginRequirement("file"), + # PluginRequirement("core", min_version="3.7.0") + # ] + # } + CAPABILITIES: ClassVar[Dict[str, List[PluginRequirement]]] = {} + HREF_PATTERN: str + + # Hidden values for the lazy entity lookup + _entity: Optional[EntityDefinition] + _entity_lookup: EntityDefinition + + # Subclasses for nested entities can define the parameters for there parent scope here + @property + def scope(self) -> Dict[str, Any]: + return {} + + @property + def entity(self) -> EntityDefinition: + """ + Entity property that will perform a lazy lookup once it is accessed. + You can specify lookup parameters by assigning a dictionary to it, + or assign an href to the ``pulp_href`` property. + To reset to having no attached entity you can assign ``None``. + Assigning to it will reset the lazy lookup behaviour. + """ + if self._entity is None: + if not self._entity_lookup: + raise PulpException( + _("A {entity} must be specified for this command.").format(entity=self.ENTITY) + ) + if self._entity_lookup.get("pulp_href"): + self._entity = self.show(self._entity_lookup["pulp_href"]) + else: + self._entity = self.find(**self._entity_lookup) + self._entity_lookup = {} + return self._entity + + @entity.setter + def entity(self, value: Optional[EntityDefinition]) -> None: + # Setting this property will always (lazily) retrigger retrieving the entity. + # If set multiple times in a row without reading, the criteria will be added. + if value is None: + self._entity_lookup = {} + else: + self._entity_lookup.update(value) + self._entity_lookup.pop("pulp_href", None) + self._entity = None + + @property + def pulp_href(self) -> str: + """ + Property to represent the href of the attached entity. + Assigning to it will reset the lazy lookup behaviour. + """ + return str(self.entity["pulp_href"]) + + @pulp_href.setter + def pulp_href(self, value: str) -> None: + if not href_regex.fullmatch(value): + raise PulpException( + _("'{value}' is not a valid HREF value for a {context_id}").format( + value=value, context_id=self.ENTITY + ) + ) + + # Setting this property will always (lazily) retrigger retrieving the entity. + self._entity_lookup = {"pulp_href": value} + self._entity = None + + def __init__( + self, + pulp_ctx: PulpContext, + pulp_href: Optional[str] = None, + entity: Optional[EntityDefinition] = None, + ) -> None: + assert pulp_href is None or entity is None + + self.meta: Dict[str, str] = {} + self.pulp_ctx: PulpContext = pulp_ctx + + # Add requirements to the lazy evaluated list + for req in self.NEEDS_PLUGINS: + self.pulp_ctx.needs_plugin(req) + + self._entity = None + if pulp_href is None: + self._entity_lookup = entity or {} + else: + self.pulp_href = pulp_href + + def call( + self, + operation: str, + non_blocking: bool = False, + parameters: Optional[Dict[str, Any]] = None, + body: Optional[EntityDefinition] = None, + uploads: Optional[UploadsMap] = None, + validate_body: bool = True, + ) -> Any: + operation_id: str = ( + getattr(self, operation.upper() + "_ID", None) or self.ID_PREFIX + "_" + operation + ) + return self.pulp_ctx.call( + operation_id, + non_blocking=non_blocking, + parameters=parameters, + body=body, + uploads=uploads, + validate_body=validate_body, + ) + + @classmethod + def _preprocess_value(cls, key: str, value: Any) -> Any: + if key in cls.NULLABLES and value == "": + return None + return _preprocess_value(value) + + def preprocess_body(self, body: EntityDefinition) -> EntityDefinition: + # This function is deprecated. Subclasses should subclass `preprocess_entity` instead. + # + # TODO once the transition is done, just keep this implementation as `preprocess_entity` + if isinstance(body, PreprocessedEntityDefinition): + return body + + return PreprocessedEntityDefinition( + { + key: self._preprocess_value(key, value) + for key, value in body.items() + if value is not None + } + ) + + def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition: + return self.preprocess_body(body) + + def list(self, limit: int, offset: int, parameters: Dict[str, Any]) -> List[Any]: + count: int = -1 + entities: List[Any] = [] + payload: Dict[str, Any] = parameters.copy() + payload.update(self.scope) + payload["offset"] = offset + payload["limit"] = BATCH_SIZE + while limit != 0: + if limit > BATCH_SIZE: + limit -= BATCH_SIZE + else: + payload["limit"] = limit + limit = 0 + result: Mapping[str, Any] = self.call("list", parameters=payload) + count = result["count"] + entities.extend(result["results"]) + if result["next"] is None: + break + payload["offset"] += payload["limit"] + else: + self.pulp_ctx.echo( + _("Not all {count} entries were shown.").format(count=count), err=True + ) + return entities + + def find(self, **kwargs: Any) -> Any: + search_result = self.list(limit=1, offset=0, parameters=kwargs) + if len(search_result) != 1: + raise PulpException( + _("Could not find {entity} with {kwargs}.").format( + entity=self.ENTITY, kwargs=kwargs + ) + ) + return search_result[0] + + def show(self, href: Optional[str] = None) -> Any: + return self.call("read", parameters={self.HREF: href or self.pulp_href}) + + def create( + self, + body: EntityDefinition, + parameters: Optional[Mapping[str, Any]] = None, + uploads: Optional[UploadsMap] = None, + non_blocking: bool = False, + ) -> Any: + _parameters = self.scope + if parameters: + _parameters.update(parameters) + if body is not None: + body = self.preprocess_entity(body, partial=False) + result = self.call( + "create", + parameters=_parameters, + body=body, + uploads=uploads, + non_blocking=non_blocking, + ) + if not non_blocking and result["pulp_href"].startswith(self.pulp_ctx.api_path + "tasks/"): + result = self.show(result["created_resources"][0]) + return result + + def update( + self, + href: Optional[str] = None, + body: Optional[EntityDefinition] = None, + parameters: Optional[Mapping[str, Any]] = None, + uploads: Optional[UploadsMap] = None, + non_blocking: bool = False, + ) -> Any: + # Workaround for plugins that do not have ID_PREFIX in place + if hasattr(self, "UPDATE_ID") and not hasattr(self, "PARTIAL_UPDATE_ID"): + self.PARTIAL_UPDATE_ID = getattr(self, "UPDATE_ID") + # ---------------------------------------------------------- + _parameters = {self.HREF: href or self.pulp_href} + if parameters: + _parameters.update(parameters) + if body is not None: + body = self.preprocess_entity(body, partial=False) + return self.call( + "partial_update", + parameters=_parameters, + body=body, + uploads=uploads, + non_blocking=non_blocking, + ) + + def delete(self, href: Optional[str] = None, non_blocking: bool = False) -> Any: + return self.call( + "delete", parameters={self.HREF: href or self.pulp_href}, non_blocking=non_blocking + ) + + def set_label(self, key: str, value: str, non_blocking: bool = False) -> Any: + # We would have a dedicated api for this ideally. + labels = self.entity["pulp_labels"] + labels[key] = value + return self.update(body={"pulp_labels": labels}, non_blocking=non_blocking) + + def unset_label(self, key: str, non_blocking: bool = False) -> Any: + # We would have a dedicated api for this ideally. + labels = self.entity["pulp_labels"] + try: + labels.pop(key) + except KeyError: + raise PulpException(_("Could not find label with key '{key}'.").format(key=key)) + return self.update(body={"pulp_labels": labels}, non_blocking=non_blocking) + + def show_label(self, key: str) -> Any: + # We would have a dedicated api for this ideally. + labels = self.entity["pulp_labels"] + try: + return labels[key] + except KeyError: + raise PulpException(_("Could not find label with key '{key}'.").format(key=key)) + + def my_permissions(self) -> Any: + self.needs_capability("roles") + return self.call("my_permissions", parameters={self.HREF: self.pulp_href}) + + def list_roles(self) -> Any: + self.needs_capability("roles") + return self.call("list_roles", parameters={self.HREF: self.pulp_href}) + + def add_role(self, role: str, users: List[str], groups: List[str]) -> Any: + self.needs_capability("roles") + return self.call( + "add_role", + parameters={self.HREF: self.pulp_href}, + body={"role": role, "users": users, "groups": groups}, + ) + + def remove_role(self, role: str, users: List[str], groups: List[str]) -> Any: + self.needs_capability("roles") + return self.call( + "remove_role", + parameters={self.HREF: self.pulp_href}, + body={"role": role, "users": users, "groups": groups}, + ) + + def capable(self, capability: str) -> bool: + return capability in self.CAPABILITIES and all( + (self.pulp_ctx.has_plugin(pr) for pr in self.CAPABILITIES[capability]) + ) + + def needs_capability(self, capability: str) -> None: + if capability in self.CAPABILITIES: + for pr in self.CAPABILITIES[capability]: + self.pulp_ctx.needs_plugin(pr) + else: + raise PulpException( + _("Capability '{capability}' needed on '{entity}' for this command.").format( + capability=capability, entity=self.ENTITY + ) + ) + + +class PulpRemoteContext(PulpEntityContext): + """ + Base class for remote specific contexts. + """ + + ENTITY = _("remote") + ENTITIES = _("remotes") + ID_PREFIX = "remotes" + HREF_PATTERN = r"remotes/(?P[\w\-_]+)/(?P[\w\-_]+)/" + NULLABLES = { + "ca_cert", + "client_cert", + "client_key", + "username", + "password", + "proxy_url", + "proxy_username", + "proxy_password", + "download_concurrency", + "max_retries", + "total_timeout", + "connect_timeout", + "sock_connect_timeout", + "sock_read_timeout", + "rate_limit", + } + + +class PulpDistributionContext(PulpEntityContext): + ENTITY = _("distribution") + ENTITIES = _("distributions") + ID_PREFIX = "distributions" + HREF_PATTERN = r"distributions/(?P[\w\-_]+)/(?P[\w\-_]+)/" + + +class PulpRepositoryVersionContext(PulpEntityContext): + """ + Base class for repository version specific contexts. + This class provides the basic CRUD commands and + ties its instances to the global PulpContext for api access. + """ + + ENTITY = _("repository version") + ENTITIES = _("repository versions") + repository_ctx: "PulpRepositoryContext" + + def __init__(self, pulp_ctx: PulpContext, repository_ctx: "PulpRepositoryContext") -> None: + super().__init__(pulp_ctx) + self.repository_ctx = repository_ctx + + @property + def scope(self) -> Dict[str, Any]: + return {self.repository_ctx.HREF: self.repository_ctx.pulp_href} + + def repair(self, href: Optional[str] = None) -> Any: + return self.call("repair", parameters={self.HREF: href or self.pulp_href}, body={}) + + +class PulpRepositoryContext(PulpEntityContext): + """ + Base class for repository specific contexts. + This class provides the basic CRUD commands as well as synchronizing and + ties its instances to the global PulpContext for api access. + """ + + ENTITY = _("repository") + ENTITIES = _("repositories") + HREF_PATTERN = r"repositories/(?P[\w\-_]+)/(?P[\w\-_]+)/" + ID_PREFIX = "repositories" + VERSION_CONTEXT: ClassVar[Type[PulpRepositoryVersionContext]] + NULLABLES = {"description", "retain_repo_versions"} + + def get_version_context(self) -> PulpRepositoryVersionContext: + return self.VERSION_CONTEXT(self.pulp_ctx, self) + + def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition: + body = super().preprocess_entity(body, partial=partial) + if self.pulp_ctx.has_plugin(PluginRequirement("core", "3.13.0", "3.15.0")): + # "retain_repo_versions" has been named "retained_versions" until pulpcore 3.15 + # https://github.com/pulp/pulpcore/pull/1472 + if "retain_repo_versions" in body: + body["retained_versions"] = body.pop("retain_repo_versions") + return body + + def sync(self, href: Optional[str] = None, body: Optional[EntityDefinition] = None) -> Any: + return self.call("sync", parameters={self.HREF: href or self.pulp_href}, body=body or {}) + + def modify( + self, + href: Optional[str] = None, + add_content: Optional[List[str]] = None, + remove_content: Optional[List[str]] = None, + base_version: Optional[str] = None, + ) -> Any: + body: Dict[str, Any] = {} + if add_content is not None: + body["add_content_units"] = add_content + if remove_content is not None: + body["remove_content_units"] = remove_content + if base_version is not None: + body["base_version"] = base_version + return self.call("modify", parameters={self.HREF: href or self.pulp_href}, body=body) + + +class PulpContentContext(PulpEntityContext): + """ + Base class for content specific contexts + """ + + ENTITY = _("content") + ENTITIES = _("content") + ID_PREFIX = "content" + + +class PulpACSContext(PulpEntityContext): + ENTITY = _("ACS") + ENTITIES = _("ACSes") + HREF_PATTERN = r"acs/(?P[\w\-_]+)/(?P[\w\-_]+)/" + ID_PREFIX = "acs" + + def refresh(self, href: Optional[str] = None) -> Any: + return self.call("refresh", parameters={self.HREF: href or self.pulp_href}) + + +EntityFieldDefinition = Union[None, str, PulpEntityContext] + + +############################################################################## +# Registries for Contexts of different sorts +# A command can use these to identify e.g. all repository types known to the CLI +# Note that it will only be fully populated, once all plugins are loaded. + + +registered_repository_contexts: Dict[str, Type[PulpRepositoryContext]] = {} diff --git a/pulp-glue/pulp_glue/common/i18n.py b/pulp-glue/pulp_glue/common/i18n.py new file mode 100644 index 000000000..22f7dad37 --- /dev/null +++ b/pulp-glue/pulp_glue/common/i18n.py @@ -0,0 +1,15 @@ +import gettext +from functools import lru_cache + +import pkg_resources + + +def get_translation(name: str) -> gettext.NullTranslations: + localedir = pkg_resources.resource_filename(name, "locale") + return _get_translation_for_domain("messages", localedir) + + +# Need to call lru_cache() before using it as a decorator for python 3.7 compatibility +@lru_cache(maxsize=None) +def _get_translation_for_domain(domain: str, localedir: str) -> gettext.NullTranslations: + return gettext.translation(domain, localedir=localedir, fallback=True) diff --git a/pulpcore/cli/common/openapi.py b/pulp-glue/pulp_glue/common/openapi.py similarity index 99% rename from pulpcore/cli/common/openapi.py rename to pulp-glue/pulp_glue/common/openapi.py index c88dcbe1d..65287fe66 100644 --- a/pulpcore/cli/common/openapi.py +++ b/pulp-glue/pulp_glue/common/openapi.py @@ -11,8 +11,8 @@ import requests import urllib3 - -from pulpcore.cli.common.i18n import get_translation +from pulp_glue.common import __version__ +from pulp_glue.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext @@ -74,7 +74,7 @@ def __init__( raise OpenAPIError(_("Cert is required if key is set.")) self._session.headers.update( { - "User-Agent": user_agent or "Pulp-CLI openapi parser", + "User-Agent": user_agent or f"Pulp-glue openapi parser ({__version__})", "Accept": "application/json", } ) diff --git a/pulp-glue/pulp_glue/common/py.typed b/pulp-glue/pulp_glue/common/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/pulp-glue/pulp_glue/container/__init__.py b/pulp-glue/pulp_glue/container/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pulpcore/cli/container/context.py b/pulp-glue/pulp_glue/container/context.py similarity index 98% rename from pulpcore/cli/container/context.py rename to pulp-glue/pulp_glue/container/context.py index 56022b461..abc25e91c 100644 --- a/pulpcore/cli/container/context.py +++ b/pulp-glue/pulp_glue/container/context.py @@ -1,6 +1,6 @@ from typing import Any, List, Optional -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityDefinition, PluginRequirement, PulpContentContext, @@ -11,7 +11,7 @@ PulpRepositoryVersionContext, registered_repository_contexts, ) -from pulpcore.cli.common.i18n import get_translation +from pulp_glue.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulp-glue/pulp_glue/container/py.typed b/pulp-glue/pulp_glue/container/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/pulp-glue/pulp_glue/core/__init__.py b/pulp-glue/pulp_glue/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pulp-glue/pulp_glue/core/context.py b/pulp-glue/pulp_glue/core/context.py new file mode 100644 index 000000000..1e66046c3 --- /dev/null +++ b/pulp-glue/pulp_glue/core/context.py @@ -0,0 +1,506 @@ +import datetime +import hashlib +import os +import sys +from typing import IO, Any, ClassVar, Dict, List, Optional + +from pulp_glue.common.context import ( + EntityDefinition, + PluginRequirement, + PulpContext, + PulpEntityContext, + PulpException, +) +from pulp_glue.common.i18n import get_translation +from pulp_glue.common.openapi import UploadsMap + +translation = get_translation(__name__) +_ = translation.gettext + + +class PulpAccessPolicyContext(PulpEntityContext): + ENTITY = _("access policy") + ENTITIES = _("access policies") + HREF = "access_policy_href" + ID_PREFIX = "access_policies" + + def reset(self) -> Any: + self.pulp_ctx.needs_plugin(PluginRequirement("core", min="3.17.0")) + return self.call("reset", parameters={self.HREF: self.pulp_href}) + + def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition: + body = super().preprocess_entity(body, partial=partial) + if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): + if "creation_hooks" in body: + body["permissions_assignment"] = body.pop("creation_hooks") + return body + + +class PulpArtifactContext(PulpEntityContext): + ENTITY = _("artifact") + ENTITIES = _("artifacts") + HREF = "artifact_href" + ID_PREFIX = "artifacts" + + def upload( + self, file: IO[bytes], chunk_size: int = 1000000, sha256: Optional[str] = None + ) -> Any: + size = os.path.getsize(file.name) + + sha256_hasher = hashlib.sha256() + for chunk in iter(lambda: file.read(chunk_size), b""): + sha256_hasher.update(chunk) + sha256_digest = sha256_hasher.hexdigest() + file.seek(0) + + if sha256 is not None and sha256_digest != sha256: + raise PulpException(_("File digest does not match.")) + + # check whether the file exists before uploading + result = self.list(limit=1, offset=0, parameters={"sha256": sha256_digest}) + if len(result) > 0: + self.pulp_ctx.echo(_("Artifact already exists."), err=True) + self.pulp_href = result[0]["pulp_href"] + return result[0]["pulp_href"] + + self.pulp_ctx.echo(_("Uploading file {filename}").format(filename=file.name), err=True) + + if chunk_size > size: + # if chunk_size is bigger than the file size, just upload it directly + artifact: Dict[str, Any] = self.create({"sha256": sha256_digest, "file": file}) + self.pulp_href = artifact["pulp_href"] + return artifact["pulp_href"] + + upload_ctx = PulpUploadContext(self.pulp_ctx) + upload_href = upload_ctx.upload_file(file, chunk_size) + + self.pulp_ctx.echo(_("Creating artifact."), err=True) + try: + task = upload_ctx.commit( + upload_href, + sha256_digest, + ) + except Exception as e: + upload_ctx.delete(upload_href) + raise e + self.pulp_href = task["created_resources"][0] + return task["created_resources"][0] + + +class PulpExporterContext(PulpEntityContext): + ENTITY = _("Pulp exporter") + ENTITIES = _("Pulp exporters") + HREF = "pulp_exporter_href" + ID_PREFIX = "exporters_core_pulp" + + +class PulpExportContext(PulpEntityContext): + ENTITY = _("Pulp export") + ENTITIES = _("Pulp exports") + HREF = "pulp_pulp_export_href" + ID_PREFIX = "exporters_core_pulp_exports" + exporter: EntityDefinition + + @property + def scope(self) -> Dict[str, Any]: + return {PulpExporterContext.HREF: self.exporter["pulp_href"]} + + +class PulpGroupContext(PulpEntityContext): + ENTITY = _("user group") + ENTITIES = _("user groups") + # Handled by a workaround + # HREF = "group_href" + ID_PREFIX = "groups" + CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]} + + @property + def HREF(self) -> str: # type:ignore + if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): + return "auth_group_href" + return "group_href" + + +class PulpGroupPermissionContext(PulpEntityContext): + ENTITY = _("group permission") + ENTITIES = _("group permissions") + NEEDS_PLUGINS = [PluginRequirement("core", max="3.20.0", feature=_("group permissions"))] + group_ctx: PulpGroupContext + + def __init__(self, pulp_ctx: PulpContext, group_ctx: PulpGroupContext) -> None: + super().__init__(pulp_ctx) + self.group_ctx = group_ctx + + def call( + self, + operation: str, + non_blocking: bool = False, + parameters: Optional[Dict[str, Any]] = None, + body: Optional[Dict[str, Any]] = None, + uploads: Optional[UploadsMap] = None, + validate_body: bool = False, + ) -> Any: + """Workaroud because the openapi spec for GroupPermissions has always been broken. + + This will probably not be fixed upstream, and GroupPermissions are removed from pulpcore. + So we just skip linting here. + """ + return super().call( + operation, + non_blocking=non_blocking, + parameters=parameters, + body=body, + uploads=uploads, + validate_body=validate_body, + ) + + def find(self, **kwargs: Any) -> Any: + """Workaroud for the missing ability to filter""" + # # TODO fix upstream and adjust to guard for the proper version + # # https://pulp.plan.io/issues/8241 + # if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.99.dev")): + # # Workaround not needed anymore + # return super().find(**kwargs) + search_result = self.list(limit=sys.maxsize, offset=0, parameters={}) + for key, value in kwargs.items(): + search_result = [res for res in search_result if res[key] == value] + if len(search_result) != 1: + raise PulpException( + _("Could not find {entity} with {kwargs}.").format( + entity=self.ENTITY, kwargs=kwargs + ) + ) + return search_result[0] + + @property + def scope(self) -> Dict[str, Any]: + return {self.group_ctx.HREF: self.group_ctx.pulp_href} + + +class PulpGroupModelPermissionContext(PulpGroupPermissionContext): + ENTITY = _("group model permission") + ENTITIES = _("group model permissions") + # Handled by a workaround + # HREF = "groups_model_permission_href" + ID_PREFIX = "groups_model_permissions" + + @property + def HREF(self) -> str: # type:ignore + if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): + return "auth_groups_model_permission_href" + return "groups_model_permission_href" + + +class PulpGroupObjectPermissionContext(PulpGroupPermissionContext): + ENTITY = _("group object permission") + ENTITIES = _("group object permissions") + # Handled by a workaround + # HREF = "groups_object_permission_href" + ID_PREFIX = "groups_object_permissions" + + @property + def HREF(self) -> str: # type:ignore + if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): + return "auth_groups_object_permission_href" + return "groups_object_permission_href" + + +class PulpGroupRoleContext(PulpEntityContext): + ENTITY = _("group role") + ENTITIES = _("group roles") + HREF = "groups_group_role_href" + ID_PREFIX = "groups_roles" + NULLABLES = {"content_object"} + group_ctx: PulpGroupContext + + def __init__(self, pulp_ctx: PulpContext, group_ctx: PulpGroupContext) -> None: + super().__init__(pulp_ctx) + self.group_ctx = group_ctx + + @property + def scope(self) -> Dict[str, Any]: + return {self.group_ctx.HREF: self.group_ctx.pulp_href} + + +class PulpGroupUserContext(PulpEntityContext): + ENTITY = _("group user") + ENTITIES = _("group users") + # Handled by a workaround + # HREF = "groups_user_href" + ID_PREFIX = "groups_users" + group_ctx: PulpGroupContext + + @property + def HREF(self) -> str: # type:ignore + if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): + return "auth_groups_user_href" + return "groups_user_href" + + def __init__(self, pulp_ctx: PulpContext, group_ctx: PulpGroupContext) -> None: + super().__init__(pulp_ctx) + self.group_ctx = group_ctx + + @property + def scope(self) -> Dict[str, Any]: + return {self.group_ctx.HREF: self.group_ctx.pulp_href} + + +class PulpContentGuardContext(PulpEntityContext): + ENTITY = "content guard" + ENTITIES = "content guards" + ID_PREFIX = "contentguards" + HREF_PATTERN = r"contentguards/(?P[\w\-_]+)/(?P[\w\-_]+)/" + + +class PulpContentRedirectContentGuardContext(PulpEntityContext): + ENTITY = "content redirect content guard" + ENTITIES = "content redirect content guards" + HREF = "content_redirect_content_guard_href" + ID_PREFIX = "contentguards_core_content_redirect" + NEEDS_PLUGINS = [PluginRequirement("core", "3.18.0")] + + +class PulpImporterContext(PulpEntityContext): + ENTITY = _("Pulp importer") + ENTITIES = _("Pulp importers") + HREF = "pulp_importer_href" + ID_PREFIX = "importers_core_pulp" + + +class PulpPublicationContext(PulpEntityContext): + ENTITY = _("Pulp publication") + ENTITIES = _("Pulp publications") + ID_PREFIX = "publications" + HREF_PATTERN = r"publications/(?P[\w\-_]+)/(?P[\w\-_]+)/" + + +class PulpRbacContentGuardContext(PulpContentGuardContext): + ENTITY = "RBAC content guard" + ENTITIES = "RBAC content guards" + HREF = "r_b_a_c_content_guard_href" + ID_PREFIX = "contentguards_core_rbac" + DOWNLOAD_ROLE: ClassVar[str] = "core.rbaccontentguard_downloader" + CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]} + NEEDS_PLUGINS = [PluginRequirement("core", "3.15.0")] + + def assign( + self, + href: Optional[str] = None, + users: Optional[List[str]] = None, + groups: Optional[List[str]] = None, + ) -> Any: + if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): + body: EntityDefinition = {"users": users, "groups": groups} + body["role"] = self.DOWNLOAD_ROLE + return self.call("add_role", parameters={self.HREF: href or self.pulp_href}, body=body) + else: + body = {"usernames": users, "groupnames": groups} + return self.call( + "assign_permission", parameters={self.HREF: href or self.pulp_href}, body=body + ) + + def remove( + self, + href: Optional[str] = None, + users: Optional[List[str]] = None, + groups: Optional[List[str]] = None, + ) -> Any: + if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): + body: EntityDefinition = {"users": users, "groups": groups} + body["role"] = self.DOWNLOAD_ROLE + return self.call( + "remove_role", parameters={self.HREF: href or self.pulp_href}, body=body + ) + else: + body = {"usernames": users, "groupnames": groups} + return self.call( + "remove_permission", parameters={self.HREF: href or self.pulp_href}, body=body + ) + + +class PulpRoleContext(PulpEntityContext): + ENTITY = _("role") + ENTITIES = _("roles") + HREF = "role_href" + ID_PREFIX = "roles" + NULLABLES = {"description"} + NEEDS_PLUGINS = [PluginRequirement("core", "3.17.0")] + + +class PulpSigningServiceContext(PulpEntityContext): + ENTITY = _("signing service") + ENTITIES = _("signing services") + HREF = "signing_service_href" + ID_PREFIX = "signing_services" + + +class PulpTaskContext(PulpEntityContext): + ENTITY = _("task") + ENTITIES = _("tasks") + HREF = "task_href" + ID_PREFIX = "tasks" + CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]} + + resource_context: Optional[PulpEntityContext] = None + + def list(self, limit: int, offset: int, parameters: Dict[str, Any]) -> List[Any]: + if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.22.0dev")): + parameters = parameters.copy() + reserved_resources = parameters.pop("reserved_resources", None) + exclusive_resources = parameters.pop("exclusive_resources", None) + shared_resources = parameters.pop("shared_resources", None) + parameters.pop("reserved_resources__in", None) + parameters.pop("exclusive_resources__in", None) + parameters.pop("shared_resources__in", None) + reserved_resources_record = [] + if reserved_resources: + reserved_resources_record.append(reserved_resources) + if exclusive_resources: + reserved_resources_record.append(exclusive_resources) + if shared_resources: + reserved_resources_record.append("shared:" + shared_resources) + if len(reserved_resources_record) > 1: + self.pulp_ctx.needs_plugin( + PluginRequirement( + "core", + min="3.22.0dev", + feature=_("specify multiple reserved resources"), + ), + ) + parameters["reserved_resources_record"] = reserved_resources_record + + return super().list(limit=limit, offset=offset, parameters=parameters) + + def cancel(self, task_href: Optional[str] = None) -> Any: + return self.call( + "cancel", + parameters={self.HREF: task_href or self.pulp_href}, + body={"state": "canceled"}, + ) + + @property + def scope(self) -> Dict[str, Any]: + if self.resource_context: + if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.22.0dev")): + return {"reserved_resources": self.resource_context.pulp_href} + else: + return {"reserved_resources_record": [self.resource_context.pulp_href]} + else: + return {} + + def purge( + self, + finished_before: Optional[datetime.datetime], + states: Optional[List[str]], + ) -> Any: + body: Dict[str, Any] = {} + if finished_before: + body["finished_before"] = finished_before + if states: + body["states"] = states + return self.call( + "purge", + body=body, + ) + + def summary(self) -> Dict[str, int]: + task_states = ["waiting", "skipped", "running", "completed", "failed", "canceled"] + if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.14.0")): + task_states.append("canceling") + result = {} + for state in task_states: + payload = {"limit": 1, "state": state} + result[state] = self.call("list", parameters=payload)["count"] + return result + + +class PulpTaskGroupContext(PulpEntityContext): + ENTITY = _("task group") + ENTITIES = _("task groups") + HREF = "task_group_href" + ID_PREFIX = "task_groups" + + +class PulpUploadContext(PulpEntityContext): + ENTITY = _("upload") + ENTITIES = _("uploads") + HREF = "upload_href" + ID_PREFIX = "uploads" + + def upload_chunk( + self, + chunk: bytes, + size: int, + start: int, + non_blocking: bool = False, + ) -> Any: + end: int = start + len(chunk) - 1 + parameters = {self.HREF: self.pulp_href, "Content-Range": f"bytes {start}-{end}/{size}"} + return self.call( + "update", + parameters=parameters, + body={"sha256": hashlib.sha256(chunk).hexdigest(), "file": chunk}, + non_blocking=non_blocking, + ) + + def commit(self, upload_href: str, sha256: str) -> Any: + return self.call( + "commit", + parameters={self.HREF: upload_href}, + body={"sha256": sha256}, + ) + + def upload_file(self, file: IO[bytes], chunk_size: int = 1000000) -> Any: + """Upload a file and return the uncommitted upload_href.""" + start = 0 + size = os.path.getsize(file.name) + upload_href = self.create(body={"size": size})["pulp_href"] + try: + self.pulp_href = upload_href + while start < size: + chunk = file.read(chunk_size) + self.upload_chunk( + chunk=chunk, + size=size, + start=start, + ) + start += chunk_size + self.pulp_ctx.echo(".", nl=False, err=True) + except Exception as e: + self.delete(upload_href) + raise e + self.pulp_ctx.echo(_("Upload complete."), err=True) + return upload_href + + +class PulpUserContext(PulpEntityContext): + ENTITY = _("user") + ENTITIES = _("users") + HREF = "auth_user_href" + ID_PREFIX = "users" + NULLABLES = {"password"} + + +class PulpUserRoleContext(PulpEntityContext): + ENTITY = _("user role") + ENTITIES = _("user roles") + HREF = "auth_users_user_role_href" + ID_PREFIX = "users_roles" + NULLABLES = {"content_object"} + user_ctx: PulpUserContext + + def __init__(self, pulp_ctx: PulpContext, user_ctx: PulpUserContext) -> None: + super().__init__(pulp_ctx) + self.user_ctx = user_ctx + + @property + def scope(self) -> Dict[str, Any]: + return {self.user_ctx.HREF: self.user_ctx.pulp_href} + + +class PulpWorkerContext(PulpEntityContext): + ENTITY = _("worker") + ENTITIES = _("workers") + HREF = "worker_href" + ID_PREFIX = "workers" + HREF_PATTERN = r"workers/" diff --git a/pulp-glue/pulp_glue/core/py.typed b/pulp-glue/pulp_glue/core/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/pulp-glue/pulp_glue/file/__init__.py b/pulp-glue/pulp_glue/file/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pulpcore/cli/file/context.py b/pulp-glue/pulp_glue/file/context.py similarity index 97% rename from pulpcore/cli/file/context.py rename to pulp-glue/pulp_glue/file/context.py index ec29ec0c0..c8aa41340 100644 --- a/pulpcore/cli/file/context.py +++ b/pulp-glue/pulp_glue/file/context.py @@ -1,4 +1,4 @@ -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityDefinition, PluginRequirement, PulpACSContext, @@ -10,7 +10,7 @@ PulpRepositoryVersionContext, registered_repository_contexts, ) -from pulpcore.cli.common.i18n import get_translation +from pulp_glue.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulp-glue/pulp_glue/file/py.typed b/pulp-glue/pulp_glue/file/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/pulp-glue/pulp_glue/python/__init__.py b/pulp-glue/pulp_glue/python/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pulpcore/cli/python/context.py b/pulp-glue/pulp_glue/python/context.py similarity index 96% rename from pulpcore/cli/python/context.py rename to pulp-glue/pulp_glue/python/context.py index 279a56330..91674371e 100644 --- a/pulpcore/cli/python/context.py +++ b/pulp-glue/pulp_glue/python/context.py @@ -1,4 +1,4 @@ -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityDefinition, PluginRequirement, PulpContentContext, @@ -9,7 +9,7 @@ PulpRepositoryVersionContext, registered_repository_contexts, ) -from pulpcore.cli.common.i18n import get_translation +from pulp_glue.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulp-glue/pulp_glue/python/py.typed b/pulp-glue/pulp_glue/python/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/pulp-glue/pulp_glue/rpm/__init__.py b/pulp-glue/pulp_glue/rpm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pulpcore/cli/rpm/context.py b/pulp-glue/pulp_glue/rpm/context.py similarity index 98% rename from pulpcore/cli/rpm/context.py rename to pulp-glue/pulp_glue/rpm/context.py index 3b46a4c35..af7599d4e 100644 --- a/pulpcore/cli/rpm/context.py +++ b/pulp-glue/pulp_glue/rpm/context.py @@ -1,8 +1,7 @@ from typing import IO, Any, ClassVar, Optional import click - -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityDefinition, PluginRequirement, PulpACSContext, @@ -15,7 +14,7 @@ PulpRepositoryVersionContext, registered_repository_contexts, ) -from pulpcore.cli.common.i18n import get_translation +from pulp_glue.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulp-glue/pulp_glue/rpm/py.typed b/pulp-glue/pulp_glue/rpm/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/pulp-glue/setup.py b/pulp-glue/setup.py new file mode 100644 index 000000000..f1de945bd --- /dev/null +++ b/pulp-glue/setup.py @@ -0,0 +1,49 @@ +from setuptools import setup + +try: + from setuptools import find_namespace_packages + + plugin_packages = find_namespace_packages(include=["pulp_glue.*"], exclude=["pulp_glue.*.*"]) + +except ImportError: + # Old versions of setuptools do not provide `find_namespace_packages` + # see https://github.com/pulp/pulp-cli/issues/248 + from setuptools import find_packages + + plugins = find_packages(where="pulp_glue") + plugin_packages = [f"pulp_glue.{plugin}" for plugin in plugins] + +long_description = "" +with open("README.md") as readme: + for line in readme: + long_description += line + +setup( + name="pulp-glue", + description="Version agnostic glue library to talk to pulpcore's REST API.", + long_description=long_description, + long_description_content_type="text/markdown", + author="Pulp Team", + author_email="pulp-list@redhat.com", + url="https://github.com/pulp/pulp-cli", + version="0.17.0.dev", + packages=plugin_packages, + package_data={"": ["py.typed"]}, + python_requires=">=3.6", + install_requires=[ + "packaging", + "setuptools", + "requests~=2.24", + ], + license="GPLv2+", + classifiers=[ + "Development Status :: 4 - Beta", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Topic :: System :: Software Distribution", + "Typing :: Typed", + ], +) diff --git a/pulpcore/cli/ansible/__init__.py b/pulpcore/cli/ansible/__init__.py index 9bd49d6b4..47eba323f 100644 --- a/pulpcore/cli/ansible/__init__.py +++ b/pulpcore/cli/ansible/__init__.py @@ -1,14 +1,14 @@ from typing import Any import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation from pulpcore.cli.ansible.content import content from pulpcore.cli.ansible.distribution import distribution from pulpcore.cli.ansible.remote import remote from pulpcore.cli.ansible.repository import repository -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_group -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/ansible/content.py b/pulpcore/cli/ansible/content.py index 58bad3cd2..eba5d7f28 100644 --- a/pulpcore/cli/ansible/content.py +++ b/pulpcore/cli/ansible/content.py @@ -1,14 +1,16 @@ from typing import IO, Any, Callable, Optional, Union import click - -from pulpcore.cli.ansible.context import ( +from pulp_glue.ansible.context import ( PulpAnsibleCollectionVersionContext, PulpAnsibleCollectionVersionSignatureContext, PulpAnsibleRepositoryContext, PulpAnsibleRoleContext, ) -from pulpcore.cli.common.context import PulpEntityContext, PulpRepositoryContext +from pulp_glue.common.context import PulpEntityContext, PulpRepositoryContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpArtifactContext + from pulpcore.cli.common.generic import ( GroupOption, PulpCLIContext, @@ -22,8 +24,6 @@ resource_option, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpArtifactContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/ansible/distribution.py b/pulpcore/cli/ansible/distribution.py index 1f23b4705..72575618c 100644 --- a/pulpcore/cli/ansible/distribution.py +++ b/pulpcore/cli/ansible/distribution.py @@ -1,17 +1,15 @@ from typing import Dict, Optional import click - -from pulpcore.cli.ansible.context import ( - PulpAnsibleDistributionContext, - PulpAnsibleRepositoryContext, -) -from pulpcore.cli.common.context import ( +from pulp_glue.ansible.context import PulpAnsibleDistributionContext, PulpAnsibleRepositoryContext +from pulp_glue.common.context import ( EntityDefinition, EntityFieldDefinition, PluginRequirement, PulpContext, ) +from pulp_glue.common.i18n import get_translation + from pulpcore.cli.common.generic import ( create_command, destroy_command, @@ -28,7 +26,6 @@ resource_option, show_command, ) -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/ansible/remote.py b/pulpcore/cli/ansible/remote.py index a38afbab6..4ef187f33 100644 --- a/pulpcore/cli/ansible/remote.py +++ b/pulpcore/cli/ansible/remote.py @@ -2,11 +2,12 @@ import click import yaml - -from pulpcore.cli.ansible.context import ( +from pulp_glue.ansible.context import ( PulpAnsibleCollectionRemoteContext, PulpAnsibleRoleRemoteContext, ) +from pulp_glue.common.i18n import get_translation + from pulpcore.cli.common.generic import ( PulpCLIContext, common_remote_create_options, @@ -26,7 +27,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/ansible/repository.py b/pulpcore/cli/ansible/repository.py index 08c2fd2a6..fdc48483a 100644 --- a/pulpcore/cli/ansible/repository.py +++ b/pulpcore/cli/ansible/repository.py @@ -2,20 +2,22 @@ import click import schema as s - -from pulpcore.cli.ansible.context import ( +from pulp_glue.ansible.context import ( PulpAnsibleCollectionRemoteContext, PulpAnsibleCollectionVersionContext, PulpAnsibleRepositoryContext, PulpAnsibleRoleContext, PulpAnsibleRoleRemoteContext, ) -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityFieldDefinition, PluginRequirement, PulpRemoteContext, PulpRepositoryContext, ) +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpSigningServiceContext + from pulpcore.cli.common.generic import ( GroupOption, PulpCLIContext, @@ -43,8 +45,6 @@ update_command, version_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpSigningServiceContext from pulpcore.cli.core.generic import task_command translation = get_translation(__name__) diff --git a/pulpcore/cli/common/__init__.py b/pulpcore/cli/common/__init__.py index 112356d7c..d54e4101f 100644 --- a/pulpcore/cli/common/__init__.py +++ b/pulpcore/cli/common/__init__.py @@ -12,11 +12,12 @@ except ImportError: HAS_CLICK_SHELL = False +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation + from pulpcore.cli.common.config import CONFIG_LOCATIONS, config, config_options, validate_config -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.debug import debug from pulpcore.cli.common.generic import PulpCLIContext, pulp_group -from pulpcore.cli.common.i18n import get_translation __version__ = "0.17.0.dev" diff --git a/pulpcore/cli/common/config.py b/pulpcore/cli/common/config.py index 37393f6ca..c8e7fd0d5 100644 --- a/pulpcore/cli/common/config.py +++ b/pulpcore/cli/common/config.py @@ -3,9 +3,9 @@ import click import toml +from pulp_glue.common.i18n import get_translation from pulpcore.cli.common.generic import pulp_group -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/common/context.py b/pulpcore/cli/common/context.py index e9a84c7e9..010320e45 100644 --- a/pulpcore/cli/common/context.py +++ b/pulpcore/cli/common/context.py @@ -1,833 +1,23 @@ -import datetime -import re -import sys -import time -from typing import Any, ClassVar, Dict, List, Mapping, NamedTuple, Optional, Set, Type, Union - -from packaging.version import parse as parse_version -from requests import HTTPError - -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.common.openapi import OpenAPI, OpenAPIError, UploadsMap - -translation = get_translation(__name__) -_ = translation.gettext - -DEFAULT_LIMIT = 25 -BATCH_SIZE = 25 -DATETIME_FORMATS = [ - "%Y-%m-%dT%H:%M:%S.%fZ", # Pulp format - "%Y-%m-%d", # intl. format - "%Y-%m-%d %H:%M:%S", # ... with time - "%Y-%m-%dT%H:%M:%S", # ... with time and T as a separator - "%y/%m/%d", # US format - "%y/%m/%d %h:%M:%S %p", # ... with time - "%y/%m/%dT%h:%M:%S %p", # ... with time and T as a separator - "%y/%m/%d %H:%M:%S", # ... with time 24h - "%y/%m/%dT%H:%M:%S", # ... with time 24h and T as a separator - "%x", # local format - "%x %X", # ... with time - "%xT%X", # ... with time and T as a separator +from pulp_glue.common.context import ( # noqa: F401 + EntityDefinition, + EntityFieldDefinition, + PluginRequirement, + PulpContext, + PulpEntityContext, + PulpException, + PulpRepositoryContext, + PulpRepositoryVersionContext, + registered_repository_contexts, +) + +__all__ = [ + "EntityDefinition", + "EntityFieldDefinition", + "PluginRequirement", + "PulpContext", + "PulpEntityContext", + "PulpRepositoryContext", + "PulpRepositoryVersionContext", + "PulpException", + "registered_repository_contexts", ] - -href_regex = re.compile(r"\/([a-z0-9-_]+\/)+", flags=re.IGNORECASE) - - -class PreprocessedEntityDefinition(Dict[str, Any]): - pass - - -EntityDefinition = Union[Dict[str, Any], PreprocessedEntityDefinition] - - -class PluginRequirement(NamedTuple): - name: str - min: Optional[str] = None - max: Optional[str] = None - feature: Optional[str] = None - inverted: bool = False - - -class PulpException(Exception): - pass - - -class PulpNoWait(Exception): - pass - - -def _preprocess_value(value: Any) -> Any: - if isinstance(value, PulpEntityContext): - return value.pulp_href - if isinstance(value, Mapping): - return {k: _preprocess_value(v) for k, v in value.items()} - if isinstance(value, (list, tuple)): - return [_preprocess_value(item) for item in value] - return value - - -def preprocess_payload(payload: EntityDefinition) -> EntityDefinition: - if isinstance(payload, PreprocessedEntityDefinition): - return payload - - return PreprocessedEntityDefinition( - {key: _preprocess_value(value) for key, value in payload.items() if value is not None} - ) - - -class PulpContext: - """ - Abstract class for the global PulpContext object. - It is an abstraction layer for api access and output handling. - """ - - def echo(self, message: str, nl: bool = True, err: bool = False) -> None: - raise NotImplementedError("PulpContext is an abstract class.") - - def prompt(self, text: str, hide_input: bool = False) -> Any: - raise NotImplementedError("PulpContext is an abstract class.") - - def __init__( - self, - api_root: str, - api_kwargs: Dict[str, Any], - format: str, - background_tasks: bool, - timeout: int, - ) -> None: - self._api: Optional[OpenAPI] = None - self.api_path: str = api_root + "api/v3/" - self._api_kwargs = api_kwargs - self._needed_plugins: List[PluginRequirement] = [] - self.isatty: bool = sys.stdout.isatty() - - self.format: str = format - self.background_tasks: bool = background_tasks - self.timeout: int = timeout - self.start_time: Optional[datetime.datetime] = None - - def _patch_api_spec(self) -> None: - # A place for last minute fixes to the api_spec. - # WARNING: Operations are already indexed at this point. - api_spec = self.api.api_spec - if self.has_plugin(PluginRequirement("core", max="3.20.0")): - for method, path in self.api.operations.values(): - operation = api_spec["paths"][path][method] - if method == "get" and "parameters" in operation: - for parameter in operation["parameters"]: - if ( - parameter["name"] == "ordering" - and parameter["in"] == "query" - and "schema" in parameter - and parameter["schema"]["type"] == "string" - ): - parameter["schema"] = {"type": "array", "items": {"type": "string"}} - parameter["explode"] = False - parameter["style"] = "form" - if self.has_plugin(PluginRequirement("core", max="3.22.0.dev")): - for method, path in self.api.operations.values(): - operation = api_spec["paths"][path][method] - if method == "get" and "parameters" in operation: - for parameter in operation["parameters"]: - if ( - parameter["name"] in ["fields", "exclude_fields"] - and parameter["in"] == "query" - and "schema" in parameter - and parameter["schema"]["type"] == "string" - ): - parameter["schema"] = {"type": "array", "items": {"type": "string"}} - if self.has_plugin(PluginRequirement("file", max="1.11.0")): - operation = api_spec["paths"]["{file_file_alternate_content_source_href}refresh/"][ - "post" - ] - operation.pop("requestBody") - if self.has_plugin( - PluginRequirement("python", max="99.99.0.dev") - ): # TODO Add version bounds - python_remote_serializer = api_spec["components"]["schemas"]["python.PythonRemote"] - patched_python_remote_serializer = api_spec["components"]["schemas"][ - "Patchedpython.PythonRemote" - ] - for prop in ("includes", "excludes"): - python_remote_serializer["properties"][prop]["type"] = "array" - python_remote_serializer["properties"][prop]["items"] = {"type": "string"} - patched_python_remote_serializer["properties"][prop]["type"] = "array" - patched_python_remote_serializer["properties"][prop]["items"] = {"type": "string"} - - @property - def api(self) -> OpenAPI: - if self._api is None: - if self._api_kwargs.get("username") and not self._api_kwargs.get("password"): - self._api_kwargs["password"] = self.prompt("password", hide_input=True) - try: - self._api = OpenAPI(doc_path=f"{self.api_path}docs/api.json", **self._api_kwargs) - except OpenAPIError as e: - raise PulpException(str(e)) - # Rerun scheduled version checks - for plugin in self._needed_plugins: - self.needs_plugin(plugin) - self._patch_api_spec() - return self._api - - @property - def component_versions(self) -> Dict[str, str]: - result: Dict[str, str] = self.api.api_spec.get("info", {}).get("x-pulp-app-versions", {}) - return result - - def call( - self, - operation_id: str, - non_blocking: bool = False, - parameters: Optional[Dict[str, Any]] = None, - body: Optional[EntityDefinition] = None, - uploads: Optional[UploadsMap] = None, - validate_body: bool = True, - ) -> Any: - """ - Perform an API call for operation_id. - Wait for triggered tasks to finish if not background. - Returns the operation result, or the finished task. - If non_blocking, returns unfinished tasks. - """ - if parameters is not None: - parameters = preprocess_payload(parameters) - if body is not None: - body = preprocess_payload(body) - # ---- SNIP ---- - # In the future we want everything as part of the body. - if uploads: - self.echo( - _("Deprecated use of 'uploads' for operation '{operation_id}'").format( - operation_id=operation_id - ) - ) - body = body or {} - body.update(uploads) - # ---- SNIP ---- - try: - result = self.api.call( - operation_id, - parameters=parameters, - body=body, - validate_body=validate_body, - ) - except OpenAPIError as e: - raise PulpException(str(e)) - except HTTPError as e: - raise PulpException(str(e.response.text)) - # Asynchronous tasks seem to be reported by a dict containing only one key "task" - if isinstance(result, dict) and ["task"] == list(result.keys()): - task_href = result["task"] - result = self.api.call("tasks_read", parameters={"task_href": task_href}) - self.echo( - _("Started background task {task_href}").format(task_href=task_href), err=True - ) - if not non_blocking: - result = self.wait_for_task(result) - if isinstance(result, dict) and ["task_group"] == list(result.keys()): - task_group_href = result["task_group"] - result = self.api.call( - "task_groups_read", parameters={"task_group_href": task_group_href} - ) - self.echo( - _("Started background task group {task_group_href}").format( - task_group_href=task_group_href - ), - err=True, - ) - if not non_blocking: - result = self.wait_for_task_group(result) - return result - - @classmethod - def _check_task_finished(cls, task: EntityDefinition) -> bool: - task_href = task["pulp_href"] - - if task["state"] == "completed": - return True - elif task["state"] == "failed": - raise PulpException( - _("Task {task_href} failed: '{description}'").format( - task_href=task_href, - description=task["error"].get("description") or task["error"].get("reason"), - ) - ) - elif task["state"] == "canceled": - raise PulpException(_("Task {task_href} canceled").format(task_href=task_href)) - elif task["state"] in ["waiting", "running", "canceling"]: - return False - else: - raise NotImplementedError(_("Unknown task state: {state}").format(state=task["state"])) - - def _poll_task(self, task: EntityDefinition) -> EntityDefinition: - while True: - if self._check_task_finished(task): - self.echo("Done.", err=True) - return task - else: - if self.timeout: - assert isinstance(self.start_time, datetime.datetime) - if (datetime.datetime.now() - self.start_time).seconds > self.timeout: - raise PulpNoWait( - _("Waiting for task {task_href} timed out.").format( - task_href=task["pulp_href"] - ) - ) - time.sleep(1) - self.echo(".", nl=False, err=True) - task = self.api.call("tasks_read", parameters={"task_href": task["pulp_href"]}) - - def wait_for_task(self, task: EntityDefinition) -> Any: - """ - Wait for a task to finish and return the finished task object. - - Raise `PulpNoWait` on timeout, background, ctrl-c, if task failed or was canceled. - """ - self.start_time = datetime.datetime.now() - - if self.background_tasks: - raise PulpNoWait(_("Not waiting for task because --background was specified.")) - task_href = task["pulp_href"] - try: - return self._poll_task(task) - except KeyboardInterrupt: - raise PulpNoWait(_("Task {task_href} sent to background.").format(task_href=task_href)) - - def wait_for_task_group(self, task_group: EntityDefinition) -> Any: - """ - Wait for a task group to finish and return the finished task object. - - Raise `PulpNoWait` on timeout, background, ctrl-c, if tasks failed or were canceled. - """ - self.start_time = datetime.datetime.now() - - if self.background_tasks: - raise PulpNoWait("Not waiting for task group because --background was specified.") - try: - while True: - if task_group["all_tasks_dispatched"] is True: - for task in task_group["tasks"]: - task = self.api.call( - "tasks_read", parameters={"task_href": task["pulp_href"]} - ) - self.echo( - _("Waiting for task {task_href}").format(task_href=task["pulp_href"]), - err=True, - ) - self._poll_task(task) - return task_group - else: - if self.timeout: - assert isinstance(self.start_time, datetime.datetime) - if (datetime.datetime.now() - self.start_time).seconds > self.timeout: - raise PulpNoWait( - _("Waiting for task group {task_group_href} timed out.").format( - task_group_href=task_group["pulp_href"] - ) - ) - time.sleep(1) - self.echo(".", nl=False, err=True) - task_group = self.api.call( - "task_groups_read", parameters={"task_group_href": task_group["pulp_href"]} - ) - except KeyboardInterrupt: - raise PulpNoWait( - _("Task group {task_group_href} sent to background.").format( - task_group_href=task_group["pulp_href"] - ) - ) - - def has_plugin( - self, - plugin: PluginRequirement, - ) -> bool: - if not self.component_versions: - # Prior to 3.9 we do not have this information. - # Assume we have no plugin installed. - return not plugin.inverted - version: Optional[str] = self.component_versions.get(plugin.name) - if version is None: - return plugin.inverted - if plugin.min is not None: - if parse_version(version) < parse_version(plugin.min): - return plugin.inverted - if plugin.max is not None: - if parse_version(version) >= parse_version(plugin.max): - return plugin.inverted - return not plugin.inverted - - def needs_plugin( - self, - plugin: PluginRequirement, - ) -> None: - if self._api is not None: - if not self.has_plugin(plugin): - specifier = plugin.name - separator = "" - if plugin.min is not None: - specifier += f">={plugin.min}" - separator = "," - if plugin.max is not None: - specifier += f"{separator}<{plugin.max}" - feature = plugin.feature or _("this command") - if plugin.inverted: - msg = _( - "The server provides the pulp component '{specifier}'," - " which prevents the use of {feature}." - " See 'pulp status' for installed components." - ) - else: - msg = _( - "The server does not provide the pulp component '{specifier}'," - " which is needed to use {feature}." - " See 'pulp status' for installed components." - ) - raise PulpException(msg.format(specifier=specifier, feature=feature)) - else: - # Schedule for later checking - self._needed_plugins.append(plugin) - - -class PulpEntityContext: - """ - Base class for entity specific contexts. - This class provides the basic CRUD commands and ties its instances to the global - PulpContext for api access. - """ - - # Subclasses should provide appropriate values here - ENTITY: ClassVar[str] = _("entity") - ENTITIES: ClassVar[str] = _("entities") - HREF: ClassVar[str] - ID_PREFIX: ClassVar[str] - # Set of fields that can be cleared by sending 'null' - NULLABLES: ClassVar[Set[str]] = set() - # List of necessary plugin to operate such an entity in the server - NEEDS_PLUGINS: ClassVar[List[PluginRequirement]] = [] - # Subclasses can specify version dependent capabilities here - # e.g. `CAPABILITIES = { - # "feature1": [ - # PluginRequirement("file"), - # PluginRequirement("core", min_version="3.7.0") - # ] - # } - CAPABILITIES: ClassVar[Dict[str, List[PluginRequirement]]] = {} - HREF_PATTERN: str - - # Hidden values for the lazy entity lookup - _entity: Optional[EntityDefinition] - _entity_lookup: EntityDefinition - - # Subclasses for nested entities can define the parameters for there parent scope here - @property - def scope(self) -> Dict[str, Any]: - return {} - - @property - def entity(self) -> EntityDefinition: - """ - Entity property that will perform a lazy lookup once it is accessed. - You can specify lookup parameters by assigning a dictionary to it, - or assign an href to the ``pulp_href`` property. - To reset to having no attached entity you can assign ``None``. - Assigning to it will reset the lazy lookup behaviour. - """ - if self._entity is None: - if not self._entity_lookup: - raise PulpException( - _("A {entity} must be specified for this command.").format(entity=self.ENTITY) - ) - if self._entity_lookup.get("pulp_href"): - self._entity = self.show(self._entity_lookup["pulp_href"]) - else: - self._entity = self.find(**self._entity_lookup) - self._entity_lookup = {} - return self._entity - - @entity.setter - def entity(self, value: Optional[EntityDefinition]) -> None: - # Setting this property will always (lazily) retrigger retrieving the entity. - # If set multiple times in a row without reading, the criteria will be added. - if value is None: - self._entity_lookup = {} - else: - self._entity_lookup.update(value) - self._entity_lookup.pop("pulp_href", None) - self._entity = None - - @property - def pulp_href(self) -> str: - """ - Property to represent the href of the attached entity. - Assigning to it will reset the lazy lookup behaviour. - """ - return str(self.entity["pulp_href"]) - - @pulp_href.setter - def pulp_href(self, value: str) -> None: - if not href_regex.fullmatch(value): - raise PulpException( - _("'{value}' is not a valid HREF value for a {context_id}").format( - value=value, context_id=self.ENTITY - ) - ) - - # Setting this property will always (lazily) retrigger retrieving the entity. - self._entity_lookup = {"pulp_href": value} - self._entity = None - - def __init__( - self, - pulp_ctx: PulpContext, - pulp_href: Optional[str] = None, - entity: Optional[EntityDefinition] = None, - ) -> None: - assert pulp_href is None or entity is None - - self.meta: Dict[str, str] = {} - self.pulp_ctx: PulpContext = pulp_ctx - - # Add requirements to the lazy evaluated list - for req in self.NEEDS_PLUGINS: - self.pulp_ctx.needs_plugin(req) - - self._entity = None - if pulp_href is None: - self._entity_lookup = entity or {} - else: - self.pulp_href = pulp_href - - def call( - self, - operation: str, - non_blocking: bool = False, - parameters: Optional[Dict[str, Any]] = None, - body: Optional[EntityDefinition] = None, - uploads: Optional[UploadsMap] = None, - validate_body: bool = True, - ) -> Any: - operation_id: str = ( - getattr(self, operation.upper() + "_ID", None) or self.ID_PREFIX + "_" + operation - ) - return self.pulp_ctx.call( - operation_id, - non_blocking=non_blocking, - parameters=parameters, - body=body, - uploads=uploads, - validate_body=validate_body, - ) - - @classmethod - def _preprocess_value(cls, key: str, value: Any) -> Any: - if key in cls.NULLABLES and value == "": - return None - return _preprocess_value(value) - - def preprocess_body(self, body: EntityDefinition) -> EntityDefinition: - # This function is deprecated. Subclasses should subclass `preprocess_entity` instead. - # - # TODO once the transition is done, just keep this implementation as `preprocess_entity` - if isinstance(body, PreprocessedEntityDefinition): - return body - - return PreprocessedEntityDefinition( - { - key: self._preprocess_value(key, value) - for key, value in body.items() - if value is not None - } - ) - - def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition: - return self.preprocess_body(body) - - def list(self, limit: int, offset: int, parameters: Dict[str, Any]) -> List[Any]: - count: int = -1 - entities: List[Any] = [] - payload: Dict[str, Any] = parameters.copy() - payload.update(self.scope) - payload["offset"] = offset - payload["limit"] = BATCH_SIZE - while limit != 0: - if limit > BATCH_SIZE: - limit -= BATCH_SIZE - else: - payload["limit"] = limit - limit = 0 - result: Mapping[str, Any] = self.call("list", parameters=payload) - count = result["count"] - entities.extend(result["results"]) - if result["next"] is None: - break - payload["offset"] += payload["limit"] - else: - self.pulp_ctx.echo( - _("Not all {count} entries were shown.").format(count=count), err=True - ) - return entities - - def find(self, **kwargs: Any) -> Any: - search_result = self.list(limit=1, offset=0, parameters=kwargs) - if len(search_result) != 1: - raise PulpException( - _("Could not find {entity} with {kwargs}.").format( - entity=self.ENTITY, kwargs=kwargs - ) - ) - return search_result[0] - - def show(self, href: Optional[str] = None) -> Any: - return self.call("read", parameters={self.HREF: href or self.pulp_href}) - - def create( - self, - body: EntityDefinition, - parameters: Optional[Mapping[str, Any]] = None, - uploads: Optional[UploadsMap] = None, - non_blocking: bool = False, - ) -> Any: - _parameters = self.scope - if parameters: - _parameters.update(parameters) - if body is not None: - body = self.preprocess_entity(body, partial=False) - result = self.call( - "create", - parameters=_parameters, - body=body, - uploads=uploads, - non_blocking=non_blocking, - ) - if not non_blocking and result["pulp_href"].startswith(self.pulp_ctx.api_path + "tasks/"): - result = self.show(result["created_resources"][0]) - return result - - def update( - self, - href: Optional[str] = None, - body: Optional[EntityDefinition] = None, - parameters: Optional[Mapping[str, Any]] = None, - uploads: Optional[UploadsMap] = None, - non_blocking: bool = False, - ) -> Any: - # Workaround for plugins that do not have ID_PREFIX in place - if hasattr(self, "UPDATE_ID") and not hasattr(self, "PARTIAL_UPDATE_ID"): - self.PARTIAL_UPDATE_ID = getattr(self, "UPDATE_ID") - # ---------------------------------------------------------- - _parameters = {self.HREF: href or self.pulp_href} - if parameters: - _parameters.update(parameters) - if body is not None: - body = self.preprocess_entity(body, partial=False) - return self.call( - "partial_update", - parameters=_parameters, - body=body, - uploads=uploads, - non_blocking=non_blocking, - ) - - def delete(self, href: Optional[str] = None, non_blocking: bool = False) -> Any: - return self.call( - "delete", parameters={self.HREF: href or self.pulp_href}, non_blocking=non_blocking - ) - - def set_label(self, key: str, value: str, non_blocking: bool = False) -> Any: - # We would have a dedicated api for this ideally. - labels = self.entity["pulp_labels"] - labels[key] = value - return self.update(body={"pulp_labels": labels}, non_blocking=non_blocking) - - def unset_label(self, key: str, non_blocking: bool = False) -> Any: - # We would have a dedicated api for this ideally. - labels = self.entity["pulp_labels"] - try: - labels.pop(key) - except KeyError: - raise PulpException(_("Could not find label with key '{key}'.").format(key=key)) - return self.update(body={"pulp_labels": labels}, non_blocking=non_blocking) - - def show_label(self, key: str) -> Any: - # We would have a dedicated api for this ideally. - labels = self.entity["pulp_labels"] - try: - return labels[key] - except KeyError: - raise PulpException(_("Could not find label with key '{key}'.").format(key=key)) - - def my_permissions(self) -> Any: - self.needs_capability("roles") - return self.call("my_permissions", parameters={self.HREF: self.pulp_href}) - - def list_roles(self) -> Any: - self.needs_capability("roles") - return self.call("list_roles", parameters={self.HREF: self.pulp_href}) - - def add_role(self, role: str, users: List[str], groups: List[str]) -> Any: - self.needs_capability("roles") - return self.call( - "add_role", - parameters={self.HREF: self.pulp_href}, - body={"role": role, "users": users, "groups": groups}, - ) - - def remove_role(self, role: str, users: List[str], groups: List[str]) -> Any: - self.needs_capability("roles") - return self.call( - "remove_role", - parameters={self.HREF: self.pulp_href}, - body={"role": role, "users": users, "groups": groups}, - ) - - def capable(self, capability: str) -> bool: - return capability in self.CAPABILITIES and all( - (self.pulp_ctx.has_plugin(pr) for pr in self.CAPABILITIES[capability]) - ) - - def needs_capability(self, capability: str) -> None: - if capability in self.CAPABILITIES: - for pr in self.CAPABILITIES[capability]: - self.pulp_ctx.needs_plugin(pr) - else: - raise PulpException( - _("Capability '{capability}' needed on '{entity}' for this command.").format( - capability=capability, entity=self.ENTITY - ) - ) - - -class PulpRemoteContext(PulpEntityContext): - """ - Base class for remote specific contexts. - """ - - ENTITY = _("remote") - ENTITIES = _("remotes") - ID_PREFIX = "remotes" - HREF_PATTERN = r"remotes/(?P[\w\-_]+)/(?P[\w\-_]+)/" - NULLABLES = { - "ca_cert", - "client_cert", - "client_key", - "username", - "password", - "proxy_url", - "proxy_username", - "proxy_password", - "download_concurrency", - "max_retries", - "total_timeout", - "connect_timeout", - "sock_connect_timeout", - "sock_read_timeout", - "rate_limit", - } - - -class PulpDistributionContext(PulpEntityContext): - ENTITY = _("distribution") - ENTITIES = _("distributions") - ID_PREFIX = "distributions" - HREF_PATTERN = r"distributions/(?P[\w\-_]+)/(?P[\w\-_]+)/" - - -class PulpRepositoryVersionContext(PulpEntityContext): - """ - Base class for repository version specific contexts. - This class provides the basic CRUD commands and - ties its instances to the global PulpContext for api access. - """ - - ENTITY = _("repository version") - ENTITIES = _("repository versions") - repository_ctx: "PulpRepositoryContext" - - def __init__(self, pulp_ctx: PulpContext, repository_ctx: "PulpRepositoryContext") -> None: - super().__init__(pulp_ctx) - self.repository_ctx = repository_ctx - - @property - def scope(self) -> Dict[str, Any]: - return {self.repository_ctx.HREF: self.repository_ctx.pulp_href} - - def repair(self, href: Optional[str] = None) -> Any: - return self.call("repair", parameters={self.HREF: href or self.pulp_href}, body={}) - - -class PulpRepositoryContext(PulpEntityContext): - """ - Base class for repository specific contexts. - This class provides the basic CRUD commands as well as synchronizing and - ties its instances to the global PulpContext for api access. - """ - - ENTITY = _("repository") - ENTITIES = _("repositories") - HREF_PATTERN = r"repositories/(?P[\w\-_]+)/(?P[\w\-_]+)/" - ID_PREFIX = "repositories" - VERSION_CONTEXT: ClassVar[Type[PulpRepositoryVersionContext]] - NULLABLES = {"description", "retain_repo_versions"} - - def get_version_context(self) -> PulpRepositoryVersionContext: - return self.VERSION_CONTEXT(self.pulp_ctx, self) - - def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition: - body = super().preprocess_entity(body, partial=partial) - if self.pulp_ctx.has_plugin(PluginRequirement("core", "3.13.0", "3.15.0")): - # "retain_repo_versions" has been named "retained_versions" until pulpcore 3.15 - # https://github.com/pulp/pulpcore/pull/1472 - if "retain_repo_versions" in body: - body["retained_versions"] = body.pop("retain_repo_versions") - return body - - def sync(self, href: Optional[str] = None, body: Optional[EntityDefinition] = None) -> Any: - return self.call("sync", parameters={self.HREF: href or self.pulp_href}, body=body or {}) - - def modify( - self, - href: Optional[str] = None, - add_content: Optional[List[str]] = None, - remove_content: Optional[List[str]] = None, - base_version: Optional[str] = None, - ) -> Any: - body: Dict[str, Any] = {} - if add_content is not None: - body["add_content_units"] = add_content - if remove_content is not None: - body["remove_content_units"] = remove_content - if base_version is not None: - body["base_version"] = base_version - return self.call("modify", parameters={self.HREF: href or self.pulp_href}, body=body) - - -class PulpContentContext(PulpEntityContext): - """ - Base class for content specific contexts - """ - - ENTITY = _("content") - ENTITIES = _("content") - ID_PREFIX = "content" - - -class PulpACSContext(PulpEntityContext): - ENTITY = _("ACS") - ENTITIES = _("ACSes") - HREF_PATTERN = r"acs/(?P[\w\-_]+)/(?P[\w\-_]+)/" - ID_PREFIX = "acs" - - def refresh(self, href: Optional[str] = None) -> Any: - return self.call("refresh", parameters={self.HREF: href or self.pulp_href}) - - -EntityFieldDefinition = Union[None, str, PulpEntityContext] - - -############################################################################## -# Registries for Contexts of different sorts -# A command can use these to identify e.g. all repository types known to the CLI -# Note that it will only be fully populated, once all plugins are loaded. - - -registered_repository_contexts: Dict[str, Type[PulpRepositoryContext]] = {} diff --git a/pulpcore/cli/common/debug.py b/pulpcore/cli/common/debug.py index 28693ceda..fbc2299c3 100644 --- a/pulpcore/cli/common/debug.py +++ b/pulpcore/cli/common/debug.py @@ -2,15 +2,15 @@ from typing import IO, Any, Dict, Iterable, Optional import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import ( PulpCLIContext, load_json_callback, pass_pulp_context, pulp_group, ) -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext @@ -45,7 +45,7 @@ def task_summary(pulp_ctx: PulpCLIContext) -> None: """ List a summary of tasks by status. """ - from pulpcore.cli.core.context import PulpTaskContext + from pulp_glue.core.context import PulpTaskContext result = PulpTaskContext(pulp_ctx).summary() pulp_ctx.output_result(result) diff --git a/pulpcore/cli/common/generic.py b/pulpcore/cli/common/generic.py index 547c37add..90ccdbb18 100644 --- a/pulpcore/cli/common/generic.py +++ b/pulpcore/cli/common/generic.py @@ -8,8 +8,7 @@ import schema as s import yaml from click.decorators import FC, F - -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( DATETIME_FORMATS, DEFAULT_LIMIT, EntityDefinition, @@ -26,7 +25,7 @@ PulpRepositoryContext, PulpRepositoryVersionContext, ) -from pulpcore.cli.common.i18n import get_translation +from pulp_glue.common.i18n import get_translation try: from pygments import highlight diff --git a/pulpcore/cli/common/i18n.py b/pulpcore/cli/common/i18n.py index 22f7dad37..7cb83a429 100644 --- a/pulpcore/cli/common/i18n.py +++ b/pulpcore/cli/common/i18n.py @@ -1,15 +1,3 @@ -import gettext -from functools import lru_cache +from pulp_glue.common.i18n import get_translation # noqa: F401 -import pkg_resources - - -def get_translation(name: str) -> gettext.NullTranslations: - localedir = pkg_resources.resource_filename(name, "locale") - return _get_translation_for_domain("messages", localedir) - - -# Need to call lru_cache() before using it as a decorator for python 3.7 compatibility -@lru_cache(maxsize=None) -def _get_translation_for_domain(domain: str, localedir: str) -> gettext.NullTranslations: - return gettext.translation(domain, localedir=localedir, fallback=True) +__all__ = ["get_translation"] diff --git a/pulpcore/cli/container/__init__.py b/pulpcore/cli/container/__init__.py index 37f286c6f..fa7f28b60 100644 --- a/pulpcore/cli/container/__init__.py +++ b/pulpcore/cli/container/__init__.py @@ -1,10 +1,10 @@ from typing import Any import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_group -from pulpcore.cli.common.i18n import get_translation from pulpcore.cli.container.content import content from pulpcore.cli.container.distribution import distribution from pulpcore.cli.container.namespace import namespace diff --git a/pulpcore/cli/container/content.py b/pulpcore/cli/container/content.py index 36666df11..8f8fe590a 100644 --- a/pulpcore/cli/container/content.py +++ b/pulpcore/cli/container/content.py @@ -2,8 +2,13 @@ from typing import Any import click +from pulp_glue.common.context import PulpEntityContext +from pulp_glue.container.context import ( + PulpContainerBlobContext, + PulpContainerManifestContext, + PulpContainerTagContext, +) -from pulpcore.cli.common.context import PulpEntityContext from pulpcore.cli.common.generic import ( GroupOption, PulpCLIContext, @@ -14,11 +19,6 @@ pulp_option, show_command, ) -from pulpcore.cli.container.context import ( - PulpContainerBlobContext, - PulpContainerManifestContext, - PulpContainerTagContext, -) _ = gettext.gettext diff --git a/pulpcore/cli/container/distribution.py b/pulpcore/cli/container/distribution.py index 26bc0b656..2368861fb 100644 --- a/pulpcore/cli/container/distribution.py +++ b/pulpcore/cli/container/distribution.py @@ -1,8 +1,14 @@ from typing import Dict, Optional, Union, cast import click +from pulp_glue.common.context import EntityDefinition, PulpEntityContext, PulpRepositoryContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.container.context import ( + PulpContainerDistributionContext, + PulpContainerPushRepositoryContext, + PulpContainerRepositoryContext, +) -from pulpcore.cli.common.context import EntityDefinition, PulpEntityContext, PulpRepositoryContext from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -21,12 +27,6 @@ role_command, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.container.context import ( - PulpContainerDistributionContext, - PulpContainerPushRepositoryContext, - PulpContainerRepositoryContext, -) translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/container/namespace.py b/pulpcore/cli/container/namespace.py index 40f563b98..bb76b6042 100644 --- a/pulpcore/cli/container/namespace.py +++ b/pulpcore/cli/container/namespace.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.container.context import PulpContainerNamespaceContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -13,8 +15,6 @@ role_command, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.container.context import PulpContainerNamespaceContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/container/remote.py b/pulpcore/cli/container/remote.py index cb35cf19d..c1dd148c4 100644 --- a/pulpcore/cli/container/remote.py +++ b/pulpcore/cli/container/remote.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.container.context import PulpContainerRemoteContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -19,8 +21,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.container.context import PulpContainerRemoteContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/container/repository.py b/pulpcore/cli/container/repository.py index 579ca90b9..439bf9d13 100644 --- a/pulpcore/cli/container/repository.py +++ b/pulpcore/cli/container/repository.py @@ -2,12 +2,18 @@ from typing import Any, Dict, List, Optional import click - -from pulpcore.cli.common.context import ( - EntityFieldDefinition, - PulpRemoteContext, - PulpRepositoryContext, +from pulp_glue.common.context import EntityFieldDefinition, PulpRemoteContext, PulpRepositoryContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.container.context import ( + PulpContainerBaseRepositoryContext, + PulpContainerBlobContext, + PulpContainerManifestContext, + PulpContainerPushRepositoryContext, + PulpContainerRemoteContext, + PulpContainerRepositoryContext, + PulpContainerTagContext, ) + from pulpcore.cli.common.generic import ( create_command, destroy_command, @@ -30,17 +36,7 @@ update_command, version_command, ) -from pulpcore.cli.common.i18n import get_translation from pulpcore.cli.container.content import show_options -from pulpcore.cli.container.context import ( - PulpContainerBaseRepositoryContext, - PulpContainerBlobContext, - PulpContainerManifestContext, - PulpContainerPushRepositoryContext, - PulpContainerRemoteContext, - PulpContainerRepositoryContext, - PulpContainerTagContext, -) from pulpcore.cli.core.generic import task_command translation = get_translation(__name__) diff --git a/pulpcore/cli/core/access_policy.py b/pulpcore/cli/core/access_policy.py index a01d1592e..852763211 100644 --- a/pulpcore/cli/core/access_policy.py +++ b/pulpcore/cli/core/access_policy.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpAccessPolicyContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -12,8 +14,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpAccessPolicyContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/artifact.py b/pulpcore/cli/core/artifact.py index 9ac372bc4..9a9f1f648 100644 --- a/pulpcore/cli/core/artifact.py +++ b/pulpcore/cli/core/artifact.py @@ -1,6 +1,8 @@ from typing import IO import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpArtifactContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -12,8 +14,6 @@ pulp_group, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpArtifactContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/content.py b/pulpcore/cli/core/content.py index 5eb3ebf74..6c099f24d 100644 --- a/pulpcore/cli/core/content.py +++ b/pulpcore/cli/core/content.py @@ -1,8 +1,8 @@ import click +from pulp_glue.common.context import PulpContentContext +from pulp_glue.common.i18n import get_translation -from pulpcore.cli.common.context import PulpContentContext from pulpcore.cli.common.generic import PulpCLIContext, list_command, pass_pulp_context, pulp_group -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/content_guard.py b/pulpcore/cli/core/content_guard.py index 66bfd12b7..271efbb78 100644 --- a/pulpcore/cli/core/content_guard.py +++ b/pulpcore/cli/core/content_guard.py @@ -1,6 +1,12 @@ from typing import List, Optional import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import ( + PulpContentGuardContext, + PulpContentRedirectContentGuardContext, + PulpRbacContentGuardContext, +) from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -16,12 +22,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import ( - PulpContentGuardContext, - PulpContentRedirectContentGuardContext, - PulpRbacContentGuardContext, -) translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/context.py b/pulpcore/cli/core/context.py index 44feeeed1..f35462833 100644 --- a/pulpcore/cli/core/context.py +++ b/pulpcore/cli/core/context.py @@ -1,506 +1,3 @@ -import datetime -import hashlib -import os -import sys -from typing import IO, Any, ClassVar, Dict, List, Optional +from pulp_glue.core.context import PulpSigningServiceContext # noqa: F401 -from pulpcore.cli.common.context import ( - EntityDefinition, - PluginRequirement, - PulpContext, - PulpEntityContext, - PulpException, -) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.common.openapi import UploadsMap - -translation = get_translation(__name__) -_ = translation.gettext - - -class PulpAccessPolicyContext(PulpEntityContext): - ENTITY = _("access policy") - ENTITIES = _("access policies") - HREF = "access_policy_href" - ID_PREFIX = "access_policies" - - def reset(self) -> Any: - self.pulp_ctx.needs_plugin(PluginRequirement("core", min="3.17.0")) - return self.call("reset", parameters={self.HREF: self.pulp_href}) - - def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition: - body = super().preprocess_entity(body, partial=partial) - if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): - if "creation_hooks" in body: - body["permissions_assignment"] = body.pop("creation_hooks") - return body - - -class PulpArtifactContext(PulpEntityContext): - ENTITY = _("artifact") - ENTITIES = _("artifacts") - HREF = "artifact_href" - ID_PREFIX = "artifacts" - - def upload( - self, file: IO[bytes], chunk_size: int = 1000000, sha256: Optional[str] = None - ) -> Any: - size = os.path.getsize(file.name) - - sha256_hasher = hashlib.sha256() - for chunk in iter(lambda: file.read(chunk_size), b""): - sha256_hasher.update(chunk) - sha256_digest = sha256_hasher.hexdigest() - file.seek(0) - - if sha256 is not None and sha256_digest != sha256: - raise PulpException(_("File digest does not match.")) - - # check whether the file exists before uploading - result = self.list(limit=1, offset=0, parameters={"sha256": sha256_digest}) - if len(result) > 0: - self.pulp_ctx.echo(_("Artifact already exists."), err=True) - self.pulp_href = result[0]["pulp_href"] - return result[0]["pulp_href"] - - self.pulp_ctx.echo(_("Uploading file {filename}").format(filename=file.name), err=True) - - if chunk_size > size: - # if chunk_size is bigger than the file size, just upload it directly - artifact: Dict[str, Any] = self.create({"sha256": sha256_digest, "file": file}) - self.pulp_href = artifact["pulp_href"] - return artifact["pulp_href"] - - upload_ctx = PulpUploadContext(self.pulp_ctx) - upload_href = upload_ctx.upload_file(file, chunk_size) - - self.pulp_ctx.echo(_("Creating artifact."), err=True) - try: - task = upload_ctx.commit( - upload_href, - sha256_digest, - ) - except Exception as e: - upload_ctx.delete(upload_href) - raise e - self.pulp_href = task["created_resources"][0] - return task["created_resources"][0] - - -class PulpExporterContext(PulpEntityContext): - ENTITY = _("Pulp exporter") - ENTITIES = _("Pulp exporters") - HREF = "pulp_exporter_href" - ID_PREFIX = "exporters_core_pulp" - - -class PulpExportContext(PulpEntityContext): - ENTITY = _("Pulp export") - ENTITIES = _("Pulp exports") - HREF = "pulp_pulp_export_href" - ID_PREFIX = "exporters_core_pulp_exports" - exporter: EntityDefinition - - @property - def scope(self) -> Dict[str, Any]: - return {PulpExporterContext.HREF: self.exporter["pulp_href"]} - - -class PulpGroupContext(PulpEntityContext): - ENTITY = _("user group") - ENTITIES = _("user groups") - # Handled by a workaround - # HREF = "group_href" - ID_PREFIX = "groups" - CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]} - - @property - def HREF(self) -> str: # type:ignore - if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): - return "auth_group_href" - return "group_href" - - -class PulpGroupPermissionContext(PulpEntityContext): - ENTITY = _("group permission") - ENTITIES = _("group permissions") - NEEDS_PLUGINS = [PluginRequirement("core", max="3.20.0", feature=_("group permissions"))] - group_ctx: PulpGroupContext - - def __init__(self, pulp_ctx: PulpContext, group_ctx: PulpGroupContext) -> None: - super().__init__(pulp_ctx) - self.group_ctx = group_ctx - - def call( - self, - operation: str, - non_blocking: bool = False, - parameters: Optional[Dict[str, Any]] = None, - body: Optional[Dict[str, Any]] = None, - uploads: Optional[UploadsMap] = None, - validate_body: bool = False, - ) -> Any: - """Workaroud because the openapi spec for GroupPermissions has always been broken. - - This will probably not be fixed upstream, and GroupPermissions are removed from pulpcore. - So we just skip linting here. - """ - return super().call( - operation, - non_blocking=non_blocking, - parameters=parameters, - body=body, - uploads=uploads, - validate_body=validate_body, - ) - - def find(self, **kwargs: Any) -> Any: - """Workaroud for the missing ability to filter""" - # # TODO fix upstream and adjust to guard for the proper version - # # https://pulp.plan.io/issues/8241 - # if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.99.dev")): - # # Workaround not needed anymore - # return super().find(**kwargs) - search_result = self.list(limit=sys.maxsize, offset=0, parameters={}) - for key, value in kwargs.items(): - search_result = [res for res in search_result if res[key] == value] - if len(search_result) != 1: - raise PulpException( - _("Could not find {entity} with {kwargs}.").format( - entity=self.ENTITY, kwargs=kwargs - ) - ) - return search_result[0] - - @property - def scope(self) -> Dict[str, Any]: - return {self.group_ctx.HREF: self.group_ctx.pulp_href} - - -class PulpGroupModelPermissionContext(PulpGroupPermissionContext): - ENTITY = _("group model permission") - ENTITIES = _("group model permissions") - # Handled by a workaround - # HREF = "groups_model_permission_href" - ID_PREFIX = "groups_model_permissions" - - @property - def HREF(self) -> str: # type:ignore - if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): - return "auth_groups_model_permission_href" - return "groups_model_permission_href" - - -class PulpGroupObjectPermissionContext(PulpGroupPermissionContext): - ENTITY = _("group object permission") - ENTITIES = _("group object permissions") - # Handled by a workaround - # HREF = "groups_object_permission_href" - ID_PREFIX = "groups_object_permissions" - - @property - def HREF(self) -> str: # type:ignore - if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): - return "auth_groups_object_permission_href" - return "groups_object_permission_href" - - -class PulpGroupRoleContext(PulpEntityContext): - ENTITY = _("group role") - ENTITIES = _("group roles") - HREF = "groups_group_role_href" - ID_PREFIX = "groups_roles" - NULLABLES = {"content_object"} - group_ctx: PulpGroupContext - - def __init__(self, pulp_ctx: PulpContext, group_ctx: PulpGroupContext) -> None: - super().__init__(pulp_ctx) - self.group_ctx = group_ctx - - @property - def scope(self) -> Dict[str, Any]: - return {self.group_ctx.HREF: self.group_ctx.pulp_href} - - -class PulpGroupUserContext(PulpEntityContext): - ENTITY = _("group user") - ENTITIES = _("group users") - # Handled by a workaround - # HREF = "groups_user_href" - ID_PREFIX = "groups_users" - group_ctx: PulpGroupContext - - @property - def HREF(self) -> str: # type:ignore - if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): - return "auth_groups_user_href" - return "groups_user_href" - - def __init__(self, pulp_ctx: PulpContext, group_ctx: PulpGroupContext) -> None: - super().__init__(pulp_ctx) - self.group_ctx = group_ctx - - @property - def scope(self) -> Dict[str, Any]: - return {self.group_ctx.HREF: self.group_ctx.pulp_href} - - -class PulpContentGuardContext(PulpEntityContext): - ENTITY = "content guard" - ENTITIES = "content guards" - ID_PREFIX = "contentguards" - HREF_PATTERN = r"contentguards/(?P[\w\-_]+)/(?P[\w\-_]+)/" - - -class PulpContentRedirectContentGuardContext(PulpEntityContext): - ENTITY = "content redirect content guard" - ENTITIES = "content redirect content guards" - HREF = "content_redirect_content_guard_href" - ID_PREFIX = "contentguards_core_content_redirect" - NEEDS_PLUGINS = [PluginRequirement("core", "3.18.0")] - - -class PulpImporterContext(PulpEntityContext): - ENTITY = _("Pulp importer") - ENTITIES = _("Pulp importers") - HREF = "pulp_importer_href" - ID_PREFIX = "importers_core_pulp" - - -class PulpPublicationContext(PulpEntityContext): - ENTITY = _("Pulp publication") - ENTITIES = _("Pulp publications") - ID_PREFIX = "publications" - HREF_PATTERN = r"publications/(?P[\w\-_]+)/(?P[\w\-_]+)/" - - -class PulpRbacContentGuardContext(PulpContentGuardContext): - ENTITY = "RBAC content guard" - ENTITIES = "RBAC content guards" - HREF = "r_b_a_c_content_guard_href" - ID_PREFIX = "contentguards_core_rbac" - DOWNLOAD_ROLE: ClassVar[str] = "core.rbaccontentguard_downloader" - CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]} - NEEDS_PLUGINS = [PluginRequirement("core", "3.15.0")] - - def assign( - self, - href: Optional[str] = None, - users: Optional[List[str]] = None, - groups: Optional[List[str]] = None, - ) -> Any: - if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): - body: EntityDefinition = {"users": users, "groups": groups} - body["role"] = self.DOWNLOAD_ROLE - return self.call("add_role", parameters={self.HREF: href or self.pulp_href}, body=body) - else: - body = {"usernames": users, "groupnames": groups} - return self.call( - "assign_permission", parameters={self.HREF: href or self.pulp_href}, body=body - ) - - def remove( - self, - href: Optional[str] = None, - users: Optional[List[str]] = None, - groups: Optional[List[str]] = None, - ) -> Any: - if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0")): - body: EntityDefinition = {"users": users, "groups": groups} - body["role"] = self.DOWNLOAD_ROLE - return self.call( - "remove_role", parameters={self.HREF: href or self.pulp_href}, body=body - ) - else: - body = {"usernames": users, "groupnames": groups} - return self.call( - "remove_permission", parameters={self.HREF: href or self.pulp_href}, body=body - ) - - -class PulpRoleContext(PulpEntityContext): - ENTITY = _("role") - ENTITIES = _("roles") - HREF = "role_href" - ID_PREFIX = "roles" - NULLABLES = {"description"} - NEEDS_PLUGINS = [PluginRequirement("core", "3.17.0")] - - -class PulpSigningServiceContext(PulpEntityContext): - ENTITY = _("signing service") - ENTITIES = _("signing services") - HREF = "signing_service_href" - ID_PREFIX = "signing_services" - - -class PulpTaskContext(PulpEntityContext): - ENTITY = _("task") - ENTITIES = _("tasks") - HREF = "task_href" - ID_PREFIX = "tasks" - CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]} - - resource_context: Optional[PulpEntityContext] = None - - def list(self, limit: int, offset: int, parameters: Dict[str, Any]) -> List[Any]: - if not self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.22.0dev")): - parameters = parameters.copy() - reserved_resources = parameters.pop("reserved_resources", None) - exclusive_resources = parameters.pop("exclusive_resources", None) - shared_resources = parameters.pop("shared_resources", None) - parameters.pop("reserved_resources__in", None) - parameters.pop("exclusive_resources__in", None) - parameters.pop("shared_resources__in", None) - reserved_resources_record = [] - if reserved_resources: - reserved_resources_record.append(reserved_resources) - if exclusive_resources: - reserved_resources_record.append(exclusive_resources) - if shared_resources: - reserved_resources_record.append("shared:" + shared_resources) - if len(reserved_resources_record) > 1: - self.pulp_ctx.needs_plugin( - PluginRequirement( - "core", - min="3.22.0dev", - feature=_("specify multiple reserved resources"), - ), - ) - parameters["reserved_resources_record"] = reserved_resources_record - - return super().list(limit=limit, offset=offset, parameters=parameters) - - def cancel(self, task_href: Optional[str] = None) -> Any: - return self.call( - "cancel", - parameters={self.HREF: task_href or self.pulp_href}, - body={"state": "canceled"}, - ) - - @property - def scope(self) -> Dict[str, Any]: - if self.resource_context: - if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.22.0dev")): - return {"reserved_resources": self.resource_context.pulp_href} - else: - return {"reserved_resources_record": [self.resource_context.pulp_href]} - else: - return {} - - def purge( - self, - finished_before: Optional[datetime.datetime], - states: Optional[List[str]], - ) -> Any: - body: Dict[str, Any] = {} - if finished_before: - body["finished_before"] = finished_before - if states: - body["states"] = states - return self.call( - "purge", - body=body, - ) - - def summary(self) -> Dict[str, int]: - task_states = ["waiting", "skipped", "running", "completed", "failed", "canceled"] - if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.14.0")): - task_states.append("canceling") - result = {} - for state in task_states: - payload = {"limit": 1, "state": state} - result[state] = self.call("list", parameters=payload)["count"] - return result - - -class PulpTaskGroupContext(PulpEntityContext): - ENTITY = _("task group") - ENTITIES = _("task groups") - HREF = "task_group_href" - ID_PREFIX = "task_groups" - - -class PulpUploadContext(PulpEntityContext): - ENTITY = _("upload") - ENTITIES = _("uploads") - HREF = "upload_href" - ID_PREFIX = "uploads" - - def upload_chunk( - self, - chunk: bytes, - size: int, - start: int, - non_blocking: bool = False, - ) -> Any: - end: int = start + len(chunk) - 1 - parameters = {self.HREF: self.pulp_href, "Content-Range": f"bytes {start}-{end}/{size}"} - return self.call( - "update", - parameters=parameters, - body={"sha256": hashlib.sha256(chunk).hexdigest(), "file": chunk}, - non_blocking=non_blocking, - ) - - def commit(self, upload_href: str, sha256: str) -> Any: - return self.call( - "commit", - parameters={self.HREF: upload_href}, - body={"sha256": sha256}, - ) - - def upload_file(self, file: IO[bytes], chunk_size: int = 1000000) -> Any: - """Upload a file and return the uncommitted upload_href.""" - start = 0 - size = os.path.getsize(file.name) - upload_href = self.create(body={"size": size})["pulp_href"] - try: - self.pulp_href = upload_href - while start < size: - chunk = file.read(chunk_size) - self.upload_chunk( - chunk=chunk, - size=size, - start=start, - ) - start += chunk_size - self.pulp_ctx.echo(".", nl=False, err=True) - except Exception as e: - self.delete(upload_href) - raise e - self.pulp_ctx.echo(_("Upload complete."), err=True) - return upload_href - - -class PulpUserContext(PulpEntityContext): - ENTITY = _("user") - ENTITIES = _("users") - HREF = "auth_user_href" - ID_PREFIX = "users" - NULLABLES = {"password"} - - -class PulpUserRoleContext(PulpEntityContext): - ENTITY = _("user role") - ENTITIES = _("user roles") - HREF = "auth_users_user_role_href" - ID_PREFIX = "users_roles" - NULLABLES = {"content_object"} - user_ctx: PulpUserContext - - def __init__(self, pulp_ctx: PulpContext, user_ctx: PulpUserContext) -> None: - super().__init__(pulp_ctx) - self.user_ctx = user_ctx - - @property - def scope(self) -> Dict[str, Any]: - return {self.user_ctx.HREF: self.user_ctx.pulp_href} - - -class PulpWorkerContext(PulpEntityContext): - ENTITY = _("worker") - ENTITIES = _("workers") - HREF = "worker_href" - ID_PREFIX = "workers" - HREF_PATTERN = r"workers/" +__all__ = ["PulpSigningServiceContext"] diff --git a/pulpcore/cli/core/distribution.py b/pulpcore/cli/core/distribution.py index 9195350b9..0a618e69d 100644 --- a/pulpcore/cli/core/distribution.py +++ b/pulpcore/cli/core/distribution.py @@ -1,6 +1,6 @@ import click +from pulp_glue.common.context import PluginRequirement, PulpDistributionContext -from pulpcore.cli.common.context import PluginRequirement, PulpDistributionContext from pulpcore.cli.common.generic import ( PulpCLIContext, distribution_filter_options, diff --git a/pulpcore/cli/core/export.py b/pulpcore/cli/core/export.py index ce4a38bf6..c34d12f85 100644 --- a/pulpcore/cli/core/export.py +++ b/pulpcore/cli/core/export.py @@ -2,14 +2,16 @@ from typing import Any, Dict, Iterable, Optional, Tuple import click - -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( DEFAULT_LIMIT, EntityDefinition, PulpRepositoryContext, PulpRepositoryVersionContext, registered_repository_contexts, ) +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpExportContext, PulpExporterContext + from pulpcore.cli.common.generic import ( PulpCLIContext, destroy_command, @@ -19,8 +21,6 @@ pulp_group, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpExportContext, PulpExporterContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/exporter.py b/pulpcore/cli/core/exporter.py index 7021a8390..c606e40f3 100644 --- a/pulpcore/cli/core/exporter.py +++ b/pulpcore/cli/core/exporter.py @@ -1,13 +1,15 @@ from typing import Any, Dict, Iterable import click - -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityFieldDefinition, PulpEntityContext, PulpRepositoryContext, registered_repository_contexts, ) +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpExporterContext + from pulpcore.cli.common.generic import ( PulpCLIContext, destroy_command, @@ -20,8 +22,6 @@ resource_option, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpExporterContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/generic.py b/pulpcore/cli/core/generic.py index 321328b5a..729f81f7d 100644 --- a/pulpcore/cli/core/generic.py +++ b/pulpcore/cli/core/generic.py @@ -2,8 +2,10 @@ from typing import Any, Optional import click +from pulp_glue.common.context import DATETIME_FORMATS, PluginRequirement, PulpEntityContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpTaskContext, PulpWorkerContext -from pulpcore.cli.common.context import DATETIME_FORMATS, PluginRequirement, PulpEntityContext from pulpcore.cli.common.generic import ( PulpCLIContext, list_command, @@ -13,8 +15,6 @@ pulp_option, resource_option, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpTaskContext, PulpWorkerContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/group.py b/pulpcore/cli/core/group.py index 5be48221a..be27beb0e 100644 --- a/pulpcore/cli/core/group.py +++ b/pulpcore/cli/core/group.py @@ -1,8 +1,18 @@ from typing import Optional import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import ( + PulpGroupContext, + PulpGroupModelPermissionContext, + PulpGroupObjectPermissionContext, + PulpGroupPermissionContext, + PulpGroupRoleContext, + PulpGroupUserContext, + PulpUserContext, +) -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -18,16 +28,6 @@ role_command, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import ( - PulpGroupContext, - PulpGroupModelPermissionContext, - PulpGroupObjectPermissionContext, - PulpGroupPermissionContext, - PulpGroupRoleContext, - PulpGroupUserContext, - PulpUserContext, -) translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/importer.py b/pulpcore/cli/core/importer.py index 930990d33..c89932029 100644 --- a/pulpcore/cli/core/importer.py +++ b/pulpcore/cli/core/importer.py @@ -1,6 +1,8 @@ from typing import Dict, List, Tuple, Union import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpImporterContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -13,8 +15,6 @@ pulp_group, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpImporterContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/orphan.py b/pulpcore/cli/core/orphan.py index 3ec0dfeee..2279cae0f 100644 --- a/pulpcore/cli/core/orphan.py +++ b/pulpcore/cli/core/orphan.py @@ -1,6 +1,8 @@ from typing import Any -from pulpcore.cli.common.context import PluginRequirement +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation + from pulpcore.cli.common.generic import ( PulpCLIContext, load_json_callback, @@ -8,7 +10,6 @@ pulp_group, pulp_option, ) -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/orphans.py b/pulpcore/cli/core/orphans.py index 17c4b3306..99bcf5a76 100644 --- a/pulpcore/cli/core/orphans.py +++ b/pulpcore/cli/core/orphans.py @@ -1,5 +1,6 @@ +from pulp_glue.common.i18n import get_translation + from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_group -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/publication.py b/pulpcore/cli/core/publication.py index d064ad3d7..21a1a6e0d 100644 --- a/pulpcore/cli/core/publication.py +++ b/pulpcore/cli/core/publication.py @@ -1,6 +1,7 @@ import click +from pulp_glue.common.context import PluginRequirement, registered_repository_contexts +from pulp_glue.core.context import PulpPublicationContext -from pulpcore.cli.common.context import PluginRequirement, registered_repository_contexts from pulpcore.cli.common.generic import ( PulpCLIContext, list_command, @@ -9,7 +10,6 @@ pulp_group, resource_option, ) -from pulpcore.cli.core.context import PulpPublicationContext repository_option = resource_option( "--repository", diff --git a/pulpcore/cli/core/remote.py b/pulpcore/cli/core/remote.py index fcee7a055..1aac1a418 100644 --- a/pulpcore/cli/core/remote.py +++ b/pulpcore/cli/core/remote.py @@ -1,6 +1,6 @@ import click +from pulp_glue.common.context import PulpRemoteContext -from pulpcore.cli.common.context import PulpRemoteContext from pulpcore.cli.common.generic import ( PulpCLIContext, list_command, diff --git a/pulpcore/cli/core/repository.py b/pulpcore/cli/core/repository.py index f231b9d1a..980b5f8c1 100644 --- a/pulpcore/cli/core/repository.py +++ b/pulpcore/cli/core/repository.py @@ -1,6 +1,7 @@ import click +from pulp_glue.common.context import PulpRepositoryContext +from pulp_glue.common.i18n import get_translation -from pulpcore.cli.common.context import PulpRepositoryContext from pulpcore.cli.common.generic import ( PulpCLIContext, list_command, @@ -8,7 +9,6 @@ pass_pulp_context, pulp_group, ) -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/role.py b/pulpcore/cli/core/role.py index a5f274238..3a3e8b335 100644 --- a/pulpcore/cli/core/role.py +++ b/pulpcore/cli/core/role.py @@ -2,8 +2,9 @@ from typing import Iterable, Optional import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.core.context import PulpRoleContext -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -17,7 +18,6 @@ show_command, update_command, ) -from pulpcore.cli.core.context import PulpRoleContext _ = gettext.gettext NO_PERMISSION_KEY = "pulpcore.cli.core.role.no_permission" diff --git a/pulpcore/cli/core/show.py b/pulpcore/cli/core/show.py index 77fd69c17..cf7e4255e 100644 --- a/pulpcore/cli/core/show.py +++ b/pulpcore/cli/core/show.py @@ -1,7 +1,7 @@ import click +from pulp_glue.common.i18n import get_translation from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_command -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/signing_service.py b/pulpcore/cli/core/signing_service.py index 7461b65cf..97bc18873 100644 --- a/pulpcore/cli/core/signing_service.py +++ b/pulpcore/cli/core/signing_service.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpSigningServiceContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -9,8 +11,6 @@ pulp_group, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpSigningServiceContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/status.py b/pulpcore/cli/core/status.py index 61fbefbcb..acd311358 100644 --- a/pulpcore/cli/core/status.py +++ b/pulpcore/cli/core/status.py @@ -1,9 +1,9 @@ import time import click +from pulp_glue.common.i18n import get_translation from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_command -from pulpcore.cli.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/task.py b/pulpcore/cli/core/task.py index e862a8413..0a52dc6b8 100644 --- a/pulpcore/cli/core/task.py +++ b/pulpcore/cli/core/task.py @@ -4,8 +4,10 @@ from typing import Optional, Tuple import click +from pulp_glue.common.context import DATETIME_FORMATS, PluginRequirement, PulpEntityContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpTaskContext -from pulpcore.cli.common.context import DATETIME_FORMATS, PluginRequirement, PulpEntityContext from pulpcore.cli.common.generic import ( PulpCLIContext, destroy_command, @@ -17,8 +19,6 @@ pulp_option, role_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpTaskContext from pulpcore.cli.core.generic import task_filter translation = get_translation(__name__) diff --git a/pulpcore/cli/core/task_group.py b/pulpcore/cli/core/task_group.py index bf189bd78..36eac2313 100644 --- a/pulpcore/cli/core/task_group.py +++ b/pulpcore/cli/core/task_group.py @@ -1,8 +1,10 @@ from typing import Optional import click +from pulp_glue.common.context import PulpEntityContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpTaskGroupContext -from pulpcore.cli.common.context import PulpEntityContext from pulpcore.cli.common.generic import ( PulpCLIContext, href_option, @@ -12,8 +14,6 @@ pulp_group, pulp_option, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpTaskGroupContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/upload.py b/pulpcore/cli/core/upload.py index 778cd051b..7ed58490e 100644 --- a/pulpcore/cli/core/upload.py +++ b/pulpcore/cli/core/upload.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpUploadContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -9,8 +11,6 @@ pulp_group, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpUploadContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/user.py b/pulpcore/cli/core/user.py index 311f94b28..3182d5734 100644 --- a/pulpcore/cli/core/user.py +++ b/pulpcore/cli/core/user.py @@ -1,8 +1,8 @@ import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpUserContext, PulpUserRoleContext -from pulpcore.cli.common.context import ( # PulpEntityContext,; pass_entity_context, - PluginRequirement, -) from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -18,8 +18,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpUserContext, PulpUserRoleContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/core/worker.py b/pulpcore/cli/core/worker.py index dc74e8ca4..66f47d9fe 100644 --- a/pulpcore/cli/core/worker.py +++ b/pulpcore/cli/core/worker.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpWorkerContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -9,8 +11,6 @@ pulp_group, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpWorkerContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/file/__init__.py b/pulpcore/cli/file/__init__.py index 4865313c5..08e8d72a5 100644 --- a/pulpcore/cli/file/__init__.py +++ b/pulpcore/cli/file/__init__.py @@ -1,10 +1,10 @@ from typing import Any import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_group -from pulpcore.cli.common.i18n import get_translation from pulpcore.cli.file.acs import acs from pulpcore.cli.file.content import content from pulpcore.cli.file.distribution import distribution diff --git a/pulpcore/cli/file/acs.py b/pulpcore/cli/file/acs.py index 330f9fa58..71bcd5156 100644 --- a/pulpcore/cli/file/acs.py +++ b/pulpcore/cli/file/acs.py @@ -1,8 +1,10 @@ from typing import Iterable import click +from pulp_glue.common.context import PulpRemoteContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.file.context import PulpFileACSContext, PulpFileRemoteContext -from pulpcore.cli.common.context import PulpRemoteContext from pulpcore.cli.common.generic import ( PulpCLIContext, acs_lookup_option, @@ -19,8 +21,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.file.context import PulpFileACSContext, PulpFileRemoteContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/file/content.py b/pulpcore/cli/file/content.py index cc765a783..f13d12779 100644 --- a/pulpcore/cli/file/content.py +++ b/pulpcore/cli/file/content.py @@ -2,8 +2,11 @@ from typing import IO, Any, Dict, Optional, Union import click +from pulp_glue.common.context import PluginRequirement, PulpEntityContext, PulpRepositoryContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpArtifactContext, PulpUploadContext +from pulp_glue.file.context import PulpFileContentContext, PulpFileRepositoryContext -from pulpcore.cli.common.context import PluginRequirement, PulpEntityContext, PulpRepositoryContext from pulpcore.cli.common.generic import ( PulpCLIContext, chunk_size_option, @@ -16,9 +19,6 @@ resource_option, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpArtifactContext, PulpUploadContext -from pulpcore.cli.file.context import PulpFileContentContext, PulpFileRepositoryContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/file/distribution.py b/pulpcore/cli/file/distribution.py index 9b96d5831..af8f19d74 100644 --- a/pulpcore/cli/file/distribution.py +++ b/pulpcore/cli/file/distribution.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.file.context import PulpFileDistributionContext, PulpFileRepositoryContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -19,8 +21,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.file.context import PulpFileDistributionContext, PulpFileRepositoryContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/file/publication.py b/pulpcore/cli/file/publication.py index de2c12960..523df016b 100644 --- a/pulpcore/cli/file/publication.py +++ b/pulpcore/cli/file/publication.py @@ -1,6 +1,8 @@ import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation +from pulp_glue.file.context import PulpFilePublicationContext, PulpFileRepositoryContext -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -14,8 +16,6 @@ role_command, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.file.context import PulpFilePublicationContext, PulpFileRepositoryContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/file/remote.py b/pulpcore/cli/file/remote.py index 42a52d3f5..6161a2858 100644 --- a/pulpcore/cli/file/remote.py +++ b/pulpcore/cli/file/remote.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.file.context import PulpFileRemoteContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -18,8 +20,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.file.context import PulpFileRemoteContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/file/repository.py b/pulpcore/cli/file/repository.py index 9e58bb87a..4e3603205 100644 --- a/pulpcore/cli/file/repository.py +++ b/pulpcore/cli/file/repository.py @@ -2,13 +2,19 @@ import click import schema as s - -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityFieldDefinition, PluginRequirement, PulpRemoteContext, PulpRepositoryContext, ) +from pulp_glue.common.i18n import get_translation +from pulp_glue.file.context import ( + PulpFileContentContext, + PulpFileRemoteContext, + PulpFileRepositoryContext, +) + from pulpcore.cli.common.generic import ( GroupOption, PulpCLIContext, @@ -37,13 +43,7 @@ update_command, version_command, ) -from pulpcore.cli.common.i18n import get_translation from pulpcore.cli.core.generic import task_command -from pulpcore.cli.file.context import ( - PulpFileContentContext, - PulpFileRemoteContext, - PulpFileRepositoryContext, -) translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/migration/__init__.py b/pulpcore/cli/migration/__init__.py index 80353ef68..af33736a2 100644 --- a/pulpcore/cli/migration/__init__.py +++ b/pulpcore/cli/migration/__init__.py @@ -1,8 +1,8 @@ from typing import Any import click +from pulp_glue.common.context import PluginRequirement -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_group from pulpcore.cli.migration.plan import plan from pulpcore.cli.migration.pulp2 import pulp2 diff --git a/pulpcore/cli/migration/context.py b/pulpcore/cli/migration/context.py index 6838084e5..125c93964 100644 --- a/pulpcore/cli/migration/context.py +++ b/pulpcore/cli/migration/context.py @@ -1,7 +1,7 @@ from typing import Any, Optional -from pulpcore.cli.common.context import PulpEntityContext -from pulpcore.cli.common.i18n import get_translation +from pulp_glue.common.context import PulpEntityContext +from pulp_glue.common.i18n import get_translation translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/migration/plan.py b/pulpcore/cli/migration/plan.py index dc5d4fac1..223c831a2 100644 --- a/pulpcore/cli/migration/plan.py +++ b/pulpcore/cli/migration/plan.py @@ -1,4 +1,5 @@ import click +from pulp_glue.common.i18n import get_translation from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -12,7 +13,6 @@ pulp_group, show_command, ) -from pulpcore.cli.common.i18n import get_translation from pulpcore.cli.migration.context import PulpMigrationPlanContext translation = get_translation(__name__) diff --git a/pulpcore/cli/python/__init__.py b/pulpcore/cli/python/__init__.py index 09ef77469..95d97e42a 100644 --- a/pulpcore/cli/python/__init__.py +++ b/pulpcore/cli/python/__init__.py @@ -1,8 +1,8 @@ from typing import Any import click +from pulp_glue.common.context import PluginRequirement -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_group from pulpcore.cli.python.content import content from pulpcore.cli.python.distribution import distribution diff --git a/pulpcore/cli/python/content.py b/pulpcore/cli/python/content.py index 25f3dd0eb..320075fdd 100644 --- a/pulpcore/cli/python/content.py +++ b/pulpcore/cli/python/content.py @@ -2,8 +2,11 @@ from typing import IO, Any, Dict, Optional, Union import click +from pulp_glue.common.context import PluginRequirement, PulpEntityContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpArtifactContext, PulpUploadContext +from pulp_glue.python.context import PulpPythonContentContext, PulpPythonRepositoryContext -from pulpcore.cli.common.context import PluginRequirement, PulpEntityContext from pulpcore.cli.common.generic import ( PulpCLIContext, chunk_size_option, @@ -16,9 +19,6 @@ resource_option, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpArtifactContext, PulpUploadContext -from pulpcore.cli.python.context import PulpPythonContentContext, PulpPythonRepositoryContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/python/distribution.py b/pulpcore/cli/python/distribution.py index 233aa1cb7..50cd76607 100644 --- a/pulpcore/cli/python/distribution.py +++ b/pulpcore/cli/python/distribution.py @@ -1,6 +1,12 @@ import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation +from pulp_glue.python.context import ( + PulpPythonDistributionContext, + PulpPythonRemoteContext, + PulpPythonRepositoryContext, +) -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import ( PulpCLIContext, common_distribution_create_options, @@ -20,12 +26,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.python.context import ( - PulpPythonDistributionContext, - PulpPythonRemoteContext, - PulpPythonRepositoryContext, -) translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/python/publication.py b/pulpcore/cli/python/publication.py index e49a811e2..6dbe3cad3 100644 --- a/pulpcore/cli/python/publication.py +++ b/pulpcore/cli/python/publication.py @@ -1,6 +1,8 @@ import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation +from pulp_glue.python.context import PulpPythonPublicationContext, PulpPythonRepositoryContext -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -13,8 +15,6 @@ resource_option, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.python.context import PulpPythonPublicationContext, PulpPythonRepositoryContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/python/remote.py b/pulpcore/cli/python/remote.py index 10ac3e9fa..b2f4ec47c 100644 --- a/pulpcore/cli/python/remote.py +++ b/pulpcore/cli/python/remote.py @@ -2,8 +2,10 @@ from typing import Any, List, Optional import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation +from pulp_glue.python.context import PulpPythonRemoteContext -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import ( PulpCLIContext, common_remote_create_options, @@ -23,8 +25,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.python.context import PulpPythonRemoteContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/python/repository.py b/pulpcore/cli/python/repository.py index fead1216b..dd37312a2 100644 --- a/pulpcore/cli/python/repository.py +++ b/pulpcore/cli/python/repository.py @@ -1,13 +1,19 @@ from typing import Any, Dict, Optional import click - -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityFieldDefinition, PluginRequirement, PulpRemoteContext, PulpRepositoryContext, ) +from pulp_glue.common.i18n import get_translation +from pulp_glue.python.context import ( + PulpPythonContentContext, + PulpPythonRemoteContext, + PulpPythonRepositoryContext, +) + from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -32,13 +38,7 @@ update_command, version_command, ) -from pulpcore.cli.common.i18n import get_translation from pulpcore.cli.core.generic import task_command -from pulpcore.cli.python.context import ( - PulpPythonContentContext, - PulpPythonRemoteContext, - PulpPythonRepositoryContext, -) translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/rpm/__init__.py b/pulpcore/cli/rpm/__init__.py index 7e24609a6..2b20b9e0a 100644 --- a/pulpcore/cli/rpm/__init__.py +++ b/pulpcore/cli/rpm/__init__.py @@ -1,10 +1,10 @@ from typing import Any import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import PulpCLIContext, pass_pulp_context, pulp_group -from pulpcore.cli.common.i18n import get_translation from pulpcore.cli.rpm.acs import acs from pulpcore.cli.rpm.comps import comps_upload from pulpcore.cli.rpm.content import content diff --git a/pulpcore/cli/rpm/acs.py b/pulpcore/cli/rpm/acs.py index 07e503c6b..af81a94f2 100644 --- a/pulpcore/cli/rpm/acs.py +++ b/pulpcore/cli/rpm/acs.py @@ -1,8 +1,10 @@ from typing import Iterable import click +from pulp_glue.common.context import PulpRemoteContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.rpm.context import PulpRpmACSContext, PulpRpmRemoteContext, PulpUlnRemoteContext -from pulpcore.cli.common.context import PulpRemoteContext from pulpcore.cli.common.generic import ( PulpCLIContext, acs_lookup_option, @@ -18,8 +20,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.rpm.context import PulpRpmACSContext, PulpRpmRemoteContext, PulpUlnRemoteContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/rpm/comps.py b/pulpcore/cli/rpm/comps.py index dd4939f2b..15de17536 100644 --- a/pulpcore/cli/rpm/comps.py +++ b/pulpcore/cli/rpm/comps.py @@ -2,15 +2,15 @@ from typing import IO, Optional import click +from pulp_glue.common.context import EntityFieldDefinition, PulpEntityContext +from pulp_glue.rpm.context import PulpRpmCompsXmlContext, PulpRpmRepositoryContext -from pulpcore.cli.common.context import EntityFieldDefinition, PulpEntityContext from pulpcore.cli.common.generic import ( PulpCLIContext, pass_pulp_context, pulp_command, resource_option, ) -from pulpcore.cli.rpm.context import PulpRpmCompsXmlContext, PulpRpmRepositoryContext _ = gettext.gettext diff --git a/pulpcore/cli/rpm/content.py b/pulpcore/cli/rpm/content.py index 8ba8a6446..5b8a5a6d2 100644 --- a/pulpcore/cli/rpm/content.py +++ b/pulpcore/cli/rpm/content.py @@ -2,8 +2,23 @@ from typing import IO, Any, Dict, Optional, Union import click +from pulp_glue.common.context import PluginRequirement, PulpEntityContext +from pulp_glue.common.i18n import get_translation +from pulp_glue.core.context import PulpArtifactContext, PulpUploadContext +from pulp_glue.rpm.context import ( + PulpRpmAdvisoryContext, + PulpRpmDistributionTreeContext, + PulpRpmModulemdContext, + PulpRpmModulemdDefaultsContext, + PulpRpmPackageCategoryContext, + PulpRpmPackageContext, + PulpRpmPackageEnvironmentContext, + PulpRpmPackageGroupContext, + PulpRpmPackageLangpacksContext, + PulpRpmRepoMetadataFileContext, + PulpRpmRepositoryContext, +) -from pulpcore.cli.common.context import PluginRequirement, PulpEntityContext from pulpcore.cli.common.generic import ( PulpCLIContext, chunk_size_option, @@ -20,21 +35,6 @@ show_command, type_option, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpArtifactContext, PulpUploadContext -from pulpcore.cli.rpm.context import ( - PulpRpmAdvisoryContext, - PulpRpmDistributionTreeContext, - PulpRpmModulemdContext, - PulpRpmModulemdDefaultsContext, - PulpRpmPackageCategoryContext, - PulpRpmPackageContext, - PulpRpmPackageEnvironmentContext, - PulpRpmPackageGroupContext, - PulpRpmPackageLangpacksContext, - PulpRpmRepoMetadataFileContext, - PulpRpmRepositoryContext, -) translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/rpm/distribution.py b/pulpcore/cli/rpm/distribution.py index 71f0f0e30..1b9a93521 100644 --- a/pulpcore/cli/rpm/distribution.py +++ b/pulpcore/cli/rpm/distribution.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.rpm.context import PulpRpmDistributionContext, PulpRpmRepositoryContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -18,8 +20,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.rpm.context import PulpRpmDistributionContext, PulpRpmRepositoryContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/rpm/publication.py b/pulpcore/cli/rpm/publication.py index fe8123406..a78f79533 100644 --- a/pulpcore/cli/rpm/publication.py +++ b/pulpcore/cli/rpm/publication.py @@ -1,6 +1,8 @@ import click +from pulp_glue.common.context import PluginRequirement +from pulp_glue.common.i18n import get_translation +from pulp_glue.rpm.context import PulpRpmPublicationContext, PulpRpmRepositoryContext -from pulpcore.cli.common.context import PluginRequirement from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -13,8 +15,6 @@ resource_option, show_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.rpm.context import PulpRpmPublicationContext, PulpRpmRepositoryContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/rpm/remote.py b/pulpcore/cli/rpm/remote.py index 0fe4452b8..bf1a9db44 100644 --- a/pulpcore/cli/rpm/remote.py +++ b/pulpcore/cli/rpm/remote.py @@ -1,4 +1,6 @@ import click +from pulp_glue.common.i18n import get_translation +from pulp_glue.rpm.context import PulpRpmRemoteContext, PulpUlnRemoteContext from pulpcore.cli.common.generic import ( PulpCLIContext, @@ -17,8 +19,6 @@ show_command, update_command, ) -from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.rpm.context import PulpRpmRemoteContext, PulpUlnRemoteContext translation = get_translation(__name__) _ = translation.gettext diff --git a/pulpcore/cli/rpm/repository.py b/pulpcore/cli/rpm/repository.py index d9bc95512..60c19f04f 100644 --- a/pulpcore/cli/rpm/repository.py +++ b/pulpcore/cli/rpm/repository.py @@ -2,13 +2,20 @@ import click import schema as s - -from pulpcore.cli.common.context import ( +from pulp_glue.common.context import ( EntityFieldDefinition, PluginRequirement, PulpRemoteContext, PulpRepositoryContext, ) +from pulp_glue.common.i18n import get_translation +from pulp_glue.rpm.context import ( + PulpRpmPackageContext, + PulpRpmRemoteContext, + PulpRpmRepositoryContext, + PulpUlnRemoteContext, +) + from pulpcore.cli.common.generic import ( PulpCLIContext, create_command, @@ -33,15 +40,8 @@ update_command, version_command, ) -from pulpcore.cli.common.i18n import get_translation from pulpcore.cli.core.generic import task_command from pulpcore.cli.rpm.common import CHECKSUM_CHOICES -from pulpcore.cli.rpm.context import ( - PulpRpmPackageContext, - PulpRpmRemoteContext, - PulpRpmRepositoryContext, - PulpUlnRemoteContext, -) translation = get_translation(__name__) _ = translation.gettext diff --git a/setup.py b/setup.py index 7c69cad7f..85d2d030d 100644 --- a/setup.py +++ b/setup.py @@ -39,12 +39,11 @@ package_data={"": ["py.typed", "locale/*/LC_MESSAGES/*.mo"]}, python_requires=">=3.6", install_requires=[ + "pulp-glue==0.17.0.dev", "click>=8.0.0,<9.0.0", - "packaging", "PyYAML~=5.3", "schema==0.7.5", "setuptools", - "requests~=2.24", "toml==0.10.2", ], extras_require={ diff --git a/test_requirements.txt b/test_requirements.txt index 0c719db7f..f17772b4f 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,4 +1,5 @@ -e . +-e ./pulp-glue # Test requirements pygments diff --git a/tests/test_help_pages.py b/tests/test_help_pages.py index 2677085fd..eec08c35f 100644 --- a/tests/test_help_pages.py +++ b/tests/test_help_pages.py @@ -34,7 +34,7 @@ def no_api(monkeypatch): def getter(self): pytest.fail("Invalid access to 'PulpContext.api'.", pytrace=False) - monkeypatch.setattr("pulpcore.cli.common.context.PulpContext.api", getter) + monkeypatch.setattr("pulp_glue.common.context.PulpContext.api", getter) @pytest.mark.help_page