Skip to content

Commit

Permalink
Use version specifiers in PluginRequirement
Browse files Browse the repository at this point in the history
fixes #681
  • Loading branch information
mdellweg committed Apr 19, 2023
1 parent 2cc6dc4 commit bb3e59b
Show file tree
Hide file tree
Showing 34 changed files with 216 additions and 202 deletions.
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

0 comments on commit bb3e59b

Please sign in to comment.