Skip to content

Commit 68def1c

Browse files
committed
clean code and add helmfile support
1 parent 1e272a0 commit 68def1c

File tree

1 file changed

+182
-81
lines changed

1 file changed

+182
-81
lines changed

pkg/list/list_values.go

+182-81
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ var (
2323
const (
2424
// KeyTerraform is the key for terraform components.
2525
KeyTerraform = "terraform"
26+
// KeyHelmfile is the key for helmfile components.
27+
KeyHelmfile = "helmfile"
2628
// KeySettings is the key for settings section.
2729
KeySettings = "settings"
2830
// KeyMetadata is the key for metadata section.
@@ -75,7 +77,7 @@ func FilterAndListValues(stacksMap map[string]interface{}, options *FilterOption
7577
}
7678

7779
// 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)
7981
if err != nil {
8082
return "", err
8183
}
@@ -117,82 +119,191 @@ func createComponentError(component, componentFilter string) error {
117119
}
118120
}
119121

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{})
123124

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)
133126

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{})
136129
if !ok {
137-
log.Debug("stack data is not a map", KeyStack, stackName)
138130
continue
139131
}
140132

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
150213
}
151214

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)
154221
}
155222

156-
if len(values) == 0 {
157-
return nil, createComponentError(component, componentFilter)
223+
if queryResult == nil {
224+
return nil, nil
158225
}
159226

160-
return values, nil
227+
return extractRelevantDataFromQueryResult(component, queryResult), nil
161228
}
162229

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 {
166276
if component == "" && componentFilter != "" {
167-
return fmt.Sprintf(".components.%s.\"%s\"", KeyTerraform, componentFilter)
277+
return fmt.Sprintf(".components.%s.\"%s\"", componentType, componentFilter)
168278
}
169279

170-
// Handle special section queries.
171280
switch component {
172281
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)
178283
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)
186285
default:
187-
// Extract component name from path.
188286
componentName := getComponentNameFromPath(component)
287+
return buildComponentYqExpression(componentName, includeAbstract, componentType)
288+
}
289+
}
189290

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)
192295
}
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
193305
}
194306

195-
// getComponentNameFromPath extracts the component name from a potentially nested path.
196307
func getComponentNameFromPath(component string) string {
197308
parts := strings.Split(component, "/")
198309
if len(parts) > 1 {
@@ -201,37 +312,33 @@ func getComponentNameFromPath(component string) string {
201312
return component
202313
}
203314

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)
208317

209-
// If not including abstract components, filter them out
210318
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)",
213320
KeyAbstract, DotChar, KeyAbstract)
214321
}
215322

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)
220324
}
221325

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
231329
}
232330

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
235342
}
236343

237344
// applyFilters applies stack pattern and column limits to the values.
@@ -406,7 +513,7 @@ func processStackWithQuery(stackName string, stackData interface{}, query string
406513
return nil, false
407514
}
408515

409-
formattedResult := formatResultForDisplay(queryResult, query)
516+
formattedResult := formatResultForDisplay(queryResult)
410517
return formattedResult, formattedResult != nil
411518
}
412519

@@ -441,16 +548,10 @@ func applyQuery(filteredValues map[string]interface{}, query string, component s
441548
}
442549

443550
// 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{} {
449552
if result == nil {
450-
log.Debug("Skipping nil result")
451553
return nil
452554
}
453-
454555
return result
455556
}
456557

0 commit comments

Comments
 (0)