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

Fixes for Jsonnet global inventory and inventory dumping #1191

Merged
merged 2 commits into from
Jul 15, 2024
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
17 changes: 13 additions & 4 deletions kapitan/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def trigger_compile(args):
jinja2_filters=args.jinja2_filters,
verbose=args.verbose,
use_go_jsonnet=args.use_go_jsonnet,
compose_target_name=args.compose_target_name
compose_target_name=args.compose_target_name,
)


Expand All @@ -119,10 +119,13 @@ def build_parser():
)

inventory_backend_parser.add_argument(
"--compose-target-name", "--compose-target-name",
"--compose-target-name",
"--compose-target-name",
help="Create same subfolder structure from inventory/targets inside compiled folder",
action="store_true",
default=from_dot_kapitan("global", "compose-target-name", from_dot_kapitan("compile", "compose-node-name", False)),
default=from_dot_kapitan(
"global", "compose-target-name", from_dot_kapitan("compile", "compose-node-name", False)
),
)

eval_parser = subparser.add_parser("eval", aliases=["e"], help="evaluate jsonnet file")
Expand Down Expand Up @@ -395,6 +398,12 @@ def build_parser():
default=from_dot_kapitan("inventory", "multiline-string-style", "double-quotes"),
help="set multiline string style to STYLE, default is 'double-quotes'",
)
inventory_parser.add_argument(
"--yaml-dump-null-as-empty",
default=from_dot_kapitan("inventory", "yaml-dump-null-as-empty", False),
action="store_true",
help="dumps all none-type entries as empty, default is dumping as 'null'",
)

searchvar_parser = subparser.add_parser(
"searchvar", aliases=["sv"], help="show all inventory files where var is declared"
Expand Down Expand Up @@ -601,7 +610,7 @@ def build_parser():
"validate",
aliases=["v"],
help="validates the compile output against schemas as specified in inventory",
parents=[inventory_backend_parser]
parents=[inventory_backend_parser],
)
validate_parser.set_defaults(func=schema_validate_compiled, name="validate")

Expand Down
5 changes: 3 additions & 2 deletions kapitan/inventory/inv_reclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@


class ReclassInventory(Inventory):

def render_targets(self, targets: list[InventoryTarget] = None, ignore_class_notfound: bool = False) -> None:
def render_targets(
self, targets: list[InventoryTarget] | None = None, ignore_class_notfound: bool = False
) -> None:
"""
Runs a reclass inventory in inventory_path
(same output as running ./reclass.py -b inv_base_uri/ --inventory)
Expand Down
4 changes: 3 additions & 1 deletion kapitan/inventory/inv_reclass_rs.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ def _make_reclass_rs(self, ignore_class_notfound: bool):
)
return reclass_rs.Reclass.from_config(config)

def render_targets(self, targets: list = None, ignore_class_notfound: bool = False):
def render_targets(
self, targets: list[InventoryTarget] | None = None, ignore_class_notfound: bool = False
):
try:
r = self._make_reclass_rs(ignore_class_notfound)
start = datetime.now()
Expand Down
45 changes: 29 additions & 16 deletions kapitan/inventory/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from abc import ABC, abstractmethod
from pydantic import BaseModel, Field
from kapitan.errors import KapitanError
from typing import Annotated, Dict, Any, Optional

logger = logging.getLogger(__name__)


Expand All @@ -24,18 +24,23 @@ class InventoryTarget(BaseModel):


class Inventory(ABC):
def __init__(self, inventory_path: str = "inventory", compose_target_name: bool = False, ignore_class_notfound=False):
def __init__(
self,
inventory_path: str = "inventory",
compose_target_name: bool = False,
ignore_class_notfound=False,
):
self.inventory_path = inventory_path
self.compose_target_name = compose_target_name
self.targets_path = os.path.join(self.inventory_path, 'targets')
self.classes_path = os.path.join(self.inventory_path, 'classes')
self.targets_path = os.path.join(self.inventory_path, "targets")
self.classes_path = os.path.join(self.inventory_path, "classes")
self.initialised: bool = False
self.targets: dict[str, InventoryTarget] = {}

self.__initialise(ignore_class_notfound=ignore_class_notfound)

@property
def inventory(self) -> dict:
def inventory(self) -> dict[str, InventoryTarget]:
"""
get all targets from inventory
"""
Expand All @@ -57,7 +62,7 @@ def __initialise(self, ignore_class_notfound) -> bool:
name = name.replace(os.sep, ".")
else:
name, ext = os.path.splitext(file)

if ext not in (".yml", ".yaml"):
logger.debug(f"ignoring {file}: targets have to be .yml or .yaml files.")
continue
Expand All @@ -69,9 +74,9 @@ def __initialise(self, ignore_class_notfound) -> bool:
f"Conflicting targets {target.name}: {target.path} and {self.targets[target.name].path}. "
f"Consider using '--compose-target-name'."
)

self.targets[target.name] = target

self.render_targets(self.targets, ignore_class_notfound=ignore_class_notfound)
self.initialised = True
return self.initialised
Expand All @@ -82,16 +87,22 @@ def get_target(self, target_name: str, ignore_class_not_found: bool = False) ->
"""
return self.targets.get(target_name)

def get_targets(self, target_names: list[str] = [], ignore_class_not_found: bool = False) -> dict:
def get_targets(
self, target_names: list[str] = [], ignore_class_not_found: bool = False
) -> dict[str, InventoryTarget]:
"""
helper function to get rendered InventoryTarget objects for multiple targets
"""

if target_names:
return {target_name: self.targets[target_name] for target_name in target_names if target_name in self.targets}
return {
target_name: self.targets[target_name]
for target_name in target_names
if target_name in self.targets
}
else:
return self.targets

def get_parameters(self, target_names: str | list[str], ignore_class_not_found: bool = False) -> dict:
"""
helper function to get rendered parameters for single target or multiple targets
Expand All @@ -100,10 +111,12 @@ def get_parameters(self, target_names: str | list[str], ignore_class_not_found:
target = self.get_target(target_names, ignore_class_not_found)
return target.parameters

return {name: target.parameters for name, target in self.get_targets(target_names)}
return {name: target.parameters for name, target in self.get_targets(target_names).items()}

@abstractmethod
def render_targets(self, targets: list[InventoryTarget] = None, ignore_class_notfound: bool = False) -> None:
def render_targets(
self, targets: list[InventoryTarget] | None = None, ignore_class_notfound: bool = False
) -> None:
"""
create the inventory depending on which backend gets used
"""
Expand Down
14 changes: 10 additions & 4 deletions kapitan/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,9 @@ def search_imports(cwd, import_str, search_paths):
return normalised_path, normalised_path_content.encode()


def inventory(search_paths: list, target_name: str = None, inventory_path: str = "./inventory"):
def inventory(
search_paths: list, target_name: str = None, inventory_path: str = "./inventory"
) -> dict[str, dict]:
"""
Reads inventory (set by inventory_path) in search_paths.
set nodes_uri to change reclass nodes_uri the default value
Expand Down Expand Up @@ -278,7 +280,7 @@ def inventory(search_paths: list, target_name: str = None, inventory_path: str =
target = inv.get_target(target_name)
return target.model_dump()

return inv.inventory
return {k: v.model_dump() for k, v in inv.inventory.items()}


def generate_inventory(args):
Expand Down Expand Up @@ -327,9 +329,13 @@ def get_inventory(inventory_path, ignore_class_notfound: bool = False) -> Invent
compose_target_name = hasattr(cached.args, "compose_target_name") and cached.args.compose_target_name
backend = AVAILABLE_BACKENDS.get(backend_id, AVAILABLE_BACKENDS.get("reclass"))
inventory_backend: Inventory = None

logger.debug(f"Using {backend_id} as inventory backend")
inventory_backend = backend(inventory_path=inventory_path, compose_target_name=compose_target_name, ignore_class_notfound=ignore_class_notfound)
inventory_backend = backend(
inventory_path=inventory_path,
compose_target_name=compose_target_name,
ignore_class_notfound=ignore_class_notfound,
)

cached.inv = inventory_backend
# migrate inventory to selected inventory backend
Expand Down
10 changes: 10 additions & 0 deletions tests/test_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,20 @@ def test_inventory_target(self):
inv = inventory(["examples/kubernetes"], "minikube-es")
self.assertEqual(inv["parameters"]["cluster"]["name"], "minikube")

def test_inventory_target_type(self):
inv = inventory(["examples/kubernetes"], "minikube-es")
self.assertIsInstance(inv, dict)
self.assertIsInstance(inv["parameters"], dict)

def test_inventory_all_targets(self):
inv = inventory(["examples/kubernetes"], None)
self.assertNotEqual(inv.get("minikube-es"), None)

def test_inventory_all_targets_type(self):
inv = inventory(["examples/kubernetes"], None)
self.assertIsInstance(inv, dict)
self.assertIsInstance(inv["minikube-es"], dict)


class InventoryTargetTestReclassRs(InventoryTargetTest):
def setUp(self):
Expand Down
Loading