11import contextvars
22import functools
3+ import json
34import logging
45from enum import Enum
56from typing import List
67
8+ import jmespath
9+ import jmespath .lexer
710import yaml
811from box .exceptions import BoxValueError
912from kapitan .cached import args
1619 current_target ,
1720 inventory_global ,
1821)
19- from kapitan .utils import render_jinja2_file
22+ from kapitan .utils import prune_empty , render_jinja2_file
2023
2124logger = logging .getLogger (__name__ )
2225
23- search_paths = args .get ("search_paths" )
26+ search_paths = args .get ("search_paths" ) if type ( args ) is dict else args . search_paths
2427registered_generators = contextvars .ContextVar (
2528 "current registered_generators in thread" , default = {}
2629)
3033
3134@functools .lru_cache
3235def load_generators (name , path ):
36+ import os
3337 from importlib import import_module
3438 from inspect import isclass
3539 from pkgutil import iter_modules
36- import os
3740
3841 # iterate through the modules in the current package
3942 package_dir = os .path .abspath (os .path .dirname (path ))
4043 for _ , module_name , _ in iter_modules ([package_dir ]):
41- # import the module and iterate through its attributes
42- module = import_module (f"{ name } .{ module_name } " )
43- for attribute_name in dir (module ):
44- attribute = getattr (module , attribute_name )
44+ try :
45+ # import the module and iterate through its attributes
46+ module = import_module (f"{ name } .{ module_name } " )
47+ for attribute_name in dir (module ):
48+ attribute = getattr (module , attribute_name )
4549
46- if isclass (attribute ):
47- # Add the class to this package's variables
48- globals ()[attribute_name ] = attribute
50+ if isclass (attribute ):
51+ # Add the class to this package's variables
52+ globals ()[attribute_name ] = attribute
53+ except Exception as e :
54+ logger .error (f"Error loading { module_name } : { e } " )
4955
5056
5157class DeleteContent (Exception ):
@@ -100,6 +106,18 @@ def render_jinja(filename, ctx):
100106 return render_jinja2_file (filename , ctx , search_paths = search_paths )
101107
102108
109+ def render_json (data ):
110+ if isinstance (data , str ):
111+ data = json .loads (data )
112+ return json .dumps (data , indent = 4 )
113+
114+
115+ def render_yaml (data ):
116+ if isinstance (data , str ):
117+ data = yaml .safe_load (data )
118+ return yaml .dump (data , default_flow_style = False , width = 1000 , sort_keys = True )
119+
120+
103121def findpaths_by_property (obj : dict , property : str ) -> dict :
104122 """
105123 Traverses the whole dictionary looking of objects containing a given property.
@@ -123,26 +141,12 @@ def findpaths_by_property(obj: dict, property: str) -> dict:
123141
124142def findpath (obj , path , default = {}):
125143 value = default
126- if path :
127- path_parts = path .split ("." )
128- else :
129- return value
130-
131144 try :
132- value = getattr (obj , path_parts [0 ])
133- except KeyError as e :
134- if value is not None :
135- return value
136- logging .info (f"Key { e } not found in { obj } : ignoring" )
137- except AttributeError as e :
138- if value is not None :
139- return value
140- logging .info (f"Attribute { e } not found in { obj } : ignoring" )
141-
142- if len (path_parts ) == 1 :
143- return value
144- else :
145- return findpath (value , "." .join (path_parts [1 :]))
145+ value = jmespath .search (path , obj )
146+ except jmespath .exceptions .EmptyExpressionError :
147+ pass
148+
149+ return value or default
146150
147151
148152def register_generator (* args , ** kwargs ):
@@ -161,15 +165,22 @@ class ContentType(Enum):
161165 YAML = 1
162166 KUBERNETES_RESOURCE = 2
163167 TERRAFORM_BLOCK = 3
168+ JSON = 4
164169
165170
166171class BaseContent (BaseModel ):
167172 content_type : ContentType = ContentType .YAML
168- filename : str = None
173+ filename : str = "output"
174+ prune : bool = True
169175
170176 def body (self ):
171177 pass
172178
179+ def dump (self ):
180+ if self .prune :
181+ self .root = Dict (prune_empty (self .root ))
182+ return super ().dump ()
183+
173184 @classmethod
174185 def from_baseobj (cls , baseobj : BaseObj ):
175186 """Return a BaseContent initialised with baseobj."""
@@ -207,12 +218,7 @@ def parse(self, content: Dict):
207218
208219 @staticmethod
209220 def findpath (obj , path ):
210- path_parts = path .split ("." )
211- value = getattr (obj , path_parts [0 ])
212- if len (path_parts ) == 1 :
213- return value
214- else :
215- return BaseContent .findpath (value , "." .join (path_parts [1 :]))
221+ return findpath (obj , path )
216222
217223 def mutate (self , mutations : List ):
218224 for action , conditions in mutations .items ():
@@ -224,16 +230,21 @@ def mutate(self, mutations: List):
224230 for condition in conditions :
225231 if self .match (condition ["conditions" ]):
226232 raise DeleteContent (f"Deleting { self } because of { condition } " )
233+ if action == "prune" :
234+ for condition in conditions :
235+ if self .match (condition ["conditions" ]):
236+ self .prune = condition .get ("prune" , True )
237+ if condition .get ("break" , True ):
238+ break
227239 if action == "bundle" :
228240 for condition in conditions :
229241 if self .match (condition ["conditions" ]):
230- if self .filename is None :
231- try :
232- self .filename = condition ["filename" ].format (content = self )
233- except (AttributeError , KeyError ):
234- pass
235- if condition .get ("break" , True ):
236- break
242+ try :
243+ self .filename = condition ["filename" ].format (content = self )
244+ except (AttributeError , KeyError ):
245+ pass
246+ if condition .get ("break" , True ):
247+ break
237248
238249 def match (self , match_conditions ):
239250 for key , values in match_conditions .items ():
@@ -321,9 +332,6 @@ def dump(self, output_filename=None, already_processed=False):
321332 output_format = output_filename
322333 else :
323334 output_format = getattr (content , "filename" , "output" )
324- if output_format is None :
325- output_format = "output"
326-
327335 filename = output_format .format (content = content )
328336 file_content_list = self .root .get (filename , [])
329337 if content in file_content_list :
0 commit comments