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

improve atmos list components view #828

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cmd/list_components.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package cmd
import (
"fmt"

"github.com/fatih/color"
"github.com/spf13/cobra"

e "github.com/cloudposse/atmos/internal/exec"
"github.com/cloudposse/atmos/pkg/config"
l "github.com/cloudposse/atmos/pkg/list"
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
"github.com/fatih/color"
"github.com/spf13/cobra"
)

// listComponentsCmd lists atmos components
Expand Down Expand Up @@ -38,7 +39,7 @@ var listComponentsCmd = &cobra.Command{
return
}

output, err := l.FilterAndListComponents(stackFlag, stacksMap)
output, err := l.FilterAndListComponents(stackFlag, stacksMap, cliConfig.Components.List)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error: %v"+"\n", err), color.New(color.FgYellow))
return
Expand Down
8 changes: 8 additions & 0 deletions examples/quick-start-advanced/atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
base_path: "."

components:
list:
columns:
- name: Component
value: '{{ .atmos_component }}'
- name: Stack
value: '{{ .atmos_stack }}'
- name: Folder
value: '{{ .vars.tenant }}'
terraform:
# Optional `command` specifies the executable to be called by `atmos` when running Terraform commands
# If not defined, `terraform` is used
Expand Down
89 changes: 83 additions & 6 deletions pkg/list/list_components.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package list

import (
"fmt"
"os"
"regexp"
"sort"
"strings"
"text/tabwriter"

"github.com/samber/lo"

"github.com/cloudposse/atmos/pkg/schema"
)

// getStackComponents extracts Terraform components from the final map of stacks
func getStackComponents(stackData any) ([]string, error) {
func getStackComponents(stackData any, listFields []string) ([]string, error) {
stackMap, ok := stackData.(map[string]any)
if !ok {
return nil, fmt.Errorf("could not parse stacks")
Expand All @@ -25,17 +30,84 @@ func getStackComponents(stackData any) ([]string, error) {
return nil, fmt.Errorf("could not parse Terraform components")
}

return lo.Keys(terraformComponents), nil
uniqueKeys := lo.Keys(terraformComponents)
result := make([]string, 0)

for _, dataKey := range uniqueKeys {
data := terraformComponents[dataKey]
dataMap, ok := data.(map[string]any)
if !ok {
return nil, fmt.Errorf("unexpected data type for component '%s'", dataKey)
}
rowData := make([]string, 0)
for _, key := range listFields {
value, found := resolveKey(dataMap, key)
if !found {
value = "-"
}
rowData = append(rowData, fmt.Sprintf("%s", value))
}
result = append(result, strings.Join(rowData, "\t\t"))
}
return result, nil
}

// resolveKey resolves a key from a map, supporting nested keys with dot notation
func resolveKey(data map[string]any, key string) (any, bool) {
// Remove leading dot from the key (e.g., `.vars.tenant` -> `vars.tenant`)
key = strings.TrimPrefix(key, ".")

// Split key on `.`
parts := strings.Split(key, ".")
current := data

// Traverse the map for each part
for i, part := range parts {
if i == len(parts)-1 {
// Return the value for the last part
if value, exists := current[part]; exists {
return value, true
}
return nil, false
}

// Traverse deeper
if nestedMap, ok := current[part].(map[string]any); ok {
current = nestedMap
} else {
return nil, false
}
}

return nil, false
}

// FilterAndListComponents filters and lists components based on the given stack
func FilterAndListComponents(stackFlag string, stacksMap map[string]any) (string, error) {
func FilterAndListComponents(stackFlag string, stacksMap map[string]any, listConfig schema.ListConfig) (string, error) {
components := []string{}

writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.AlignRight)
header := make([]string, 0)
listFields := make([]string, 0)

re := regexp.MustCompile(`\{\{\s*(.*?)\s*\}\}`)

for _, v := range listConfig.Columns {
header = append(header, v.Name)
match := re.FindStringSubmatch(v.Value)

if len(match) > 1 {
listFields = append(listFields, match[1])
} else {
return "", fmt.Errorf("invalid value format for column name %s", v.Name)
}
}
fmt.Fprintln(writer, strings.Join(header, "\t\t"))

pkbhowmick marked this conversation as resolved.
Show resolved Hide resolved
if stackFlag != "" {
// Filter components for the specified stack
if stackData, ok := stacksMap[stackFlag]; ok {
stackComponents, err := getStackComponents(stackData)
stackComponents, err := getStackComponents(stackData, listFields)
if err != nil {
return "", fmt.Errorf("error processing stack '%s': %w", stackFlag, err)
}
Expand All @@ -46,7 +118,7 @@ func FilterAndListComponents(stackFlag string, stacksMap map[string]any) (string
} else {
// Get all components from all stacks
for _, stackData := range stacksMap {
stackComponents, err := getStackComponents(stackData)
stackComponents, err := getStackComponents(stackData, listFields)
if err != nil {
continue // Skip invalid stacks
}
Expand All @@ -61,5 +133,10 @@ func FilterAndListComponents(stackFlag string, stacksMap map[string]any) (string
if len(components) == 0 {
return "No components found", nil
}
return strings.Join(components, "\n") + "\n", nil

for _, com := range components {
fmt.Fprintln(writer, com)
}
writer.Flush()
return "", nil
}
2 changes: 1 addition & 1 deletion pkg/list/list_components_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestListComponents(t *testing.T) {
nil, false, false, false)
assert.Nil(t, err)

output, err := FilterAndListComponents("", stacksMap)
output, err := FilterAndListComponents("", stacksMap, schema.ListConfig{})
pkbhowmick marked this conversation as resolved.
Show resolved Hide resolved
assert.Nil(t, err)
dependentsYaml, err := u.ConvertToYAML(output)
assert.Nil(t, err)
Expand Down
14 changes: 12 additions & 2 deletions pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,18 @@ type Helmfile struct {
}

type Components struct {
Terraform Terraform `yaml:"terraform" json:"terraform" mapstructure:"terraform"`
Helmfile Helmfile `yaml:"helmfile" json:"helmfile" mapstructure:"helmfile"`
Terraform Terraform `yaml:"terraform" json:"terraform" mapstructure:"terraform"`
Helmfile Helmfile `yaml:"helmfile" json:"helmfile" mapstructure:"helmfile"`
List ListConfig `yaml:"list" json:"list" mapstructure:"list"`
}

type ListConfig struct {
Columns []ListColumnConfig `yaml:"columns" json:"columns" mapstructure:"columns"`
}
pkbhowmick marked this conversation as resolved.
Show resolved Hide resolved

type ListColumnConfig struct {
Name string `yaml:"name" json:"name" mapstructure:"name"`
Value string `yaml:"value" json:"value" mapstructure:"value"`
}

type Stacks struct {
Expand Down
Loading