Skip to content
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

Filter out empty results from describe stacks #764

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions cmd/describe_stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ func init() {

describeStacksCmd.PersistentFlags().Bool("process-templates", true, "Enable/disable Go template processing in Atmos stack manifests when executing the command: atmos describe stacks --process-templates=false")

describeStacksCmd.PersistentFlags().Bool("include-empty-stacks", false, "Include stacks with no components in the output: atmos describe stacks --include-empty-stacks")

describeCmd.AddCommand(describeStacksCmd)
}
2 changes: 1 addition & 1 deletion internal/exec/atmos.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func ExecuteAtmosCmd() error {

// Get a map of stacks and components in the stacks
// Don't process `Go` templates in Atmos stack manifests since we just need to display the stack and component names in the TUI
stacksMap, err := ExecuteDescribeStacks(cliConfig, "", nil, nil, nil, false, false)
stacksMap, err := ExecuteDescribeStacks(cliConfig, "", nil, nil, nil, false, false, false)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/exec/describe_affected_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ func executeDescribeAffected(
u.LogTrace(cliConfig, fmt.Sprintf("Current HEAD: %s", localRepoHead))
u.LogTrace(cliConfig, fmt.Sprintf("BASE: %s", remoteRepoHead))

currentStacks, err := ExecuteDescribeStacks(cliConfig, stack, nil, nil, nil, false, true)
currentStacks, err := ExecuteDescribeStacks(cliConfig, stack, nil, nil, nil, false, true, false)
if err != nil {
return nil, nil, nil, err
}
Expand Down Expand Up @@ -444,7 +444,7 @@ func executeDescribeAffected(
return nil, nil, nil, err
}

remoteStacks, err := ExecuteDescribeStacks(cliConfig, stack, nil, nil, nil, true, true)
remoteStacks, err := ExecuteDescribeStacks(cliConfig, stack, nil, nil, nil, true, true, false)
if err != nil {
return nil, nil, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/exec/describe_dependents.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func ExecuteDescribeDependents(
var ok bool

// Get all stacks with all components
stacks, err := ExecuteDescribeStacks(cliConfig, "", nil, nil, nil, false, true)
stacks, err := ExecuteDescribeStacks(cliConfig, "", nil, nil, nil, false, true, false)
if err != nil {
return nil, err
}
Expand Down
109 changes: 103 additions & 6 deletions internal/exec/describe_stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ func ExecuteDescribeStacksCmd(cmd *cobra.Command, args []string) error {
return err
}

includeEmptyStacks, err := cmd.Flags().GetBool("include-empty-stacks")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
includeEmptyStacks, err := cmd.Flags().GetBool("include-empty-stacks")
includeEmptyStacks, err := flags.GetBool("include-empty-stacks")

if err != nil {
return err
}

componentsCsv, err := flags.GetString("components")
if err != nil {
return err
Expand Down Expand Up @@ -97,6 +102,7 @@ func ExecuteDescribeStacksCmd(cmd *cobra.Command, args []string) error {
sections,
false,
processTemplates,
includeEmptyStacks,
)
if err != nil {
return err
Expand All @@ -119,6 +125,7 @@ func ExecuteDescribeStacks(
sections []string,
ignoreMissingFiles bool,
processTemplates bool,
includeEmptyStacks bool,
) (map[string]any, error) {

stacksMap, _, err := FindStacksMap(cliConfig, ignoreMissingFiles)
Expand All @@ -127,6 +134,7 @@ func ExecuteDescribeStacks(
}

finalStacksMap := make(map[string]any)
processedStacks := make(map[string]bool)
var varsSection map[string]any
var metadataSection map[string]any
var settingsSection map[string]any
Expand All @@ -136,12 +144,48 @@ func ExecuteDescribeStacks(
var backendSection map[string]any
var backendTypeSection string
var stackName string
context := schema.Context{}

for stackFileName, stackSection := range stacksMap {
var context schema.Context

// Delete the stack-wide imports
delete(stackSection.(map[string]any), "imports")

// Check if components section exists and has explicit components
hasExplicitComponents := false
if componentsSection, ok := stackSection.(map[string]any)["components"]; ok {
if componentsSection != nil {
if terraformSection, ok := componentsSection.(map[string]any)["terraform"].(map[string]any); ok {
hasExplicitComponents = len(terraformSection) > 0
}
if helmfileSection, ok := componentsSection.(map[string]any)["helmfile"].(map[string]any); ok {
hasExplicitComponents = hasExplicitComponents || len(helmfileSection) > 0
}
}
}

// Also check for imports
hasImports := false
if importsSection, ok := stackSection.(map[string]any)["import"].([]any); ok {
hasImports = len(importsSection) > 0
}

// Skip stacks without components or imports when includeEmptyStacks is false
if !includeEmptyStacks && !hasExplicitComponents && !hasImports {
continue
}

stackName = stackFileName
if processedStacks[stackName] {
continue
}
processedStacks[stackName] = true

if !u.MapKeyExists(finalStacksMap, stackName) {
finalStacksMap[stackName] = make(map[string]any)
finalStacksMap[stackName].(map[string]any)["components"] = make(map[string]any)
}

if componentsSection, ok := stackSection.(map[string]any)["components"].(map[string]any); ok {

if len(componentTypes) == 0 || u.SliceContainsString(componentTypes, "terraform") {
Expand Down Expand Up @@ -242,9 +286,13 @@ func ExecuteDescribeStacks(

if stackName == "" {
stackName = stackFileName
} else if strings.HasPrefix(stackFileName, "deploy/") {
Copy link
Member

Choose a reason for hiding this comment

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

@Cerebrovinny why do we need a special treatment for the deploy/ folder?
The deploy/ was just an example in the examples folder, it should not be a special name, and should not be hardcoded in Atmos code

// If we have a deploy/ prefixed version, use that as the canonical name
stackName = stackFileName
}

if !u.MapKeyExists(finalStacksMap, stackName) {
// Only create the stack entry if it doesn't exist or if we're using the canonical name
if !u.MapKeyExists(finalStacksMap, stackName) || strings.HasPrefix(stackName, "deploy/") {
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
finalStacksMap[stackName] = make(map[string]any)
}

Expand Down Expand Up @@ -430,9 +478,13 @@ func ExecuteDescribeStacks(

if stackName == "" {
stackName = stackFileName
} else if strings.HasPrefix(stackFileName, "deploy/") {
// If we have a deploy/ prefixed version, use that as the canonical name
stackName = stackFileName
}

if !u.MapKeyExists(finalStacksMap, stackName) {
// Only create the stack entry if it doesn't exist or if we're using the canonical name
if !u.MapKeyExists(finalStacksMap, stackName) || strings.HasPrefix(stackName, "deploy/") {
finalStacksMap[stackName] = make(map[string]any)
}

Expand Down Expand Up @@ -511,13 +563,58 @@ func ExecuteDescribeStacks(
}
}
}
}

// Filter out empty stacks after processing all stack files
if !includeEmptyStacks {
for stackName := range finalStacksMap {
if stackName == "" {
delete(finalStacksMap, stackName)
continue
}

// Filter out empty stacks (stacks without any components)
if st, ok := finalStacksMap[stackName].(map[string]any); ok {
if len(st) == 0 {
stackEntry := finalStacksMap[stackName].(map[string]any)
componentsSection, hasComponents := stackEntry["components"].(map[string]any)

Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
if !hasComponents {
delete(finalStacksMap, stackName)
continue
}

// Check if any component type (terraform/helmfile) has components
hasNonEmptyComponents := false
for _, components := range componentsSection {
if compTypeMap, ok := components.(map[string]any); ok {
for _, comp := range compTypeMap {
if compContent, ok := comp.(map[string]any); ok {
// Check for any meaningful content
relevantSections := []string{"vars", "metadata", "settings", "env"}
for _, section := range relevantSections {
if _, hasSection := compContent[section]; hasSection {
hasNonEmptyComponents = true
break
}
}
}
}
}
if hasNonEmptyComponents {
break
}
}

if !hasNonEmptyComponents {
delete(finalStacksMap, stackName)
continue
}

// Check for duplicate stacks (deploy/ prefix)
if strings.HasPrefix(stackName, "deploy/") {
baseStackName := strings.TrimPrefix(stackName, "deploy/")
delete(finalStacksMap, baseStackName)
}
}
} else {
}

return finalStacksMap, nil
Expand Down
Loading