Skip to content

Fix: nested components for list pkg #1225

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open

Conversation

Cerebrovinny
Copy link
Collaborator

@Cerebrovinny Cerebrovinny commented Apr 30, 2025

what

Enabled atmos list vars and atmos list values to correctly display information for nested components (e.g., apis/weather, eks/cluster).

why
previously we assume and extract only the basename of nested components
These changes ensure the full component path is used for both existence checks and data querying.

references

Summary by CodeRabbit

  • Refactor
    • Added support for a new component type, enabling users to query both Terraform and Helmfile components.
    • Improved component value extraction with enhanced error handling and modular query construction for better reliability.
    • Expanded component existence checks to cover all component types, providing more comprehensive search results.
  • Tests
    • Introduced new tests to verify query expression construction across different component types and configurations.

@Cerebrovinny Cerebrovinny added the patch A minor, backward compatible change label Apr 30, 2025
Copy link

codecov bot commented Apr 30, 2025

Codecov Report

Attention: Patch coverage is 63.87097% with 56 lines in your changes missing coverage. Please review.

Project coverage is 33.69%. Comparing base (f26dfe1) to head (71aadb9).

Files with missing lines Patch % Lines
pkg/list/list_values.go 67.34% 37 Missing and 11 partials ⚠️
pkg/list/utils/utils.go 0.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1225      +/-   ##
==========================================
+ Coverage   33.56%   33.69%   +0.12%     
==========================================
  Files         226      226              
  Lines       24219    24289      +70     
==========================================
+ Hits         8130     8185      +55     
- Misses      14875    14882       +7     
- Partials     1214     1222       +8     
Flag Coverage Δ
unittests 33.69% <63.87%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

coderabbitai bot commented Apr 30, 2025

📝 Walkthrough

"""

Walkthrough

The changes refactor the logic for extracting and querying component values from stacks, introducing support for a new component type helmfile alongside terraform. The extraction process is modularized by splitting it into smaller functions, including detection of component type per stack and building YQ expressions parameterized by component type. The code now uses the full component path string directly for existence checks across all component types rather than only terraform. Error handling and query execution are centralized, and the formatting logic is simplified by removing unused parameters.

Changes

Files Change Summary
pkg/list/list_values.go Refactored component value extraction into smaller functions; added support for helmfile component type; modularized YQ expression builders; improved error handling and query execution; removed debug logs and simplified formatting.
pkg/list/utils/utils.go Changed component existence check to iterate over all component types using the full component path string, removing base name extraction logic.
pkg/list/list_values_test.go Added tests for buildComponentYqExpression covering different component types and nested component names with and without abstract filtering.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AtmosCLI
    participant ListModule

    User->>AtmosCLI: Run 'atmos list vars/values <component> --stack <stack>'
    AtmosCLI->>ListModule: Check if component exists across all types using full path
    ListModule-->>AtmosCLI: Return existence result
    alt Component exists
        AtmosCLI->>ListModule: Determine component type (terraform or helmfile)
        ListModule->>ListModule: Build YQ expression based on component type and filters
        ListModule->>ListModule: Execute query on stack data
        ListModule-->>AtmosCLI: Return extracted values
        AtmosCLI-->>User: Output vars/values
    else Component does not exist
        AtmosCLI-->>User: Error: component does not exist
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Support listing vars/values for nested components (e.g., eks/cluster) (#1220)

Possibly related PRs

Suggested labels

minor

Suggested reviewers

  • osterman
  • aknysh
    """

📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed89ec7 and f6b2c1a.

📒 Files selected for processing (1)
  • pkg/list/list_values_test.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Summary
🔇 Additional comments (1)
pkg/list/list_values_test.go (1)

413-457: Well-structured test that verifies nested component support and helmfile component type.

This test function properly validates the generation of YQ expressions for different scenarios including nested components (like "nested/vpc") and the new helmfile component type. The table-driven approach with clear test cases makes the verification straightforward.

The test case on line 437-441 specifically confirms that nested component paths are handled correctly, which directly addresses the PR objective of fixing nested components for the list package.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or @auto-summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @auto-title anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
pkg/list/list_values.go (1)

174-176: 🛠️ Refactor suggestion

Consider consistent handling of component paths throughout the code.

While line 167 was updated to handle nested components correctly, other instances of getComponentNameFromPath are still being used in this file. These might need similar updates to ensure consistent handling of nested components in all cases.

Consider applying the same pattern to the remaining occurrences where getComponentNameFromPath is used for component filtering:

-            componentName := getComponentNameFromPath(componentFilter)
-            return fmt.Sprintf(".components.%s.%s", KeyTerraform, componentName)
+            return fmt.Sprintf(".components.%s.\"%s\"", KeyTerraform, componentFilter)

Also applies to: 180-183, 188-191

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f42f14b and d876261.

📒 Files selected for processing (2)
  • pkg/list/list_values.go (1 hunks)
  • pkg/list/utils/utils.go (1 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
pkg/list/utils/utils.go

[warning] 58-58: pkg/list/utils/utils.go#L58
Added line #L58 was not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Summary

coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 30, 2025
coderabbitai[bot]
coderabbitai bot previously approved these changes May 2, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
pkg/list/list_values.go (1)

241-262: Prefer wrapped sentinel errors instead of dynamic fmt.Errorf

Static analysis rightfully points out the dynamic errors here. Defining sentinel errors (var ErrComponentNotFound = errors.New(...)) and wrapping them (errors.Wrap) provides stronger typing and makes error handling downstream simpler.

Not urgent, but adopting this pattern keeps the codebase consistent with existing lint rules.

🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 243-243:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("targetComponentName cannot be empty")"


[failure] 248-248:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("components section not found in stack")"


[failure] 261-261:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("component '%s' not found in terraform or helmfile sections", targetComponentName)"

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e272a0 and c58b6ff.

📒 Files selected for processing (1)
  • pkg/list/list_values.go (6 hunks)
🧰 Additional context used
🪛 GitHub Check: golangci-lint
pkg/list/list_values.go

[failure] 194-194:
argument-limit: maximum number of arguments per function exceeded; max 5 but got 7


[failure] 243-243:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("targetComponentName cannot be empty")"


[failure] 248-248:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("components section not found in stack")"


[failure] 261-261:
do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf("component '%s' not found in terraform or helmfile sections", targetComponentName)"

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Summary

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
pkg/list/list_values.go (3)

302-304: ⚠️ Potential issue

Component path is lost in regular component branch.

Similar to past review comments, the regular component branch still strips the path from nested components with getComponentNameFromPath(), which will break nested component support.

-       componentName := getComponentNameFromPath(component)
-       return buildComponentYqExpression(componentName, includeAbstract, componentType)
+       // Use full component path to support nested components
+       return fmt.Sprintf(".components.%s.\"%s\"", componentType, component) + 
+              (includeAbstract ? "" : fmt.Sprintf(" | select(has(\"%s\") == false or %s%s == false)", 
+                              KeyAbstract, DotChar, KeyAbstract)) + 
+              fmt.Sprintf(" | %s%s", DotChar, KeyVars)

307-313: ⚠️ Potential issue

Settings expression still loses nested paths.

The settings expression still uses getComponentNameFromPath() which strips the parent path from nested components and doesn't wrap the component in quotes.

 func buildSettingsExpression(componentFilter, componentType string) string {
     if componentFilter != "" {
-        componentName := getComponentNameFromPath(componentFilter)
-        return fmt.Sprintf(".components.%s.%s", componentType, componentName)
+        // Keep full path and quote it for nested components
+        return fmt.Sprintf(".components.%s.\"%s\".%s", componentType, componentFilter, KeySettings)
     }
-    return "select(.settings // .terraform.settings // .components.terraform.*.settings)"
+    // Generic fallback that works for both component types
+    return fmt.Sprintf("select(.settings // .%s.settings // .components.%s.*.%s)", 
+                     componentType, componentType, KeySettings)
 }

315-321: ⚠️ Potential issue

Metadata expression needs the same fix for nested paths.

The metadata expression has the same issue as the settings expression - it doesn't preserve nested paths.

 func buildMetadataExpression(componentFilter, componentType string) string {
     if componentFilter != "" {
-        componentName := getComponentNameFromPath(componentFilter)
-        return fmt.Sprintf(".components.%s.%s", componentType, componentName)
+        // Keep full path and quote it for nested components
+        return fmt.Sprintf(".components.%s.\"%s\".%s", componentType, componentFilter, KeyMetadata)
     }
     return DotChar + KeyMetadata
 }
🧹 Nitpick comments (1)
pkg/list/list_values.go (1)

200-209: Pass QueryParams by pointer to fix static analysis warning.

The QueryParams struct helps organize parameters nicely, but the static analyzer is flagging that it's large (96 bytes) when passed by value.

-func executeQueryForStack(params QueryParams) (interface{}, error) {
+func executeQueryForStack(params *QueryParams) (interface{}, error) {

Also update the call site:

-value, err := executeQueryForStack(params)
+value, err := executeQueryForStack(&params)

Also applies to: 211-244

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c58b6ff and 2525b3f.

📒 Files selected for processing (1)
  • pkg/list/list_values.go (6 hunks)
🧰 Additional context used
🪛 GitHub Check: golangci-lint
pkg/list/list_values.go

[failure] 211-211:
hugeParam: params is heavy (96 bytes); consider passing it by pointer

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Summary
🔇 Additional comments (8)
pkg/list/list_values.go (8)

19-23: Good use of predefined error variables.

Using predefined error variables with errors.New() improves error handling consistency and allows for better error checking throughout the codebase.


30-31: Appropriate addition of helmfile constant.

Adding the KeyHelmfile constant properly supports the new helmfile component type alongside terraform, enabling consistent handling of both component types.


126-148: Good refactoring of component extraction logic.

The extraction logic has been nicely modularized from a likely monolithic function into smaller, specialized functions with clear responsibilities. This makes the code more maintainable and easier to understand.


159-184: Appropriate component type detection and error handling.

The new function properly extracts component values from a single stack with improved component type detection and graceful error handling by returning nil on errors rather than failing the entire operation.


259-289: Good component type detection implementation.

The new functions properly check for components in both terraform and helmfile sections, returning appropriate errors when components aren't found. This enables proper handling of nested components across different component types.


291-294: Properly handling nested component paths for filtered components.

The direct use of the full component path in quotes ensures that nested components like apis/weather work correctly with YQ expressions.


342-358: Clean extraction logic for settings data.

The function properly handles special case extraction for settings data, reducing duplication and improving maintainability.


567-572: Simplified result formatting logic.

The function has been refactored to remove unnecessary parameters and simplify the logic, which makes it more maintainable.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
pkg/list/list_values.go (3)

186-198: Default component type should be reconsidered.

When the target component is empty, the function defaults to Terraform. Now that we support multiple component types, we should revisit this default.

Consider returning an empty string or detecting the component type dynamically:

func detectComponentTypeInStack(stackMap map[string]interface{}, targetComponent, stackName string) string {
	if targetComponent == "" {
-		return KeyTerraform
+		// Either return empty string or detect based on what's available in the stack
+		return detectDefaultComponentType(stackMap)
	}
	...
}

+// detectDefaultComponentType determines which component type to use as default
+// based on what sections are available in the stack
+func detectDefaultComponentType(stackMap map[string]interface{}) string {
+	components, ok := stackMap[KeyComponents].(map[string]interface{})
+	if !ok {
+		return KeyTerraform // Fallback to Terraform for backward compatibility
+	}
+	
+	// Check if either section exists and has components
+	_, hasTerraform := components[KeyTerraform].(map[string]interface{})
+	_, hasHelmfile := components[KeyHelmfile].(map[string]interface{})
+	
+	if hasTerraform {
+		return KeyTerraform
+	} else if hasHelmfile {
+		return KeyHelmfile
+	}
+	
+	return KeyTerraform // Default for backward compatibility
+}

259-280: Consider using errors.Wrap instead of fmt.Errorf.

Using errors.Wrap would be more consistent with other error handling in the codebase.

- return "", fmt.Errorf("%w: %s", ErrComponentNotFoundInSections, targetComponentName)
+ return "", errors.Wrapf(ErrComponentNotFoundInSections, "%s", targetComponentName)

283-289: Consider adding logging to isComponentInSection.

Adding debug logging would help with troubleshooting nested component issues.

func isComponentInSection(components map[string]interface{}, sectionKey, componentName string) bool {
	section, ok := components[sectionKey].(map[string]interface{})
	if !ok {
+		log.Debug("Section not found", "section", sectionKey)
		return false
	}
	_, exists := section[componentName]
+	log.Debug("Component check", "section", sectionKey, "component", componentName, "exists", exists)
	return exists
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2525b3f and ce5a47a.

📒 Files selected for processing (1)
  • pkg/list/list_values.go (6 hunks)
🧰 Additional context used
🪛 GitHub Check: golangci-lint
pkg/list/list_values.go

[failure] 211-211:
hugeParam: params is heavy (96 bytes); consider passing it by pointer

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Summary
🔇 Additional comments (6)
pkg/list/list_values.go (6)

20-23: Good addition of structured error variables.

Adding dedicated error variables makes the code more maintainable and helps with error handling consistency.


30-31: Excellent addition of Helmfile support.

Adding the KeyHelmfile constant enables handling both Terraform and Helmfile components, which is essential for nested component support.


126-148: Clean refactoring of extraction logic.

Breaking down the component extraction into smaller, focused functions improves readability and maintainability.


201-209: Good struct creation to simplify function signatures.

Creating a QueryParams struct is cleaner than passing many individual parameters.


269-277: Good addition of component type detection.

Now properly checks for components in both terraform and helmfile sections.


329-338: Good implementation of nested component support.

Properly quoting the component path prevents issues with nested paths containing slashes.

coderabbitai[bot]
coderabbitai bot previously approved these changes May 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
patch A minor, backward compatible change size/m
Projects
None yet
Development

Successfully merging this pull request may close these issues.

atmos list vars and atmos list values does not work on nested components
2 participants