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

Implement: atmos list values #1036

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2e1e51d
feat: add `list values` command to compare component configurations a…
Cerebrovinny Feb 7, 2025
547f984
feat: add TSV format support for workflow listing
Cerebrovinny Feb 7, 2025
917cdd0
wip
Cerebrovinny Feb 7, 2025
72a8e76
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 7, 2025
b6b19b2
Merge branch 'main' into DEV-2802
Cerebrovinny Feb 13, 2025
30e14d0
update list values
Cerebrovinny Feb 11, 2025
f3db126
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 14, 2025
54d8ad0
fixes and clean up
Cerebrovinny Feb 14, 2025
b03b3c2
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 14, 2025
cc9c8bf
update terminal
Cerebrovinny Feb 14, 2025
9060a00
test list values
Cerebrovinny Feb 14, 2025
79e94f8
Update website/docs/cli/commands/list/list-values.mdx
Cerebrovinny Feb 15, 2025
cfec6fb
Add markdown docs for listing values/vars; update examples
Cerebrovinny Feb 15, 2025
df4c494
Add logger, replace error prints with log calls
Cerebrovinny Feb 15, 2025
872d881
Refactor imports and rename logger variable
Cerebrovinny Feb 15, 2025
368d21d
Handle 'no values found' error; improve logging logic
Cerebrovinny Feb 15, 2025
6a3e02b
Improve error messages with colored output
Cerebrovinny Feb 16, 2025
538d6b7
Add expectedError field to test cases for error validation
Cerebrovinny Feb 16, 2025
216e6cd
Merge branch 'main' into DEV-2802
Cerebrovinny Feb 17, 2025
6d87e2c
Refactor error handling, add custom error type
Cerebrovinny Feb 17, 2025
befa005
Refactor error handling, update TTY check logic
Cerebrovinny Feb 17, 2025
f07cb4a
Refactor commands to list metadata and settings
Cerebrovinny Feb 17, 2025
132dfce
general fixes
Cerebrovinny Feb 17, 2025
52f11fb
Remove unused getMapKeys function
Cerebrovinny Feb 17, 2025
ab3bde4
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 17, 2025
948a3c0
Merge branch 'main' into DEV-2802
Cerebrovinny Feb 18, 2025
1ce3cf4
Merge branch 'main' into DEV-2802
Cerebrovinny Feb 18, 2025
571d382
Merge branch 'main' into DEV-2802
Cerebrovinny Feb 19, 2025
9b72518
Switch to charmbracelet/log, remove custom logger setup
Cerebrovinny Feb 19, 2025
f4779cc
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 19, 2025
7d92050
Remove examples from command help, update markdown docs
Cerebrovinny Feb 19, 2025
4e758fc
Merge remote-tracking branch 'origin/DEV-2802' into DEV-2802
Cerebrovinny Feb 19, 2025
a2f790c
Merge branch 'main' into DEV-2802
Cerebrovinny Feb 19, 2025
d0e24c3
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 19, 2025
53f83ab
Refactor error logging, simplify path conversion
Cerebrovinny Feb 20, 2025
e0ec172
Merge branch 'main' into DEV-2802
Cerebrovinny Feb 20, 2025
a8af871
Merge branch 'main' into DEV-2802
osterman Feb 20, 2025
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
148 changes: 148 additions & 0 deletions cmd/list_values.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"

e "github.com/cloudposse/atmos/internal/exec"
"github.com/cloudposse/atmos/pkg/config"
list "github.com/cloudposse/atmos/pkg/list"
l "github.com/cloudposse/atmos/pkg/logger"
"github.com/cloudposse/atmos/pkg/schema"
"github.com/cloudposse/atmos/pkg/ui/theme"
u "github.com/cloudposse/atmos/pkg/utils"
)

// listValuesCmd lists component values across stacks
var listValuesCmd = &cobra.Command{
Use: "values [component]",
Short: "List component values across stacks",
Long: "List values for a component across all stacks where it is used",
Example: "atmos list values vpc\n" +
Copy link
Member

Choose a reason for hiding this comment

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

Move to markdown file and embed with properly annotated examples.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

so are we going to stop using it even for cobra cli only?

"atmos list values vpc --query .vars\n" +
"atmos list values vpc --abstract\n" +
"atmos list values vpc --max-columns 5\n" +
"atmos list values vpc --format json\n" +
"atmos list values vpc --format yaml\n" +
"atmos list values vpc --format csv",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// Check Atmos configuration
checkAtmosConfig()

// Initialize logger from CLI config
configAndStacksInfo := schema.ConfigAndStacksInfo{}
atmosConfig, err := config.InitCliConfig(configAndStacksInfo, true)
if err != nil {
fmt.Fprintf(os.Stderr, "Error initializing CLI config: %v\n", err)
return
}

logger, err := l.NewLoggerFromCliConfig(atmosConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Error initializing logger: %v\n", err)
return
}

Copy link
Member

Choose a reason for hiding this comment

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

I don't believe we need to do this everywhere. And if we did, then we should have a function for it, so we don't duplicate this logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

config.InitCliConfig(configAndStacksInfo, true) is used to start config.

however we indeed don't need logger, err := l.NewLoggerFromCliConfig(atmosConfig)

flags := cmd.Flags()

queryFlag, err := flags.GetString("query")
if err != nil {
logger.Error(fmt.Errorf("failed to get query flag: %v", err))
return
}

abstractFlag, err := flags.GetBool("abstract")
if err != nil {
logger.Error(fmt.Errorf("failed to get abstract flag: %v", err))
return
}

maxColumnsFlag, err := flags.GetInt("max-columns")
if err != nil {
logger.Error(fmt.Errorf("failed to get max-columns flag: %v", err))
return
}

formatFlag, err := flags.GetString("format")
if err != nil {
logger.Error(fmt.Errorf("failed to get format flag: %v", err))
return
}

delimiterFlag, err := flags.GetString("delimiter")
if err != nil {
logger.Error(fmt.Errorf("failed to get delimiter flag: %v", err))
return
}

// Set appropriate default delimiter based on format
if formatFlag == list.FormatCSV && delimiterFlag == list.DefaultTSVDelimiter {
delimiterFlag = list.DefaultCSVDelimiter
}

component := args[0]

// Get all stacks
stacksMap, err := e.ExecuteDescribeStacks(atmosConfig, "", nil, nil, nil, false, false, false, false, nil)
if err != nil {
logger.Error(fmt.Errorf("failed to describe stacks: %v", err))
return
}

output, err := list.FilterAndListValues(stacksMap, component, queryFlag, abstractFlag, maxColumnsFlag, formatFlag, delimiterFlag)
if err != nil {
// Check if this is a 'no values found' error
if list.IsNoValuesFoundError(err) {
logger.Error(err)
} else {
logger.Warning(fmt.Sprintf("Failed to filter and list values: %v", err))
}
return
}

logger.Info(output)
},
}

// listVarsCmd is an alias for 'list values --query .vars'
var listVarsCmd = &cobra.Command{
Use: "vars [component]",
Short: "List component vars across stacks (alias for 'list values --query .vars')",
Long: "List vars for a component across all stacks where it is used",
Example: "atmos list vars vpc\n" +
"atmos list vars vpc --abstract\n" +
"atmos list vars vpc --max-columns 5\n" +
"atmos list vars vpc --format json\n" +
"atmos list vars vpc --format yaml\n" +
"atmos list vars vpc --format csv",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// Set the query flag to .vars
if err := cmd.Flags().Set("query", ".vars"); err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error setting query flag: %v", err), theme.Colors.Error)
return
}
// Run the values command
listValuesCmd.Run(cmd, args)
},
}

func init() {
// Flags for both commands
commonFlags := func(cmd *cobra.Command) {
cmd.PersistentFlags().String("query", "", "JMESPath query to filter values")
cmd.PersistentFlags().Bool("abstract", false, "Include abstract components")
cmd.PersistentFlags().Int("max-columns", 10, "Maximum number of columns to display")
cmd.PersistentFlags().String("format", "", "Output format (table, json, yaml, csv, tsv)")
cmd.PersistentFlags().String("delimiter", "\t", "Delimiter for csv/tsv output (default: tab for tsv, comma for csv)")
}

commonFlags(listValuesCmd)
commonFlags(listVarsCmd)

listCmd.AddCommand(listValuesCmd)
listCmd.AddCommand(listVarsCmd)
}
8 changes: 8 additions & 0 deletions cmd/markdown/atmos_list_values_usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
– List component values across stacks

```
$ atmos list values <component> # List all values
$ atmos list values <component> --query .vars # List only variables
$ atmos list values <component> --abstract # Include abstract components
$ atmos list values <component> --format json # Output in JSON format
```
7 changes: 7 additions & 0 deletions cmd/markdown/atmos_list_vars_usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
– List component variables across stacks

```
$ atmos list vars <component> # List all variables
$ atmos list vars <component> --abstract # Include abstract components
$ atmos list vars <component> --format json # Output in JSON format
```
18 changes: 18 additions & 0 deletions pkg/list/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package list

import "fmt"

// ErrNoValuesFound is returned when no values are found for a component
type ErrNoValuesFound struct {
Component string
}

func (e *ErrNoValuesFound) Error() string {
return fmt.Sprintf("no values found for component '%s'", e.Component)
}

// IsNoValuesFoundError checks if an error is a NoValuesFound error
func IsNoValuesFoundError(err error) bool {
_, ok := err.(*ErrNoValuesFound)
return ok
}
Loading
Loading