diff --git a/README.md b/README.md index de9c76350f..4007263626 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t * Currently the default AI provider is OpenAI, you will need to generate an API key from [OpenAI](https://openai.com) * You can do this by running `k8sgpt generate` to open a browser link to generate it * Run `k8sgpt auth` to set it in k8sgpt. -* Run `k8sgpt filters` to manage filters. +* Run `k8sgpt filters` to manage the active filters used by the analyzer. By default, all filters are executed during analysis. * Run `k8sgpt analyze` to run a scan. * And use `k8sgpt analyze --explain` to get a more detailed explanation of the issues. @@ -92,18 +92,42 @@ Flags: Use "k8sgpt [command] --help" for more information about a command. ``` -_Run a scan with the default analyzers_ +_Manage filters_ + +_List filters_ ``` -k8sgpt generate -k8sgpt auth -k8sgpt analyze --explain +k8sgpt filters list ``` -_List filters_ +_Add default filters_ ``` -k8sgpt filters list +k8sgpt filters add [filter(s)] +``` + +### Examples : + +- Simple filter : `k8sgpt filters add Service` +- Multiple filters : `k8sgpt filters add Ingress,Pod` + +_Add default filters_ + +``` +k8sgpt filters remove [filter(s)] +``` + +### Examples : + +- Simple filter : `k8sgpt filters remove Service` +- Multiple filters : `k8sgpt filters remove Ingress,Pod` + +_Run a scan with the default analyzers_ + +``` +k8sgpt generate +k8sgpt auth +k8sgpt analyze --explain ``` _Filter on resource_ diff --git a/cmd/filters/add.go b/cmd/filters/add.go new file mode 100644 index 0000000000..2387fa95a4 --- /dev/null +++ b/cmd/filters/add.go @@ -0,0 +1,71 @@ +package filters + +import ( + "os" + "strings" + + "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var addCmd = &cobra.Command{ + Use: "add [filter(s)]", + Short: "Adds one or more new filters.", + Long: `The add command adds one or more new filters to the default set of filters used by the analyze.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + inputFilters := strings.Split(args[0], ",") + availableFilters := analyzer.ListFilters() + + // Verify filter exist + invalidFilters := []string{} + for _, f := range inputFilters { + if f == "" { + color.Red("Filter cannot be empty. Please use correct syntax.") + os.Exit(1) + } + foundFilter := false + for _, filter := range availableFilters { + if filter == f { + foundFilter = true + break + } + } + if !foundFilter { + invalidFilters = append(invalidFilters, f) + } + } + + if len(invalidFilters) != 0 { + color.Red("Filter %s does not exist. Please use k8sgpt filters list", strings.Join(invalidFilters, ", ")) + os.Exit(1) + } + + // Get defined active_filters + activeFilters := viper.GetStringSlice("active_filters") + if len(activeFilters) == 0 { + activeFilters = availableFilters + } + + mergedFilters := append(activeFilters, inputFilters...) + + uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(mergedFilters) + + // Verify dupplicate + if len(dupplicatedFilters) != 0 { + color.Red("Duplicate filters found: %s", strings.Join(dupplicatedFilters, ", ")) + os.Exit(1) + } + + viper.Set("active_filters", uniqueFilters) + + if err := viper.WriteConfig(); err != nil { + color.Red("Error writing config file: %s", err.Error()) + os.Exit(1) + } + color.Green("Filter %s added", strings.Join(inputFilters, ", ")) + }, +} diff --git a/cmd/filters/filters.go b/cmd/filters/filters.go index f2146ffff4..eb555240be 100644 --- a/cmd/filters/filters.go +++ b/cmd/filters/filters.go @@ -19,5 +19,7 @@ var FiltersCmd = &cobra.Command{ } func init() { - FiltersCmd.AddCommand(filterListCmd) + FiltersCmd.AddCommand(listCmd) + FiltersCmd.AddCommand(addCmd) + FiltersCmd.AddCommand(removeCmd) } diff --git a/cmd/filters/list.go b/cmd/filters/list.go index 3f230b522c..349ad8beba 100644 --- a/cmd/filters/list.go +++ b/cmd/filters/list.go @@ -5,17 +5,35 @@ import ( "github.com/fatih/color" "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/spf13/cobra" + "github.com/spf13/viper" ) -var filterListCmd = &cobra.Command{ +var listCmd = &cobra.Command{ Use: "list", Short: "List available filters", Long: `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`, Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Available filters : \n") - for _, analyzer := range analyzer.ListFilters() { - fmt.Printf("> %s\n", color.GreenString(analyzer)) + activeFilters := viper.GetStringSlice("active_filters") + availableFilters := analyzer.ListFilters() + + if len(activeFilters) == 0 { + activeFilters = availableFilters + } + + inactiveFilters := util.SliceDiff(availableFilters, activeFilters) + fmt.Printf(color.YellowString("Active: \n")) + for _, filter := range activeFilters { + fmt.Printf("> %s\n", color.GreenString(filter)) } + // display inactive filters + if len(inactiveFilters) != 0 { + fmt.Printf(color.YellowString("Unused: \n")) + for _, filter := range inactiveFilters { + fmt.Printf("> %s\n", color.RedString(filter)) + } + } + }, } diff --git a/cmd/filters/remove.go b/cmd/filters/remove.go new file mode 100644 index 0000000000..dab99a1e6e --- /dev/null +++ b/cmd/filters/remove.go @@ -0,0 +1,72 @@ +package filters + +import ( + "os" + "strings" + + "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var removeCmd = &cobra.Command{ + Use: "remove [filter(s)]", + Short: "Remove one or more filters.", + Long: `The add command remove one or more filters to the default set of filters used by the analyze.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + inputFilters := strings.Split(args[0], ",") + + // Get defined active_filters + activeFilters := viper.GetStringSlice("active_filters") + if len(activeFilters) == 0 { + activeFilters = analyzer.ListFilters() + } + + // Check if input input filters is not empty + for _, f := range inputFilters { + if f == "" { + color.Red("Filter cannot be empty. Please use correct syntax.") + os.Exit(1) + } + } + + // verify dupplicate filters example: k8sgpt filters remove Pod Pod + uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(inputFilters) + if len(dupplicatedFilters) != 0 { + color.Red("Duplicate filters found: %s", strings.Join(dupplicatedFilters, ", ")) + os.Exit(1) + } + + // Verify if filter exist in config file and update default_filter + filterNotFound := []string{} + for _, filter := range uniqueFilters { + foundFilter := false + for i, f := range activeFilters { + if f == filter { + foundFilter = true + activeFilters = append(activeFilters[:i], activeFilters[i+1:]...) + break + } + } + if !foundFilter { + filterNotFound = append(filterNotFound, filter) + } + } + + if len(filterNotFound) != 0 { + color.Red("Filter(s) %s does not exist in configuration file. Please use k8sgpt filters add.", strings.Join(filterNotFound, ", ")) + os.Exit(1) + } + + viper.Set("active_filters", activeFilters) + + if err := viper.WriteConfig(); err != nil { + color.Red("Error writing config file: %s", err.Error()) + os.Exit(1) + } + color.Green("Filter(s) %s removed", strings.Join(inputFilters, ", ")) + }, +} diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index e5a7d954d9..72bc8ea786 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -24,8 +24,10 @@ func RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfigur client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error { - // if there are no filters selected then run all of them - if len(filters) == 0 { + activeFilters := viper.GetStringSlice("active_filters") + + // if there are no filters selected and no active_filters then run all of them + if len(filters) == 0 && len(activeFilters) == 0 { for _, analyzer := range analyzerMap { if err := analyzer(ctx, config, client, aiClient, analysisResults); err != nil { return err @@ -34,7 +36,20 @@ func RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfigur return nil } - for _, filter := range filters { + // if the filters flag is specified + if len(filters) != 0 { + for _, filter := range filters { + if analyzer, ok := analyzerMap[filter]; ok { + if err := analyzer(ctx, config, client, aiClient, analysisResults); err != nil { + return err + } + } + } + return nil + } + + // use active_filters + for _, filter := range activeFilters { if analyzer, ok := analyzerMap[filter]; ok { if err := analyzer(ctx, config, client, aiClient, analysisResults); err != nil { return err diff --git a/pkg/util/util.go b/pkg/util/util.go index 598a8c73c3..78e3cf3774 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -65,3 +65,34 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool) } return meta.Name, false } + +func RemoveDuplicates(slice []string) ([]string, []string) { + set := make(map[string]bool) + duplicates := []string{} + for _, val := range slice { + if _, ok := set[val]; !ok { + set[val] = true + } else { + duplicates = append(duplicates, val) + } + } + uniqueSlice := make([]string, 0, len(set)) + for val := range set { + uniqueSlice = append(uniqueSlice, val) + } + return uniqueSlice, duplicates +} + +func SliceDiff(source, dest []string) []string { + mb := make(map[string]struct{}, len(dest)) + for _, x := range dest { + mb[x] = struct{}{} + } + var diff []string + for _, x := range source { + if _, found := mb[x]; !found { + diff = append(diff, x) + } + } + return diff +}