Skip to content

Commit

Permalink
Merge pull request #1012 from projectsyn/feat/commodore-inventory-rec…
Browse files Browse the repository at this point in the history
…lass-rs

Refactor `commodore inventory` to use reclass-rs
  • Loading branch information
simu authored Aug 15, 2024
2 parents 3bcdd08 + 58d4efc commit b016485
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 69 deletions.
73 changes: 10 additions & 63 deletions commodore/inventory/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
from pathlib import Path
from typing import Any, Optional

from kapitan.reclass import reclass
from kapitan.reclass.reclass import settings as reclass_settings
from kapitan.reclass.reclass import core as reclass_core
from kapitan.reclass.reclass import errors as reclass_errors
from reclass_rs import Reclass

from commodore.cluster import Cluster, render_params
from commodore.component import component_parameters_key
Expand All @@ -20,28 +17,6 @@
FAKE_CLUSTER_ID = "c-bar"


class ClassNotFound(reclass_errors.ClassNotFound):
"""
Create a wrapper around kapitan.reclass's ClassNotFound exception.
This allows us to have clean exception handling outside of this file, because we
unfortunately can't catch ClassNotFound directly in our code if we import
and use Kapitan reclass's ClassNotFound with any import which doesn't map the class
to `reclass.errors.ClassNotFound`. This is the case because Python's implementation
doesn't try to figure out if two imports are the same if they have different import
paths, cf. https://docs.python.org/3/reference/import.html.
The only import that should work (in my understanding) would be
`from kapitan.reclass import reclass` but with that import we get the error
"AttributeError: module 'kapitan.reclass.reclass' has no attribute 'errors'"
"""

@classmethod
def from_reclass(cls, e: reclass_errors.ClassNotFound):
"""Wrap a reclass.errors.ClassNotFound instance in our wrapper."""
return ClassNotFound(e.storage, e.name, e.path)


class InventoryFacts:
def __init__(
self,
Expand Down Expand Up @@ -176,53 +151,25 @@ def global_dir(self) -> Path:
def tenant_dir(self) -> Optional[Path]:
return self._tenant_dir

def _reclass_config(
self, allow_missing_classes: bool, ignore_class_notfound_warning: bool = True
) -> dict:
return {
"storage_type": "yaml_fs",
"inventory_base_uri": str(self.directory.absolute()),
"nodes_uri": str(self.targets_dir.absolute()),
"classes_uri": str(self.classes_dir.absolute()),
"compose_node_name": False,
"allow_none_override": True,
"ignore_class_notfound": allow_missing_classes,
"ignore_class_notfound_warning": ignore_class_notfound_warning,
}

def _render_inventory(
self,
target: Optional[str] = None,
allow_missing_classes: bool = True,
ignore_class_notfound_warning: bool = True,
) -> dict[str, Any]:
rc = self._reclass_config(
allow_missing_classes,
ignore_class_notfound_warning=ignore_class_notfound_warning,
)
storage = reclass.get_storage(
rc["storage_type"],
rc["nodes_uri"],
rc["classes_uri"],
rc["compose_node_name"],
)
class_mappings = rc.get("class_mappings")
_reclass = reclass_core.Core(
storage, class_mappings, reclass_settings.Settings(rc)
r = Reclass(
nodes_path=str(self.targets_dir.absolute()),
classes_path=str(self.classes_dir.absolute()),
ignore_class_notfound=allow_missing_classes,
)

try:
if target:
return _reclass.nodeinfo(target)

return _reclass.inventory()
except Exception as e:
if type(e).__name__ == "ClassNotFound":
# Wrap Kapitan reclass's ClassNotFound with ours so we can cleanly
# catch it. See docsstring for our ClassNotFound for a more detailed
# explanation why this is necessary.
raise ClassNotFound.from_reclass(e)
raise
return r.nodeinfo(target).as_dict()

return r.inventory().as_dict()
except ValueError as e:
raise e

def reclass(self, invfacts: InventoryFacts) -> InventoryParameters:
cluster_response: dict[str, Any] = {
Expand Down
5 changes: 2 additions & 3 deletions commodore/inventory/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from commodore.config import Config

from .parameters import (
ClassNotFound,
InventoryFactory,
InventoryFacts,
InventoryParameters,
Expand Down Expand Up @@ -67,11 +66,11 @@ def _get_inventory(cfg: Config, invfacts: InventoryFacts) -> InventoryParameters

try:
inv = invfactory.reclass(invfacts)
except ClassNotFound as e:
except ValueError as e:
_cleanup_work_dir(cfg, work_dir)
raise ValueError(
"Unable to render inventory with `--no-allow-missing-classes`. "
+ f"Class '{e.name}' not found. "
+ f"{e}. "
+ "Verify the provided values or allow missing classes."
) from e

Expand Down
6 changes: 3 additions & 3 deletions tests/test_inventory_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,19 @@ def test_extract_components(
lambda t, g: create_inventory_facts(
t, g, None, "x-invalid-dist", None, None, allow_missing_classes=False
),
"Class 'global.distribution.x-invalid-dist' not found.",
"Error while rendering global: Class global.distribution.x-invalid-dist not found.",
),
(
lambda t, g: create_inventory_facts(
t, g, None, "a", "x-invalid-cloud", None, allow_missing_classes=False
),
"Class 'global.cloud.x-invalid-cloud' not found.",
"Error while rendering global: Class global.cloud.x-invalid-cloud not found.",
),
(
lambda t, g: create_inventory_facts(
t, g, None, "a", "y", "x-invalid-region", allow_missing_classes=False
),
"Class 'global.cloud.y.x-invalid-region' not found.",
"Error while rendering global: Class global.cloud.y.x-invalid-region not found.",
),
],
)
Expand Down

0 comments on commit b016485

Please sign in to comment.