Skip to content

Commit ccd3771

Browse files
committed
Add prn support to show
fixes: #1236
1 parent 2a3c52c commit ccd3771

10 files changed

Lines changed: 214 additions & 40 deletions

File tree

CHANGES/1236.feature

Whitespace-only changes.

docs/dev/guides/contributing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,6 @@ To run tests:
6161

6262
```bash
6363
make test # all tests
64-
pytest -m pulp_file # tests for pulp_file
65-
pytest -m pulp_file -k test_remote # run tests/scripts/pulp_file/test_remote.sh
64+
pytest tests -m pulp_file # tests for pulp_file
65+
pytest tests -m pulp_file -k test_remote # run tests/scripts/pulp_file/test_remote.sh
6666
```

pulp-glue/src/pulp_glue/common/context.py

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
PulpHTTPError,
2020
PulpNoWait,
2121
UnsafeCallError,
22+
ValidationError,
2223
)
2324
from pulp_glue.common.i18n import get_translation
2425
from pulp_glue.common.openapi import METHODS, OpenAPI
@@ -61,6 +62,11 @@ def _inner(f: T) -> T:
6162
]
6263

6364
href_regex = re.compile(r"\/([a-z0-9-_]+\/)+", flags=re.IGNORECASE)
65+
# Be careful model in this regex differs from resource_type in others.
66+
# model: "file.repository" ; resource_type: "file"
67+
prn_regex = re.compile(
68+
r"^prn:(?P<plugin>[a-z][a-z0-9-_]*)\.(?P<model>[a-z][a-z0-9_]*):(?P<pulp_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$", # noqa: E501
69+
)
6470

6571

6672
class PreprocessedEntityDefinition(dict[str, t.Any]):
@@ -548,7 +554,8 @@ def call(
548554
task_href = result["task"]
549555
result = self.api.call("tasks_read", parameters={"task_href": task_href})
550556
self.echo(
551-
_("Started background task {task_href}").format(task_href=task_href), err=True
557+
_("Started background task {task_href}").format(task_href=task_href),
558+
err=True,
552559
)
553560
if not non_blocking:
554561
result = self.wait_for_task(result)
@@ -672,7 +679,8 @@ def wait_for_task_group(self, task_group: EntityDefinition) -> t.Any:
672679
time.sleep(1)
673680
self.echo(".", nl=False, err=True)
674681
task_group = self.api.call(
675-
"task_groups_read", parameters={"task_group_href": task_group["pulp_href"]}
682+
"task_groups_read",
683+
parameters={"task_group_href": task_group["pulp_href"]},
676684
)
677685
except KeyboardInterrupt:
678686
raise PulpNoWait(
@@ -713,6 +721,18 @@ def needs_plugin(
713721
# Schedule for later checking
714722
self._needed_plugins.append(plugin_requirement)
715723

724+
def resolve_prn(self, prn: str) -> "PulpEntityContext":
725+
match = prn_regex.fullmatch(prn)
726+
if match is None:
727+
raise ValidationError("Not a PRN.")
728+
plugin = match.group("plugin")
729+
model = match.group("model")
730+
pulp_id = match.group("pulp_id")
731+
if (ctx_class := PulpEntityContext.PRN_TYPE_REGISTRY.get(f"{plugin}:{model}")) is not None:
732+
result = ctx_class.from_pulp_id(self, pulp_id)
733+
return result
734+
raise ValidationError("Resource type unknown.")
735+
716736

717737
class PulpViewSetContext:
718738
"""
@@ -815,6 +835,17 @@ class PulpEntityContext(PulpViewSetContext):
815835
"""
816836
HREF_PATTERN: str
817837
"""Regular expression with capture groups for 'plugin' and 'resource_type' to match URIs."""
838+
HREF_TEMPLATE: str
839+
840+
PLUGIN: t.ClassVar[str]
841+
RESOURCE_TYPE: t.ClassVar[str]
842+
MODEL: t.ClassVar[str]
843+
PRN_TYPE_REGISTRY: t.Final[dict[str, t.Type["PulpEntityContext"]]] = {}
844+
845+
def __init_subclass__(cls, **kwargs: t.Any) -> None:
846+
super().__init_subclass__(**kwargs)
847+
if hasattr(cls, "PLUGIN") and hasattr(cls, "MODEL"):
848+
cls.PRN_TYPE_REGISTRY[f"{cls.PLUGIN}:{cls.MODEL}"] = cls
818849

819850
# Hidden values for the lazy entity lookup
820851
_entity: EntityDefinition | None
@@ -887,6 +918,15 @@ def pulp_href(self, value: str) -> None:
887918
self._entity_lookup = {"pulp_href": value}
888919
self._entity = None
889920

921+
@classmethod
922+
def from_pulp_id(cls, pulp_ctx: PulpContext, pulp_id: str) -> "t.Self":
923+
if hasattr(cls, "HREF_TEMPLATE"):
924+
return cls(
925+
pulp_ctx,
926+
pulp_href=pulp_ctx.api_path + cls.HREF_TEMPLATE.replace("{pulp_id}", pulp_id),
927+
)
928+
raise NotImplementedError("Subclasses should implement this or provide a HREF_TEMPLATE.")
929+
890930
@property
891931
def tangible(self) -> bool:
892932
"""Indicate whether an entity is available or specified by search parameters."""
@@ -1003,7 +1043,8 @@ def _list(self, limit: int, offset: int, parameters: dict[str, t.Any]) -> list[t
10031043
pass
10041044
else:
10051045
self.pulp_ctx.echo(
1006-
_("Not all {count} entries were shown.").format(count=stats["count"]), err=True
1046+
_("Not all {count} entries were shown.").format(count=stats["count"]),
1047+
err=True,
10071048
)
10081049
return entities
10091050

@@ -1101,7 +1142,8 @@ def create(
11011142
h
11021143
for h in result["created_resources"]
11031144
if re.match(
1104-
re.escape(self.pulp_ctx.api_path) + self.HREF_PATTERN, h
1145+
re.escape(self.pulp_ctx.api_path) + self.HREF_PATTERN,
1146+
h,
11051147
)
11061148
)
11071149
)
@@ -1229,7 +1271,9 @@ def unset_label(self, key: str, non_blocking: bool = False) -> t.Any:
12291271
if self.pulp_ctx.has_plugin(PluginRequirement("core", specifier=">=3.34.0")):
12301272
try:
12311273
return self.call(
1232-
"unset_label", parameters={self.HREF: self.pulp_href}, body={"key": key}
1274+
"unset_label",
1275+
parameters={self.HREF: self.pulp_href},
1276+
body={"key": key},
12331277
)
12341278
except PulpHTTPError as e:
12351279
if e.status_code != 403:
@@ -1382,6 +1426,7 @@ class PulpRemoteContext(PulpEntityContext):
13821426
ENTITIES = _("remotes")
13831427
ID_PREFIX = "remotes"
13841428
HREF_PATTERN = r"remotes/(?P<plugin>[\w\-_]+)/(?P<resource_type>[\w\-_]+)/"
1429+
HREF_TEMPLATE = "remotes/{plugin}/{resource_type}/{pulp_id}/"
13851430
NULLABLES = {
13861431
"ca_cert",
13871432
"client_cert",
@@ -1402,8 +1447,13 @@ class PulpRemoteContext(PulpEntityContext):
14021447
TYPE_REGISTRY: t.Final[dict[str, t.Type["PulpRemoteContext"]]] = {}
14031448

14041449
def __init_subclass__(cls, **kwargs: t.Any) -> None:
1450+
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1451+
cls.MODEL = f"{cls.RESOURCE_TYPE}remote"
14051452
super().__init_subclass__(**kwargs)
14061453
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1454+
cls.HREF_TEMPLATE = cls.HREF_TEMPLATE.replace("{plugin}", cls.PLUGIN).replace(
1455+
"{resource_type}", cls.RESOURCE_TYPE
1456+
)
14071457
cls.TYPE_REGISTRY[f"{cls.PLUGIN}:{cls.RESOURCE_TYPE}"] = cls
14081458

14091459

@@ -1414,11 +1464,17 @@ class PulpPublicationContext(PulpEntityContext):
14141464
ENTITIES = _("publications")
14151465
ID_PREFIX = "publications"
14161466
HREF_PATTERN = r"publications/(?P<plugin>[\w\-_]+)/(?P<resource_type>[\w\-_]+)/"
1467+
HREF_TEMPLATE = "publications/{plugin}/{resource_type}/{pulp_id}/"
14171468
TYPE_REGISTRY: t.Final[dict[str, t.Type["PulpPublicationContext"]]] = {}
14181469

14191470
def __init_subclass__(cls, **kwargs: t.Any) -> None:
1471+
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1472+
cls.MODEL = f"{cls.RESOURCE_TYPE}publication"
14201473
super().__init_subclass__(**kwargs)
14211474
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1475+
cls.HREF_TEMPLATE = cls.HREF_TEMPLATE.replace("{plugin}", cls.PLUGIN).replace(
1476+
"{resource_type}", cls.RESOURCE_TYPE
1477+
)
14221478
cls.TYPE_REGISTRY[f"{cls.PLUGIN}:{cls.RESOURCE_TYPE}"] = cls
14231479

14241480
def list(self, limit: int, offset: int, parameters: dict[str, t.Any]) -> list[t.Any]:
@@ -1436,12 +1492,24 @@ class PulpDistributionContext(PulpEntityContext):
14361492
ENTITIES = _("distributions")
14371493
ID_PREFIX = "distributions"
14381494
HREF_PATTERN = r"distributions/(?P<plugin>[\w\-_]+)/(?P<resource_type>[\w\-_]+)/"
1439-
NULLABLES = {"content_guard", "publication", "remote", "repository", "repository_version"}
1495+
HREF_TEMPLATE = "distributions/{plugin}/{resource_type}/{pulp_id}/"
1496+
NULLABLES = {
1497+
"content_guard",
1498+
"publication",
1499+
"remote",
1500+
"repository",
1501+
"repository_version",
1502+
}
14401503
TYPE_REGISTRY: t.Final[dict[str, t.Type["PulpDistributionContext"]]] = {}
14411504

14421505
def __init_subclass__(cls, **kwargs: t.Any) -> None:
1506+
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1507+
cls.MODEL = f"{cls.RESOURCE_TYPE}distribution"
14431508
super().__init_subclass__(**kwargs)
14441509
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1510+
cls.HREF_TEMPLATE = cls.HREF_TEMPLATE.replace("{plugin}", cls.PLUGIN).replace(
1511+
"{resource_type}", cls.RESOURCE_TYPE
1512+
)
14451513
cls.TYPE_REGISTRY[f"{cls.PLUGIN}:{cls.RESOURCE_TYPE}"] = cls
14461514

14471515

@@ -1511,14 +1579,20 @@ class PulpRepositoryContext(PulpEntityContext):
15111579
ENTITY = _("repository")
15121580
ENTITIES = _("repositories")
15131581
HREF_PATTERN = r"repositories/(?P<plugin>[\w\-_]+)/(?P<resource_type>[\w\-_]+)/"
1582+
HREF_TEMPLATE = "repositories/{plugin}/{resource_type}/{pulp_id}/"
15141583
ID_PREFIX = "repositories"
15151584
VERSION_CONTEXT: t.ClassVar[t.Type[PulpRepositoryVersionContext]] = PulpRepositoryVersionContext
15161585
NULLABLES = {"description", "remote", "retain_repo_versions"}
15171586
TYPE_REGISTRY: t.Final[dict[str, t.Type["PulpRepositoryContext"]]] = {}
15181587

15191588
def __init_subclass__(cls, **kwargs: t.Any) -> None:
1589+
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1590+
cls.MODEL = f"{cls.RESOURCE_TYPE}repository"
15201591
super().__init_subclass__(**kwargs)
15211592
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1593+
cls.HREF_TEMPLATE = cls.HREF_TEMPLATE.replace("{plugin}", cls.PLUGIN).replace(
1594+
"{resource_type}", cls.RESOURCE_TYPE
1595+
)
15221596
cls.TYPE_REGISTRY[f"{cls.PLUGIN}:{cls.RESOURCE_TYPE}"] = cls
15231597

15241598
def get_version_context(
@@ -1633,11 +1707,17 @@ class PulpContentContext(PulpEntityContext):
16331707
ENTITIES = _("content")
16341708
ID_PREFIX = "content"
16351709
HREF_PATTERN = r"content/(?P<plugin>[\w\-_]+)/(?P<resource_type>[\w\-_]+)/"
1710+
HREF_TEMPLATE = "content/{plugin}/{resource_type}/{pulp_id}/"
16361711
TYPE_REGISTRY: t.Final[dict[str, t.Type["PulpContentContext"]]] = {}
16371712

16381713
def __init_subclass__(cls, **kwargs: t.Any) -> None:
1714+
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1715+
cls.MODEL = f"{cls.RESOURCE_TYPE}content"
16391716
super().__init_subclass__(**kwargs)
16401717
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1718+
cls.HREF_TEMPLATE = cls.HREF_TEMPLATE.replace("{plugin}", cls.PLUGIN).replace(
1719+
"{resource_type}", cls.RESOURCE_TYPE
1720+
)
16411721
cls.TYPE_REGISTRY[f"{cls.PLUGIN}:{cls.RESOURCE_TYPE}"] = cls
16421722

16431723
def __init__(
@@ -1743,12 +1823,18 @@ class PulpACSContext(PulpEntityContext):
17431823
ENTITY = _("ACS")
17441824
ENTITIES = _("ACSes")
17451825
HREF_PATTERN = r"acs/(?P<plugin>[\w\-_]+)/(?P<resource_type>[\w\-_]+)/"
1826+
HREF_TEMPLATE = "acs/{plugin}/{resource_type}/{pulp_id}/"
17461827
ID_PREFIX = "acs"
17471828
TYPE_REGISTRY: t.Final[dict[str, t.Type["PulpACSContext"]]] = {}
17481829

17491830
def __init_subclass__(cls, **kwargs: t.Any) -> None:
1831+
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1832+
cls.MODEL = f"{cls.RESOURCE_TYPE}acs"
17501833
super().__init_subclass__(**kwargs)
17511834
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1835+
cls.HREF_TEMPLATE = cls.HREF_TEMPLATE.replace("{plugin}", cls.PLUGIN).replace(
1836+
"{resource_type}", cls.RESOURCE_TYPE
1837+
)
17521838
cls.TYPE_REGISTRY[f"{cls.PLUGIN}:{cls.RESOURCE_TYPE}"] = cls
17531839

17541840
def refresh(self, href: str | None = None) -> t.Any:
@@ -1762,12 +1848,18 @@ class PulpContentGuardContext(PulpEntityContext):
17621848
ENTITIES = "content guards"
17631849
ID_PREFIX = "contentguards"
17641850
HREF_PATTERN = r"contentguards/(?P<plugin>[\w\-_]+)/(?P<resource_type>[\w\-_]+)/"
1851+
HREF_TEMPLATE = "contentguards/{plugin}/{resource_type}/{pulp_id}/"
17651852
NULLABLES = {"description"}
17661853
TYPE_REGISTRY: t.Final[dict[str, t.Type["PulpContentGuardContext"]]] = {}
17671854

17681855
def __init_subclass__(cls, **kwargs: t.Any) -> None:
1856+
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1857+
cls.MODEL = f"{cls.RESOURCE_TYPE}contentguard"
17691858
super().__init_subclass__(**kwargs)
17701859
if hasattr(cls, "PLUGIN") and hasattr(cls, "RESOURCE_TYPE"):
1860+
cls.HREF_TEMPLATE = cls.HREF_TEMPLATE.replace("{plugin}", cls.PLUGIN).replace(
1861+
"{resource_type}", cls.RESOURCE_TYPE
1862+
)
17711863
cls.TYPE_REGISTRY[f"{cls.PLUGIN}:{cls.RESOURCE_TYPE}"] = cls
17721864

17731865

pulp-glue/src/pulp_glue/core/context.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class PulpArtifactContext(PulpEntityContext):
4242
ENTITY = _("artifact")
4343
ENTITIES = _("artifacts")
4444
HREF = "artifact_href"
45+
PLUGIN = "core"
46+
MODEL = "artifact"
47+
HREF_TEMPLATE = "artifacts/{pulp_id}/"
4548
ID_PREFIX = "artifacts"
4649

4750
def upload(
@@ -398,6 +401,9 @@ class PulpTaskContext(PulpEntityContext):
398401
HREF = "task_href"
399402
ID_PREFIX = "tasks"
400403
CAPABILITIES = {"roles": [PluginRequirement("core", specifier=">=3.17.0")]}
404+
PLUGIN = "core"
405+
MODEL = "task"
406+
HREF_TEMPLATE = "tasks/{pulp_id}/"
401407

402408
resource_context: PulpEntityContext | None = None
403409

@@ -513,6 +519,9 @@ class PulpUploadContext(PulpEntityContext):
513519
ENTITIES = _("uploads")
514520
HREF = "upload_href"
515521
ID_PREFIX = "uploads"
522+
PLUGIN = "core"
523+
MODEL = "upload"
524+
HREF_TEMPLATE = "uploads/{pulp_id}/"
516525

517526
def upload_chunk(
518527
self,

pulp-glue/tests/conftest.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
from pulp_glue.common.context import PulpContext
88
from pulp_glue.common.openapi import OpenAPI
99

10-
FAKE_OPENAPI_SPEC = json.dumps(
10+
MOCK_OPENAPI_SPEC = json.dumps(
1111
{
1212
"openapi": "3.0.3",
13-
"info": {"title": "test", "version": "0.0.0", "x-pulp-app-versions": {"core": "3.75.0"}},
13+
"info": {
14+
"title": "test",
15+
"version": "0.0.0",
16+
"x-pulp-app-versions": {"core": "3.75.0", "file": "3.75.0"},
17+
},
1418
"paths": {},
1519
}
1620
)
@@ -35,7 +39,7 @@ def mock_pulp_ctx(
3539
monkeypatch: pytest.MonkeyPatch,
3640
) -> PulpContext:
3741
monkeypatch.setattr(
38-
OpenAPI, "load_api", lambda self, refresh_cache: self._parse_api(FAKE_OPENAPI_SPEC)
42+
OpenAPI, "load_api", lambda self, refresh_cache: self._parse_api(MOCK_OPENAPI_SPEC)
3943
)
4044
monkeypatch.setattr(
4145
OpenAPI,

0 commit comments

Comments
 (0)