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