diff --git a/kapitan/inventory/inv_reclass.py b/kapitan/inventory/inv_reclass.py index f14bac280..4628c454d 100644 --- a/kapitan/inventory/inv_reclass.py +++ b/kapitan/inventory/inv_reclass.py @@ -40,8 +40,12 @@ def render_targets(self, targets: list = None, ignore_class_notfound: bool = Fal rendered_inventory = _reclass.inventory() # store parameters and classes - for name, rendered_target in rendered_inventory["nodes"].items(): - self.targets[name].parameters = rendered_target["parameters"] + for target_name, rendered_target in rendered_inventory["nodes"].items(): + self.targets[target_name].parameters = rendered_target["parameters"] + + for class_name, referenced_targets in rendered_inventory["classes"].items(): + for target_name in referenced_targets: + self.targets[target_name].classes += class_name except ReclassException as e: if isinstance(e, NotFoundError): @@ -49,20 +53,6 @@ def render_targets(self, targets: list = None, ignore_class_notfound: bool = Fal else: logger.error(f"Inventory reclass error: {e.message}") raise InventoryError(e.message) - - def get_targets(self, target_names: list) -> dict: - - for target_name in target_names: - target = self.targets.get(target_name) - if not target: - raise InventoryError(f"target '{target_name}' not found") - - if not target.parameters: - # reclass has no optimization for rendering only some specific targets, - # so we have to render the whole inventory - self.render_targets() - - return {name: target.parameters for name, target in self.targets.items() if name in target_names} def get_reclass_config(inventory_path: str) -> dict: diff --git a/kapitan/inventory/inventory.py b/kapitan/inventory/inventory.py index 78ba50308..3eff8de40 100644 --- a/kapitan/inventory/inventory.py +++ b/kapitan/inventory/inventory.py @@ -10,6 +10,7 @@ import time from abc import ABC, abstractmethod from dataclasses import dataclass, field +from typing import overload, Union from kapitan.errors import KapitanError from kapitan.reclass.reclass.values import item @@ -23,6 +24,7 @@ class InventoryTarget: path: str composed_name: str parameters: dict = field(default_factory=dict) + classes: list = field(default_factory=list) class Inventory(ABC): @@ -40,16 +42,17 @@ def __init__(self, path: str = path, compose_target_name: bool = False): @property def inventory(self) -> dict: - if not self.targets: - return self.render_targets() - return {name: target.parameters for name, target in self.targets.items()} - - @abstractmethod - def render_targets(self, targets: list = None, ignore_class_notfound: bool = False) -> dict: """ - create the inventory depending on which backend gets used + get all targets from inventory + targets will be rendered """ - raise NotImplementedError + if not self.targets: + self.search_targets() + + inventory = self.get_targets([*self.targets.keys()]) + + return {target_name: {"parameters": target.parameters, "classes": target.classes} + for target_name, target in inventory.items()} def search_targets(self) -> dict: """ @@ -84,16 +87,44 @@ def search_targets(self) -> dict: return self.targets - def get_target(self, target_name: str) -> dict: + def get_target(self, target_name: str) -> InventoryTarget: """ - helper function to get parameters for a specific target + helper function to get rendered InventoryTarget object for single target """ return self.get_targets([target_name])[target_name] - @abstractmethod def get_targets(self, target_names: list) -> dict: """ - helper function to get parameters for multiple targets + helper function to get rendered InventoryTarget objects for multiple targets + """ + targets_to_render = [] + + for target_name in target_names: + target = self.targets.get(target_name) + if not target: + raise InventoryError(f"target '{target_name}' not found") + + if not target.parameters: + targets_to_render.append(target) + + self.render_targets(targets_to_render) + + return {name: target for name, target in self.targets.items() if name in target_names} + + def get_parameters(self, target_names: Union[str, list]) -> dict: + """ + helper function to get rendered parameters for single target or multiple targets + """ + if type(target_names) is str: + target = self.get_target(target_names) + return target.parameters + + return {name: target.parameters for name, target in self.get_targets(target_names)} + + @abstractmethod + def render_targets(self, targets: list = None, ignore_class_notfound: bool = False): + """ + create the inventory depending on which backend gets used """ raise NotImplementedError diff --git a/kapitan/refs/cmd_parser.py b/kapitan/refs/cmd_parser.py index fff735b5e..3a6bae75d 100644 --- a/kapitan/refs/cmd_parser.py +++ b/kapitan/refs/cmd_parser.py @@ -66,7 +66,7 @@ def ref_write(args, ref_controller): recipients = [dict((("name", name),)) for name in args.recipients] if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError( "parameters.kapitan.secrets not defined in inventory of target {}".format( @@ -96,7 +96,7 @@ def ref_write(args, ref_controller): key = args.key if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError( "parameters.kapitan.secrets not defined in inventory of target {}".format( @@ -124,7 +124,7 @@ def ref_write(args, ref_controller): key = args.key if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError( "parameters.kapitan.secrets not defined in inventory of target {}".format( @@ -153,7 +153,7 @@ def ref_write(args, ref_controller): key = args.key if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError( "parameters.kapitan.secrets not defined in inventory of target {}".format( @@ -197,7 +197,7 @@ def ref_write(args, ref_controller): encoding = "original" if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError( "parameters.kapitan.secrets not defined in inventory of target {}".format( @@ -252,7 +252,7 @@ def ref_write(args, ref_controller): vault_params = {} if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError("parameters.kapitan.secrets not defined in {}".format(args.target_name)) @@ -324,7 +324,7 @@ def secret_update(args, ref_controller): ] if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError("parameters.kapitan.secrets not defined in {}".format(args.target_name)) @@ -352,7 +352,7 @@ def secret_update(args, ref_controller): key = args.key if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError("parameters.kapitan.secrets not defined in {}".format(args.target_name)) @@ -378,7 +378,7 @@ def secret_update(args, ref_controller): key = args.key if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError("parameters.kapitan.secrets not defined in {}".format(args.target_name)) @@ -404,7 +404,7 @@ def secret_update(args, ref_controller): key = args.key if args.target_name: inv = get_inventory(args.inventory_path) - kap_inv_params = inv.get_target(args.target_name)["kapitan"] + kap_inv_params = inv.get_parameters(args.target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError("parameters.kapitan.secrets not defined in {}".format(args.target_name)) @@ -467,7 +467,7 @@ def secret_update_validate(args, ref_controller): ret_code = 0 for target_name, token_paths in target_token_paths.items(): - kap_inv_params = inv.get_target(target_name)["kapitan"] + kap_inv_params = inv.get_parameters(target_name)["kapitan"] if "secrets" not in kap_inv_params: raise KapitanError("parameters.kapitan.secrets not defined in {}".format(target_name)) diff --git a/kapitan/refs/secrets/awskms.py b/kapitan/refs/secrets/awskms.py index 7e12908af..beb9658e6 100644 --- a/kapitan/refs/secrets/awskms.py +++ b/kapitan/refs/secrets/awskms.py @@ -55,7 +55,7 @@ def from_params(cls, data, ref_params): if target_name is None: raise ValueError("target_name not set") - target_inv = cached.inv.get_target(target_name) + target_inv = cached.inv.get_parameters(target_name) if target_inv is None: raise ValueError("target_inv not set") diff --git a/kapitan/refs/secrets/azkms.py b/kapitan/refs/secrets/azkms.py index c5e694945..3cd2ed264 100644 --- a/kapitan/refs/secrets/azkms.py +++ b/kapitan/refs/secrets/azkms.py @@ -79,7 +79,7 @@ def from_params(cls, data, ref_params): if target_name is None: raise ValueError("target_name not set") - target_inv = cached.inv.get_target(target_name) + target_inv = cached.inv.get_parameters(target_name) key = target_inv["kapitan"]["secrets"]["azkms"]["key"] return cls(data, key, **ref_params.kwargs) diff --git a/kapitan/refs/secrets/gkms.py b/kapitan/refs/secrets/gkms.py index 7536c989f..a071b270c 100644 --- a/kapitan/refs/secrets/gkms.py +++ b/kapitan/refs/secrets/gkms.py @@ -66,7 +66,7 @@ def from_params(cls, data, ref_params): if target_name is None: raise ValueError("target_name not set") - target_inv = cached.inv.get_target(target_name) + target_inv = cached.inv.get_parameters(target_name) key = target_inv["kapitan"]["secrets"]["gkms"]["key"] return cls(data, key, **ref_params.kwargs) diff --git a/kapitan/refs/secrets/gpg.py b/kapitan/refs/secrets/gpg.py index bde0d7f2d..e19078911 100644 --- a/kapitan/refs/secrets/gpg.py +++ b/kapitan/refs/secrets/gpg.py @@ -76,7 +76,7 @@ def from_params(cls, data, ref_params): if target_name is None: raise ValueError("target_name not set") - target_inv = cached.inv.get_target(target_name) + target_inv = cached.inv.get_parameters(target_name) if "secrets" not in target_inv["kapitan"]: raise KapitanError( diff --git a/kapitan/refs/secrets/vaultkv.py b/kapitan/refs/secrets/vaultkv.py index a00d89a37..6c89bfcaf 100644 --- a/kapitan/refs/secrets/vaultkv.py +++ b/kapitan/refs/secrets/vaultkv.py @@ -60,7 +60,7 @@ def from_params(cls, data, ref_params): if target_name is None: raise ValueError("target_name not set") - target_inv = cached.inv.get_target(target_name) + target_inv = cached.inv.get_parameters(target_name) try: vault_params = target_inv["kapitan"]["secrets"]["vaultkv"] diff --git a/kapitan/refs/secrets/vaulttransit.py b/kapitan/refs/secrets/vaulttransit.py index f48385c1e..3f08cdaf9 100644 --- a/kapitan/refs/secrets/vaulttransit.py +++ b/kapitan/refs/secrets/vaulttransit.py @@ -50,7 +50,7 @@ def from_params(cls, data, ref_params): if target_name is None: raise ValueError("target_name not set") - target_inv = cached.inv.get_target(target_name) + target_inv = cached.inv.get_parameters(target_name) ref_params.kwargs["vault_params"] = target_inv["kapitan"]["secrets"]["vaulttransit"] diff --git a/kapitan/resources.py b/kapitan/resources.py index a6b0f974f..96c3bdc0c 100644 --- a/kapitan/resources.py +++ b/kapitan/resources.py @@ -244,7 +244,7 @@ def search_imports(cwd, import_str, search_paths): return normalised_path, normalised_path_content.encode() -def inventory(search_paths: list, target, inventory_path: str = None): +def inventory(search_paths: list, target_name: str = None, inventory_path: str = "./inventory"): """ Reads inventory (set by inventory_path) in search_paths. set nodes_uri to change reclass nodes_uri the default value @@ -276,8 +276,9 @@ def inventory(search_paths: list, target, inventory_path: str = None): inv = get_inventory(full_inv_path) - if target: - return {"parameters": inv.get_target(target)} + if target_name: + target = inv.get_target(target_name) + return {"parameters": target.parameters, "classes": target.classes} return inv.inventory @@ -287,7 +288,7 @@ def generate_inventory(args): inv = get_inventory(args.inventory_path) if args.target_name: - inv = inv.get_target(args.target_name) + inv = inv.get_parameters(args.target_name) if args.pattern: pattern = args.pattern.split(".") inv = deep_get(inv, pattern) diff --git a/kapitan/targets.py b/kapitan/targets.py index adc645df8..f7f4c536e 100644 --- a/kapitan/targets.py +++ b/kapitan/targets.py @@ -250,15 +250,15 @@ def generate_inv_cache_hashes(inventory_path, targets, cache_paths): for target in targets: try: cached.inv_cache["inventory"][target] = {} - cached.inv_cache["inventory"][target] = dictionary_hash(inv.get_target(target)) + cached.inv_cache["inventory"][target] = dictionary_hash(inv.get_parameters(target)) except KeyError: raise CompileError(f"target not found: {target}") else: for target in inv.targets.keys(): cached.inv_cache["inventory"][target] = {} - cached.inv_cache["inventory"][target] = dictionary_hash(inv.get_target(target)) + cached.inv_cache["inventory"][target] = dictionary_hash(inv.get_parameters(target)) - compile_obj = inv.get_target(target)["kapitan"]["compile"] + compile_obj = inv.get_parameters(target)["kapitan"]["compile"] for obj in compile_obj: for input_path in obj["input_paths"]: base_folder = os.path.dirname(input_path).split("/")[0] @@ -374,8 +374,7 @@ def load_target_inventory(inventory_path, targets, ignore_class_notfound=False): for target_name in targets_list: try: - target_obj = inv.get_target(target_name) - target_obj = target_obj.get("kapitan") + target_obj = inv.get_parameters(target_name).get("kapitan") # check if parameters.kapitan is empty if not target_obj: raise InventoryError(f"InventoryError: {target_name}: parameters.kapitan has no assignment") @@ -410,7 +409,7 @@ def search_targets(inventory_path, targets, labels): matched_all_labels = False for label, value in labels_dict.items(): try: - if inv.get_target(target_name)["kapitan"]["labels"][label] == value: + if inv.get_parameters(target_name)["kapitan"]["labels"][label] == value: matched_all_labels = True continue except KeyError: diff --git a/tests/test_compile.py b/tests/test_compile.py index 73ee31eaf..677b57183 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -173,7 +173,7 @@ def test_compile_not_matching_targets(self): def test_compile_vars_target_missing(self): inventory_path = "inventory" target_filename = "minikube-es" - target_obj = get_inventory(inventory_path).get_target(target_filename)["kapitan"] + target_obj = get_inventory(inventory_path).get_parameters(target_filename)["kapitan"] # delete vars.target del target_obj["vars"]["target"]