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

feat: add stack and component autocompletion for commands #820

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
28 changes: 28 additions & 0 deletions cmd/describe_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/spf13/cobra"

e "github.com/cloudposse/atmos/internal/exec"
l "github.com/cloudposse/atmos/pkg/list"
u "github.com/cloudposse/atmos/pkg/utils"
)

Expand All @@ -14,6 +15,18 @@ var describeComponentCmd = &cobra.Command{
Short: "Execute 'describe component' command",
Long: `This command shows configuration for an Atmos component in an Atmos stack: atmos describe component <component> -s <stack>`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

componentList, err := l.FilterAndListComponents(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

return componentList, cobra.ShellCompDirectiveNoFileComp
},
Run: func(cmd *cobra.Command, args []string) {
// Check Atmos configuration
checkAtmosConfig()
Expand All @@ -37,5 +50,20 @@ func init() {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

// Autocompletion for stack flag
describeComponentCmd.RegisterFlagCompletionFunc("stack", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 1 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

stacksList, err := l.FilterAndListStacks(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

return stacksList, cobra.ShellCompDirectiveNoFileComp
},
)

describeCmd.AddCommand(describeComponentCmd)
}
16 changes: 16 additions & 0 deletions cmd/describe_dependents.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
l "github.com/cloudposse/atmos/pkg/list"
"github.com/cloudposse/atmos/pkg/schema"
"github.com/spf13/cobra"

Expand Down Expand Up @@ -38,5 +39,20 @@ func init() {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

// Autocompletion for stack flag
describeDependentsCmd.RegisterFlagCompletionFunc("stack", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

stacksList, err := l.FilterAndListStacks(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}
aknysh marked this conversation as resolved.
Show resolved Hide resolved

return stacksList, cobra.ShellCompDirectiveNoFileComp
},
)

describeCmd.AddCommand(describeDependentsCmd)
}
24 changes: 19 additions & 5 deletions cmd/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"golang.org/x/term"

cfg "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"
)
Expand All @@ -21,11 +22,24 @@ const atmosDocsURL = "https://atmos.tools"

// docsCmd opens the Atmos docs and can display component documentation
var docsCmd = &cobra.Command{
Use: "docs",
Short: "Open the Atmos docs or display component documentation",
Long: `This command opens the Atmos docs or displays the documentation for a specified Atmos component.`,
Example: "atmos docs vpc",
Args: cobra.MaximumNArgs(1),
Use: "docs",
Short: "Open the Atmos docs or display component documentation",
Long: `This command opens the Atmos docs or displays the documentation for a specified Atmos component.`,
Example: "atmos docs vpc",
Args: cobra.MaximumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

componentList, err := l.FilterAndListComponents(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

return componentList, cobra.ShellCompDirectiveNoFileComp
},

FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 1 {
Expand Down
39 changes: 22 additions & 17 deletions cmd/list_components.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package cmd
import (
"fmt"

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"
Expand All @@ -25,30 +23,37 @@ var listComponentsCmd = &cobra.Command{

stackFlag, _ := cmd.Flags().GetString("stack")

configAndStacksInfo := schema.ConfigAndStacksInfo{}
cliConfig, err := config.InitCliConfig(configAndStacksInfo, true)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error initializing CLI config: %v", err), color.New(color.FgRed))
return
}

stacksMap, err := e.ExecuteDescribeStacks(cliConfig, "", nil, nil, nil, false, false, false)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error describing stacks: %v", err), color.New(color.FgRed))
return
}

output, err := l.FilterAndListComponents(stackFlag, stacksMap)
componentList, err := l.FilterAndListComponents(stackFlag)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error: %v"+"\n", err), color.New(color.FgYellow))
return
}

u.PrintMessageInColor(output, color.New(color.FgGreen))
if len(componentList) == 0 {
u.PrintMessageInColor("No components found", color.New(color.FgYellow))
} else {
for _, component := range componentList {
u.PrintMessageInColor(component+"\n", color.New(color.FgGreen))
}
}
},
}

func init() {
listComponentsCmd.PersistentFlags().StringP("stack", "s", "", "Filter components by stack (e.g., atmos list components -s stack1)")
listCmd.AddCommand(listComponentsCmd)
// Autocompletion for stack flag
listComponentsCmd.RegisterFlagCompletionFunc("stack", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

stacksList, err := l.FilterAndListStacks(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}
aknysh marked this conversation as resolved.
Show resolved Hide resolved

return stacksList, cobra.ShellCompDirectiveNoFileComp
},
)
}
38 changes: 22 additions & 16 deletions cmd/list_stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package cmd
import (
"fmt"

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"
Expand All @@ -26,30 +24,38 @@ var listStacksCmd = &cobra.Command{

componentFlag, _ := cmd.Flags().GetString("component")

configAndStacksInfo := schema.ConfigAndStacksInfo{}
cliConfig, err := config.InitCliConfig(configAndStacksInfo, true)
stackList, err := l.FilterAndListStacks(componentFlag)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error initializing CLI config: %v", err), color.New(color.FgRed))
return
}

stacksMap, err := e.ExecuteDescribeStacks(cliConfig, "", nil, nil, nil, false, false, false)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error describing stacks: %v", err), color.New(color.FgRed))
u.PrintMessageInColor(fmt.Sprintf("Error filtering stacks: %v", err), color.New(color.FgRed))
return
}

output, err := l.FilterAndListStacks(stacksMap, componentFlag)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error filtering stacks: %v", err), color.New(color.FgRed))
return
if len(stackList) == 0 {
u.PrintMessageInColor("No stacks found.", color.New(color.FgYellow))
} else {
for _, stack := range stackList {
u.PrintMessageInColor(stack+"\n", color.New(color.FgGreen))
}
}
u.PrintMessageInColor(output, color.New(color.FgGreen))
},
}

func init() {
listStacksCmd.DisableFlagParsing = false
listStacksCmd.PersistentFlags().StringP("component", "c", "", "atmos list stacks -c <component>")
listCmd.AddCommand(listStacksCmd)
// Autocompletion for stack flag
listStacksCmd.RegisterFlagCompletionFunc("component", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

componentList, err := l.FilterAndListComponents(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}
aknysh marked this conversation as resolved.
Show resolved Hide resolved

return componentList, cobra.ShellCompDirectiveNoFileComp
},
)
}
29 changes: 29 additions & 0 deletions cmd/validate_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra"

e "github.com/cloudposse/atmos/internal/exec"
l "github.com/cloudposse/atmos/pkg/list"
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
)
Expand All @@ -19,6 +20,19 @@ var validateComponentCmd = &cobra.Command{
Example: "atmos validate component <component> -s <stack>\n" +
"atmos validate component <component> -s <stack> --schema-path <schema_path> --schema-type <jsonschema|opa>\n" +
"atmos validate component <component> -s <stack> --schema-path <schema_path> --schema-type opa --module-paths catalog",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

componentList, err := l.FilterAndListComponents(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

return componentList, cobra.ShellCompDirectiveNoFileComp
},
Comment on lines +23 to +34
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider creating shared autocompletion utilities.

The autocompletion implementations are identical across multiple command files. Consider creating shared utility functions to:

  1. Reduce code duplication
  2. Ensure consistent validation logic
  3. Centralize error handling

Create a new file cmd/completion_utils.go:

package cmd

import (
    "github.com/spf13/cobra"
    l "github.com/cloudposse/atmos/pkg/list"
    "github.com/cloudposse/atmos/pkg/schema"
    u "github.com/cloudposse/atmos/pkg/utils"
)

func ComponentCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    if len(args) != 0 {
        return nil, cobra.ShellCompDirectiveNoFileComp
    }

    componentList, err := l.FilterAndListComponents(toComplete)
    if err != nil {
        u.LogErrorAndExit(schema.CliConfiguration{}, err)
    }

    return componentList, cobra.ShellCompDirectiveNoFileComp
}

func StackCompletionWithComponent(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    if len(args) != 1 {
        return nil, cobra.ShellCompDirectiveNoFileComp
    }

    stacksList, err := l.FilterAndListStacks(toComplete)
    if err != nil {
        u.LogErrorAndExit(schema.CliConfiguration{}, err)
    }

    return stacksList, cobra.ShellCompDirectiveNoFileComp
}

Also applies to: 65-78


FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
Run: func(cmd *cobra.Command, args []string) {
// Check Atmos configuration
Expand Down Expand Up @@ -48,5 +62,20 @@ func init() {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

// Autocompletion for stack flag
validateComponentCmd.RegisterFlagCompletionFunc("stack", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 1 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

stacksList, err := l.FilterAndListStacks(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

return stacksList, cobra.ShellCompDirectiveNoFileComp
},
)

validateCmd.AddCommand(validateComponentCmd)
}
31 changes: 31 additions & 0 deletions cmd/vendor_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"

e "github.com/cloudposse/atmos/internal/exec"
l "github.com/cloudposse/atmos/pkg/list"
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
)
Expand Down Expand Up @@ -33,5 +34,35 @@ func init() {
vendorPullCmd.PersistentFlags().Bool("dry-run", false, "atmos vendor pull --component <component> --dry-run")
vendorPullCmd.PersistentFlags().String("tags", "", "Only vendor the components that have the specified tags: atmos vendor pull --tags=dev,test")

// Autocompletion for stack flag
vendorPullCmd.RegisterFlagCompletionFunc("stack", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

stacksList, err := l.FilterAndListStacks(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

return stacksList, cobra.ShellCompDirectiveNoFileComp
},
)

// Autocompletion for component flag
vendorPullCmd.RegisterFlagCompletionFunc("component", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

componentList, err := l.FilterAndListComponents(toComplete)
if err != nil {
u.LogErrorAndExit(schema.CliConfiguration{}, err)
}

return componentList, cobra.ShellCompDirectiveNoFileComp
},
)

vendorCmd.AddCommand(vendorPullCmd)
}
Loading
Loading