23
23
const (
24
24
// KeyTerraform is the key for terraform components.
25
25
KeyTerraform = "terraform"
26
+ // KeyHelmfile is the key for helmfile components.
27
+ KeyHelmfile = "helmfile"
26
28
// KeySettings is the key for settings section.
27
29
KeySettings = "settings"
28
30
// KeyMetadata is the key for metadata section.
@@ -75,7 +77,7 @@ func FilterAndListValues(stacksMap map[string]interface{}, options *FilterOption
75
77
}
76
78
77
79
// Extract stack values
78
- extractedValues , err := extractComponentValues (stacksMap , options .Component , options .ComponentFilter , options .IncludeAbstract )
80
+ extractedValues , err := extractComponentValuesFromAllStacks (stacksMap , options .Component , options .ComponentFilter , options .IncludeAbstract )
79
81
if err != nil {
80
82
return "" , err
81
83
}
@@ -117,82 +119,191 @@ func createComponentError(component, componentFilter string) error {
117
119
}
118
120
}
119
121
120
- // extractComponentValues extracts the component values from all stacks.
121
- func extractComponentValues (stacksMap map [string ]interface {}, component string , componentFilter string , includeAbstract bool ) (map [string ]interface {}, error ) {
122
- values := make (map [string ]interface {})
122
+ func extractComponentValuesFromAllStacks (stacks map [string ]interface {}, component , filter string , includeAbstract bool ) (map [string ]interface {}, error ) {
123
+ stackComponentValues := make (map [string ]interface {})
123
124
124
- // Check if this is a regular component and use it as filter if no specific filter
125
- isComponentSection := component != KeySettings && component != KeyMetadata
126
- if isComponentSection && componentFilter == "" {
127
- log .Debug ("Using component as filter" , KeyComponent , component )
128
- componentFilter = component
129
- component = ""
130
- }
131
-
132
- log .Debug ("Building YQ expression" , KeyComponent , component , "componentFilter" , componentFilter )
125
+ component , filter = normalizeComponentAndFilterInputs (component , filter )
133
126
134
- for stackName , stackData := range stacksMap {
135
- stack , ok := stackData .(map [string ]interface {})
127
+ for stackName , data := range stacks {
128
+ stackMap , ok := data .(map [string ]interface {})
136
129
if ! ok {
137
- log .Debug ("stack data is not a map" , KeyStack , stackName )
138
130
continue
139
131
}
140
132
141
- // Build and execute YQ expression
142
- yqExpression := processComponentType (component , componentFilter , includeAbstract )
143
- queryResult , err := utils .EvaluateYqExpression (nil , stack , yqExpression )
144
- if err != nil || queryResult == nil {
145
- log .Debug ("no values found" ,
146
- KeyStack , stackName , KeyComponent , component ,
147
- "componentFilter" , componentFilter , "yq_expression" , yqExpression ,
148
- "error" , err )
149
- continue
133
+ componentValue := extractComponentValueFromSingleStack (stackMap , stackName , component , filter , includeAbstract )
134
+ if componentValue != nil {
135
+ stackComponentValues [stackName ] = componentValue
136
+ }
137
+ }
138
+
139
+ if len (stackComponentValues ) == 0 {
140
+ return nil , createComponentError (component , filter )
141
+ }
142
+
143
+ return stackComponentValues , nil
144
+ }
145
+
146
+ func normalizeComponentAndFilterInputs (component , filter string ) (string , string ) {
147
+ isRegularComponent := component != KeySettings && component != KeyMetadata
148
+ if isRegularComponent && filter == "" {
149
+ log .Debug ("Using component name as filter" , KeyComponent , component )
150
+ return "" , component
151
+ }
152
+ return component , filter
153
+ }
154
+
155
+ func extractComponentValueFromSingleStack (stackMap map [string ]interface {}, stackName , component , filter string , includeAbstract bool ) interface {} {
156
+ targetComponentName := determineTargetComponentName (component , filter )
157
+
158
+ componentType := detectComponentTypeInStack (stackMap , targetComponentName , stackName )
159
+ if componentType == "" {
160
+ return nil
161
+ }
162
+
163
+ value , err := executeQueryForStack (
164
+ stackName ,
165
+ stackMap ,
166
+ component ,
167
+ filter ,
168
+ targetComponentName ,
169
+ componentType ,
170
+ includeAbstract ,
171
+ )
172
+
173
+ if err != nil {
174
+ log .Warn ("Query failed" , KeyStack , stackName , "error" , err )
175
+ return nil
176
+ }
177
+
178
+ return value
179
+ }
180
+
181
+ func detectComponentTypeInStack (stackMap map [string ]interface {}, targetComponent , stackName string ) string {
182
+ if targetComponent == "" {
183
+ return KeyTerraform
184
+ }
185
+
186
+ detectedType , err := determineComponentType (stackMap , targetComponent )
187
+ if err != nil {
188
+ log .Debug ("Component not found" , KeyStack , stackName , KeyComponent , targetComponent )
189
+ return ""
190
+ }
191
+
192
+ return detectedType
193
+ }
194
+
195
+ func executeQueryForStack (
196
+ stackName string ,
197
+ stackMap map [string ]interface {},
198
+ component , componentFilter , targetComponentName , componentType string ,
199
+ includeAbstract bool ,
200
+ ) (interface {}, error ) {
201
+ yqExpression := buildYqExpressionForComponent (component , componentFilter , includeAbstract , componentType )
202
+ queryResult , err := utils .EvaluateYqExpression (nil , stackMap , yqExpression )
203
+
204
+ if err != nil {
205
+ var logKey string
206
+ var logValue string
207
+ if targetComponentName != "" {
208
+ logKey = KeyComponent
209
+ logValue = targetComponentName
210
+ } else {
211
+ logKey = "section"
212
+ logValue = component
150
213
}
151
214
152
- // Process the result based on component type
153
- values [stackName ] = processQueryResult (component , queryResult )
215
+ log .Warn ("YQ evaluation failed" ,
216
+ KeyStack , stackName ,
217
+ "yqExpression" , yqExpression ,
218
+ logKey , logValue ,
219
+ "error" , err )
220
+ return nil , fmt .Errorf ("query failed: %w" , err )
154
221
}
155
222
156
- if len ( values ) == 0 {
157
- return nil , createComponentError ( component , componentFilter )
223
+ if queryResult == nil {
224
+ return nil , nil
158
225
}
159
226
160
- return values , nil
227
+ return extractRelevantDataFromQueryResult ( component , queryResult ) , nil
161
228
}
162
229
163
- // processComponentType determines the YQ expression based on component type.
164
- func processComponentType (component string , componentFilter string , includeAbstract bool ) string {
165
- // If this is a regular component query with a specific component filter
230
+ func determineTargetComponentName (component , componentFilter string ) string {
231
+ if componentFilter != "" {
232
+ return getComponentNameFromPath (componentFilter )
233
+ }
234
+
235
+ isRegularComponent := component != KeySettings && component != KeyMetadata
236
+ if isRegularComponent {
237
+ return getComponentNameFromPath (component )
238
+ }
239
+
240
+ return ""
241
+ }
242
+
243
+ func determineComponentType (stack map [string ]interface {}, targetComponentName string ) (string , error ) {
244
+ if targetComponentName == "" {
245
+ return "" , fmt .Errorf ("targetComponentName cannot be empty" )
246
+ }
247
+
248
+ components , ok := stack [KeyComponents ].(map [string ]interface {})
249
+ if ! ok {
250
+ return "" , fmt .Errorf ("components section not found in stack" )
251
+ }
252
+
253
+ if isComponentInSection (components , KeyTerraform , targetComponentName ) {
254
+ log .Debug ("Component found under terraform" , KeyComponent , targetComponentName )
255
+ return KeyTerraform , nil
256
+ }
257
+
258
+ if isComponentInSection (components , KeyHelmfile , targetComponentName ) {
259
+ log .Debug ("Component found under helmfile" , KeyComponent , targetComponentName )
260
+ return KeyHelmfile , nil
261
+ }
262
+
263
+ return "" , fmt .Errorf ("component '%s' not found in terraform or helmfile sections" , targetComponentName )
264
+ }
265
+
266
+ func isComponentInSection (components map [string ]interface {}, sectionKey , componentName string ) bool {
267
+ section , ok := components [sectionKey ].(map [string ]interface {})
268
+ if ! ok {
269
+ return false
270
+ }
271
+ _ , exists := section [componentName ]
272
+ return exists
273
+ }
274
+
275
+ func buildYqExpressionForComponent (component string , componentFilter string , includeAbstract bool , componentType string ) string {
166
276
if component == "" && componentFilter != "" {
167
- return fmt .Sprintf (".components.%s.\" %s\" " , KeyTerraform , componentFilter )
277
+ return fmt .Sprintf (".components.%s.\" %s\" " , componentType , componentFilter )
168
278
}
169
279
170
- // Handle special section queries.
171
280
switch component {
172
281
case KeySettings :
173
- if componentFilter != "" {
174
- componentName := getComponentNameFromPath (componentFilter )
175
- return fmt .Sprintf (".components.%s.%s" , KeyTerraform , componentName )
176
- }
177
- return "select(.settings // .terraform.settings // .components.terraform.*.settings)"
282
+ return buildSettingsExpression (componentFilter , componentType )
178
283
case KeyMetadata :
179
- if componentFilter != "" {
180
- // For metadata with component filter, target the specific component.
181
- componentName := getComponentNameFromPath (componentFilter )
182
- return fmt .Sprintf (".components.%s.%s" , KeyTerraform , componentName )
183
- }
184
- // For general metadata query.
185
- return DotChar + KeyMetadata
284
+ return buildMetadataExpression (componentFilter , componentType )
186
285
default :
187
- // Extract component name from path.
188
286
componentName := getComponentNameFromPath (component )
287
+ return buildComponentYqExpression (componentName , includeAbstract , componentType )
288
+ }
289
+ }
189
290
190
- // Build query for component vars.
191
- return buildComponentYqExpression (componentName , includeAbstract )
291
+ func buildSettingsExpression (componentFilter , componentType string ) string {
292
+ if componentFilter != "" {
293
+ componentName := getComponentNameFromPath (componentFilter )
294
+ return fmt .Sprintf (".components.%s.%s" , componentType , componentName )
192
295
}
296
+ return "select(.settings // .terraform.settings // .components.terraform.*.settings)"
297
+ }
298
+
299
+ func buildMetadataExpression (componentFilter , componentType string ) string {
300
+ if componentFilter != "" {
301
+ componentName := getComponentNameFromPath (componentFilter )
302
+ return fmt .Sprintf (".components.%s.%s" , componentType , componentName )
303
+ }
304
+ return DotChar + KeyMetadata
193
305
}
194
306
195
- // getComponentNameFromPath extracts the component name from a potentially nested path.
196
307
func getComponentNameFromPath (component string ) string {
197
308
parts := strings .Split (component , "/" )
198
309
if len (parts ) > 1 {
@@ -201,37 +312,33 @@ func getComponentNameFromPath(component string) string {
201
312
return component
202
313
}
203
314
204
- // buildComponentYqExpression creates the YQ expression for extracting component vars.
205
- func buildComponentYqExpression (componentName string , includeAbstract bool ) string {
206
- // Base expression to target the component
207
- yqExpression := fmt .Sprintf ("%scomponents%s%s%s%s" , DotChar , DotChar , KeyTerraform , DotChar , componentName )
315
+ func buildComponentYqExpression (componentName string , includeAbstract bool , componentType string ) string {
316
+ path := fmt .Sprintf ("%scomponents%s%s%s%s" , DotChar , DotChar , componentType , DotChar , componentName )
208
317
209
- // If not including abstract components, filter them out
210
318
if ! includeAbstract {
211
- // Only get component that either doesn't have abstract flag or has it set to false
212
- yqExpression += fmt .Sprintf (" | select(has(\" %s\" ) == false or %s%s == false)" ,
319
+ path += fmt .Sprintf (" | select(has(\" %s\" ) == false or %s%s == false)" ,
213
320
KeyAbstract , DotChar , KeyAbstract )
214
321
}
215
322
216
- // Get the vars
217
- yqExpression += fmt .Sprintf (" | %s%s" , DotChar , KeyVars )
218
-
219
- return yqExpression
323
+ return path + fmt .Sprintf (" | %s%s" , DotChar , KeyVars )
220
324
}
221
325
222
- // processQueryResult handles the query result based on component type.
223
- func processQueryResult (component string , queryResult interface {}) interface {} {
224
- // Process settings specially to handle nested settings key
225
- if component == KeySettings {
226
- if settings , ok := queryResult .(map [string ]interface {}); ok {
227
- if settingsContent , ok := settings [KeySettings ].(map [string ]interface {}); ok {
228
- return settingsContent
229
- }
230
- }
326
+ func extractRelevantDataFromQueryResult (component string , queryResult interface {}) interface {} {
327
+ if component != KeySettings {
328
+ return queryResult
231
329
}
232
330
233
- // Return the result as is for other components
234
- return queryResult
331
+ settings , ok := queryResult .(map [string ]interface {})
332
+ if ! ok {
333
+ return queryResult
334
+ }
335
+
336
+ settingsContent , ok := settings [KeySettings ].(map [string ]interface {})
337
+ if ! ok {
338
+ return queryResult
339
+ }
340
+
341
+ return settingsContent
235
342
}
236
343
237
344
// applyFilters applies stack pattern and column limits to the values.
@@ -406,7 +513,7 @@ func processStackWithQuery(stackName string, stackData interface{}, query string
406
513
return nil , false
407
514
}
408
515
409
- formattedResult := formatResultForDisplay (queryResult , query )
516
+ formattedResult := formatResultForDisplay (queryResult )
410
517
return formattedResult , formattedResult != nil
411
518
}
412
519
@@ -441,16 +548,10 @@ func applyQuery(filteredValues map[string]interface{}, query string, component s
441
548
}
442
549
443
550
// formatResultForDisplay formats query results for display.
444
- func formatResultForDisplay (result interface {}, query string ) interface {} {
445
- log .Debug ("Formatting query result for display" ,
446
- "result_type" , fmt .Sprintf (TypeFormatSpec , result ),
447
- "query" , query )
448
-
551
+ func formatResultForDisplay (result interface {}) interface {} {
449
552
if result == nil {
450
- log .Debug ("Skipping nil result" )
451
553
return nil
452
554
}
453
-
454
555
return result
455
556
}
456
557
0 commit comments