Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use version specifiers in PluginRequirement #679

Merged
merged 1 commit into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/681.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `PEP-440` version specifieres to `PluginRequirement` and `pulp debug has-plugin`.
1 change: 1 addition & 0 deletions CHANGES/681.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deprecate the use of `min` and `max` in `PluginRequirement`.
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ It will raise an error, once the first access to the server is attempted.
```python
class MyEntityContext(PulpEntityContext):
def show(self, href):
if self.pulp_ctx.has_plugin(PluginRequirement("my_content", min="1.2.3", inverted=True)):
if self.pulp_ctx.has_plugin(PluginRequirement("my_content", specifier=">=1.2.3", inverted=True)):
# Versioned workaroud
# see bug-tracker/12345678
return lookup_my_content_legacy(href)
Expand All @@ -78,7 +78,7 @@ class MyEntityContext(PulpEntityContext):
@pass_pulp_context
@click.pass_context
def my_command(ctx, pulp_ctx):
pulp_ctx.needs_plugin(PluginRequirement("my_content", min="1.0.0"))
pulp_ctx.needs_plugin(PluginRequirement("my_content", specifier=">=1.0.0"))
ctx.obj = MyEntityContext(pulp_ctx)
```

Expand Down
20 changes: 11 additions & 9 deletions pulp-glue/pulp_glue/ansible/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class PulpAnsibleCollectionVersionContext(PulpContentContext):
HREF = "ansible_collection_version_href"
ID_PREFIX = "content_ansible_collection_versions"
UPLOAD_ID: ClassVar[str] = "upload_collection"
NEEDS_PLUGINS = [PluginRequirement("ansible", min="0.7.0")]
NEEDS_PLUGINS = [PluginRequirement("ansible", specifier=">=0.7.0")]

def upload(self, file: IO[bytes], **kwargs: Any) -> Any: # type:ignore
return self.call("upload", body={"file": file})
Expand All @@ -33,15 +33,15 @@ class PulpAnsibleRoleContext(PulpContentContext):
ENTITIES = _("ansible roles")
HREF = "ansible_role_href"
ID_PREFIX = "content_ansible_roles"
NEEDS_PLUGINS = [PluginRequirement("ansible", min="0.7.0")]
NEEDS_PLUGINS = [PluginRequirement("ansible", specifier=">=0.7.0")]


class PulpAnsibleCollectionVersionSignatureContext(PulpContentContext):
ENTITY = _("ansible collection version signature")
ENTITIES = _("ansible collection version signatures")
HREF = _("ansible_collection_version_signature_href")
ID_PREFIX = "content_ansible_collection_signatures"
NEEDS_PLUGINS = [PluginRequirement("ansible", min="0.12.0")]
NEEDS_PLUGINS = [PluginRequirement("ansible", specifier=">=0.12.0")]

def create(
self,
Expand All @@ -50,7 +50,9 @@ def create(
non_blocking: bool = False,
) -> Any:
self.pulp_ctx.needs_plugin(
PluginRequirement("ansible", min="0.13.0", feature=_("collection version creation"))
PluginRequirement(
"ansible", specifier=">=0.13.0", feature=_("collection version creation")
)
)
return super().create(body=body, parameters=parameters, non_blocking=non_blocking)

Expand All @@ -60,7 +62,7 @@ class PulpAnsibleDistributionContext(PulpDistributionContext):
ENTITIES = _("ansible distributions")
HREF = "ansible_ansible_distribution_href"
ID_PREFIX = "distributions_ansible_ansible"
NEEDS_PLUGINS = [PluginRequirement("ansible", min="0.7.0")]
NEEDS_PLUGINS = [PluginRequirement("ansible", specifier=">=0.7.0")]

def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition:
body = super().preprocess_entity(body, partial=partial)
Expand All @@ -77,7 +79,7 @@ class PulpAnsibleRoleRemoteContext(PulpRemoteContext):
HREF = "ansible_role_remote_href"
ID_PREFIX = "remotes_ansible_role"
HREF_PATTERN = r"remotes/(?P<plugin>ansible)/(?P<resource_type>role)/"
NEEDS_PLUGINS = [PluginRequirement("ansible", min="0.7.0")]
NEEDS_PLUGINS = [PluginRequirement("ansible", specifier=">=0.7.0")]


class PulpAnsibleCollectionRemoteContext(PulpRemoteContext):
Expand All @@ -86,7 +88,7 @@ class PulpAnsibleCollectionRemoteContext(PulpRemoteContext):
HREF = "ansible_collection_remote_href"
ID_PREFIX = "remotes_ansible_collection"
HREF_PATTERN = r"remotes/(?P<plugin>ansible)/(?P<resource_type>collection)/"
NEEDS_PLUGINS = [PluginRequirement("ansible", min="0.7.0")]
NEEDS_PLUGINS = [PluginRequirement("ansible", specifier=">=0.7.0")]

def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition:
body = super().preprocess_entity(body, partial=partial)
Expand All @@ -98,7 +100,7 @@ def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> En
class PulpAnsibleRepositoryVersionContext(PulpRepositoryVersionContext):
HREF = "ansible_ansible_repository_version_href"
ID_PREFIX = "repositories_ansible_ansible_versions"
NEEDS_PLUGINS = [PluginRequirement("ansible", min="0.7.0")]
NEEDS_PLUGINS = [PluginRequirement("ansible", specifier=">=0.7.0")]


class PulpAnsibleRepositoryContext(PulpRepositoryContext):
Expand All @@ -112,7 +114,7 @@ class PulpAnsibleRepositoryContext(PulpRepositoryContext):
"pulpexport": [PluginRequirement("ansible")],
}
NULLABLES = PulpRepositoryContext.NULLABLES | {"gpgkey"}
NEEDS_PLUGINS = [PluginRequirement("ansible", min="0.7.0")]
NEEDS_PLUGINS = [PluginRequirement("ansible", specifier=">=0.7.0")]


registered_repository_contexts["ansible:ansible"] = PulpAnsibleRepositoryContext
4 changes: 2 additions & 2 deletions pulp-glue/pulp_glue/certguard/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ class PulpX509CertGuardContext(PulpContentGuardContext):
ENTITIES = _("x509 certguards")
HREF = "certguard_x509_cert_guard_href"
ID_PREFIX = "contentguards_certguard_x509"
NEEDS_PLUGINS = [PluginRequirement("certguard", min="1.4.0")]
NEEDS_PLUGINS = [PluginRequirement("certguard", specifier=">=1.4.0")]


class PulpRHSMCertGuardContext(PulpContentGuardContext):
ENTITY = _("RHSM certguard")
ENTITIES = _("RHSM certguards")
HREF = "certguard_r_h_s_m_cert_guard_href"
ID_PREFIX = "contentguards_certguard_rhsm"
NEEDS_PLUGINS = [PluginRequirement("certguard", min="1.4.0")]
NEEDS_PLUGINS = [PluginRequirement("certguard", specifier=">=1.4.0")]
110 changes: 54 additions & 56 deletions pulp-glue/pulp_glue/common/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,9 @@
import re
import sys
import time
from typing import (
IO,
Any,
ClassVar,
Dict,
List,
Mapping,
NamedTuple,
Optional,
Set,
Type,
Union,
cast,
)

from packaging.version import parse as parse_version
from typing import IO, Any, ClassVar, Dict, List, Mapping, Optional, Set, Type, Union, cast

from packaging.specifiers import SpecifierSet
from requests import HTTPError

from pulp_glue.common.i18n import get_translation
Expand Down Expand Up @@ -54,12 +41,39 @@ class PreprocessedEntityDefinition(Dict[str, Any]):
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 PluginRequirement:
def __init__(
self,
name: str,
min: Optional[str] = None,
max: Optional[str] = None,
feature: Optional[str] = None,
inverted: bool = False,
specifier: Optional[Union[str, SpecifierSet]] = None,
):
self.name = name
self.feature = feature
self.inverted = inverted
if min is None and max is None:
specifier = specifier or ""
else:
assert specifier is None
specifier = ""
separator = ""
if min:
specifier += f">={min}"
separator = ","
if max:
specifier += f"{separator}<{max}"
if isinstance(specifier, SpecifierSet):
self.specifier = specifier
else:
self.specifier = SpecifierSet(specifier, prereleases=True)

def __contains__(self, version: Optional[str]) -> bool:
if version is None:
return self.inverted
return (version in self.specifier) != self.inverted


class PulpException(Exception):
Expand Down Expand Up @@ -113,7 +127,9 @@ def __init__(
self._api: Optional[OpenAPI] = None
self._api_root: str = api_root
self._api_kwargs = api_kwargs
self._needed_plugins: List[PluginRequirement] = [PluginRequirement("core", min="3.11.0")]
self._needed_plugins: List[PluginRequirement] = [
PluginRequirement("core", specifier=">=3.11.0")
]
self.isatty: bool = sys.stdout.isatty()
self.pulp_domain: str = domain

Expand All @@ -125,7 +141,7 @@ 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")):
if self.has_plugin(PluginRequirement("core", specifier="<3.20.0")):
for method, path in self.api.operations.values():
operation = api_spec["paths"][path][method]
if method == "get" and "parameters" in operation:
Expand All @@ -139,7 +155,7 @@ def _patch_api_spec(self) -> None:
parameter["schema"] = {"type": "array", "items": {"type": "string"}}
parameter["explode"] = False
parameter["style"] = "form"
if self.has_plugin(PluginRequirement("core", max="3.22.0")):
if self.has_plugin(PluginRequirement("core", specifier="<3.22.0")):
for method, path in self.api.operations.values():
operation = api_spec["paths"][path][method]
if method == "get" and "parameters" in operation:
Expand All @@ -151,7 +167,7 @@ def _patch_api_spec(self) -> None:
and parameter["schema"]["type"] == "string"
):
parameter["schema"] = {"type": "array", "items": {"type": "string"}}
if self.has_plugin(PluginRequirement("core", max="99.99.0")):
if self.has_plugin(PluginRequirement("core", specifier="<99.99.0")):
# https://github.com/pulp/pulpcore/issues/3634
for operation_id, (method, path) in self.api.operations.items():
if (
Expand All @@ -171,13 +187,13 @@ def _patch_api_spec(self) -> None:
and parameter["schema"]["type"] == "string"
):
parameter["schema"] = {"type": "array", "items": {"type": "string"}}
if self.has_plugin(PluginRequirement("file", min="1.10.0", max="1.11.0")):
if self.has_plugin(PluginRequirement("file", specifier=">=1.10.0,<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")
PluginRequirement("python", specifier="<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"][
Expand Down Expand Up @@ -387,48 +403,30 @@ def has_plugin(
self,
plugin_requirement: 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_requirement.inverted
version: Optional[str] = self.component_versions.get(plugin_requirement.name)
if version is None:
return plugin_requirement.inverted
if plugin_requirement.min is not None:
if parse_version(version) < parse_version(plugin_requirement.min):
return plugin_requirement.inverted
if plugin_requirement.max is not None:
if parse_version(version) >= parse_version(plugin_requirement.max):
return plugin_requirement.inverted
return not plugin_requirement.inverted
return version in plugin_requirement

def needs_plugin(
self,
plugin_requirement: PluginRequirement,
) -> None:
if self._api is not None:
if not self.has_plugin(plugin_requirement):
specifier = plugin_requirement.name
separator = ""
if plugin_requirement.min is not None:
specifier += f">={plugin_requirement.min}"
separator = ","
if plugin_requirement.max is not None:
specifier += f"{separator}<{plugin_requirement.max}"
component = f"{plugin_requirement.name}{plugin_requirement.specifier}"
feature = plugin_requirement.feature or _("this command")
if plugin_requirement.inverted:
msg = _(
"The server provides the pulp component '{specifier}',"
"The server provides the pulp component '{component}',"
" which prevents the use of {feature}."
" See 'pulp status' for installed components."
)
else:
msg = _(
"The server does not provide the pulp component '{specifier}',"
"The server does not provide the pulp component '{component}',"
" which is needed to use {feature}."
" See 'pulp status' for installed components."
)
raise PulpException(msg.format(specifier=specifier, feature=feature))
raise PulpException(msg.format(component=component, feature=feature))
else:
# Schedule for later checking
self._needed_plugins.append(plugin_requirement)
Expand All @@ -454,7 +452,7 @@ class PulpEntityContext:
# e.g. `CAPABILITIES = {
# "feature1": [
# PluginRequirement("file"),
# PluginRequirement("core", min="3.7.0")
# PluginRequirement("core", specifier=">=3.7.0")
# ]
# }
CAPABILITIES: ClassVar[Dict[str, List[PluginRequirement]]] = {}
Expand Down Expand Up @@ -787,7 +785,7 @@ class PulpPublicationContext(PulpEntityContext):
def list(self, limit: int, offset: int, parameters: Dict[str, Any]) -> List[Any]:
if parameters.get("repository") is not None:
self.pulp_ctx.needs_plugin(
PluginRequirement("core", min="3.20.0", feature=_("repository filter"))
PluginRequirement("core", specifier=">=3.20.0", feature=_("repository filter"))
)
return super().list(limit, offset, parameters)

Expand Down Expand Up @@ -834,8 +832,8 @@ def get_version_context(self) -> PulpRepositoryVersionContext:
def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> EntityDefinition:
body = super().preprocess_entity(body, partial=partial)
if "retain_repo_versions" in body:
self.pulp_ctx.needs_plugin(PluginRequirement("core", min="3.13.0"))
if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.13.0", max="3.15.0")):
self.pulp_ctx.needs_plugin(PluginRequirement("core", specifier=">=3.13.0"))
if self.pulp_ctx.has_plugin(PluginRequirement("core", specifier=">=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:
Expand Down Expand Up @@ -880,7 +878,7 @@ def upload(
body: Dict[str, Any] = {**kwargs}
if chunk_size > size:
body["file"] = file
elif self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.20.0")):
elif self.pulp_ctx.has_plugin(PluginRequirement("core", specifier=">=3.20.0")):
from pulp_glue.core.context import PulpUploadContext

upload_href = PulpUploadContext(self.pulp_ctx).upload_file(file, chunk_size)
Expand Down
Loading