diff --git a/README.md b/README.md index af08021..f8d8f60 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,38 @@ curl -X GET "http://localhost:8080/analyze?namespace=k8sgpt&explain=false" ## Additional AI providers +### Setting a new default AI provider + +
+ +There may be scenarios where you wish to have K8sGPT plugged into several default AI providers. In this case you may wish to use one as a new default, other than OpenAI which is the project default. + +_To view available providers_ + +``` +k8sgpt auth list +Default: +> openai +Active: +> openai +> azureopenai +Unused: +> localai +> noopai + +``` + +_To set a new default provider_ + +``` +k8sgpt auth default -p azureopenai +Default provider set to azureopenai +``` + + +
+ + ### Azure OpenAI Prerequisites: an Azure OpenAI deployment is needed, please visit MS official [documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource) to create your own. @@ -286,7 +318,7 @@ To authenticate with k8sgpt, you will need the Azure OpenAI endpoint of your ten ### Run k8sgpt To run k8sgpt, run `k8sgpt auth` with the `azureopenai` backend: ``` -k8sgpt auth --backend azureopenai --baseurl https:// --engine --model +k8sgpt auth new --backend azureopenai --baseurl https:// --engine --model ``` Lastly, enter your Azure API key, after the prompt. diff --git a/cmd/auth/auth.go b/cmd/auth/auth.go index 914139a..45727de 100644 --- a/cmd/auth/auth.go +++ b/cmd/auth/auth.go @@ -48,4 +48,6 @@ func init() { AuthCmd.AddCommand(newCmd) // add subcommand to remove new backend provider AuthCmd.AddCommand(removeCmd) + // add subcommand to set default backend provider + AuthCmd.AddCommand(defaultCmd) } diff --git a/cmd/auth/default.go b/cmd/auth/default.go new file mode 100644 index 0000000..8b58293 --- /dev/null +++ b/cmd/auth/default.go @@ -0,0 +1,79 @@ +/* +Copyright 2023 The K8sGPT Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package auth + +import ( + "os" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + providerName string +) + +var defaultCmd = &cobra.Command{ + Use: "default", + Short: "Set your default AI backend provider", + Long: "The command to set your new default AI backend provider (default is openai)", + Run: func(cmd *cobra.Command, args []string) { + err := viper.UnmarshalKey("ai", &configAI) + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + if providerName == "" { + if configAI.DefaultProvider != "" { + color.Yellow("Your default provider is %s", configAI.DefaultProvider) + } else { + color.Yellow("Your default provider is openai") + } + os.Exit(0) + } + // lowercase the provider name + providerName = strings.ToLower(providerName) + + // Check if the provider is in the provider list + providerExists := false + for _, provider := range configAI.Providers { + if provider.Name == providerName { + providerExists = true + } + } + if !providerExists { + color.Red("Error: Provider %s does not exist", providerName) + os.Exit(1) + } + // Set the default provider + configAI.DefaultProvider = providerName + + viper.Set("ai", configAI) + // Viper write config + err = viper.WriteConfig() + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + // Print acknowledgement + color.Green("Default provider set to %s", providerName) + }, +} + +func init() { + // provider name flag + defaultCmd.Flags().StringVarP(&providerName, "provider", "p", "", "The name of the provider to set as default") +} diff --git a/cmd/auth/list.go b/cmd/auth/list.go index 41b7ecd..09a75e3 100644 --- a/cmd/auth/list.go +++ b/cmd/auth/list.go @@ -18,6 +18,7 @@ import ( "os" "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -35,12 +36,37 @@ var listCmd = &cobra.Command{ os.Exit(1) } - // iterate over the provider list and prints each provider name - for _, provider := range configAI.Providers { - if len(configAI.Providers) == 0 { - color.Red("Provider list is currently empty.") - } else { - fmt.Printf("> %s\n", provider.Name) + // Print the default if it is set + fmt.Print(color.YellowString("Default: \n")) + if configAI.DefaultProvider != "" { + fmt.Printf("> %s\n", color.BlueString(configAI.DefaultProvider)) + } else { + fmt.Printf("> %s\n", color.BlueString("openai")) + } + + // Get list of all AI Backends and only print htem if they are not in the provider list + fmt.Print(color.YellowString("Active: \n")) + for _, aiBackend := range ai.Backends { + providerExists := false + for _, provider := range configAI.Providers { + if provider.Name == aiBackend { + providerExists = true + } + } + if providerExists { + fmt.Printf("> %s\n", color.GreenString(aiBackend)) + } + } + fmt.Print(color.YellowString("Unused: \n")) + for _, aiBackend := range ai.Backends { + providerExists := false + for _, provider := range configAI.Providers { + if provider.Name == aiBackend { + providerExists = true + } + } + if !providerExists { + fmt.Printf("> %s\n", color.RedString(aiBackend)) } } }, diff --git a/pkg/ai/iai.go b/pkg/ai/iai.go index 767e1e5..bd4dd06 100644 --- a/pkg/ai/iai.go +++ b/pkg/ai/iai.go @@ -59,7 +59,8 @@ func NewClient(provider string) IAI { } type AIConfiguration struct { - Providers []AIProvider `mapstructure:"providers"` + Providers []AIProvider `mapstructure:"providers"` + DefaultProvider string `mapstructure:"defaultprovider"` } type AIProvider struct { diff --git a/pkg/analysis/analysis.go b/pkg/analysis/analysis.go index b9f8ca5..1d82f1d 100644 --- a/pkg/analysis/analysis.go +++ b/pkg/analysis/analysis.go @@ -34,16 +34,17 @@ import ( ) type Analysis struct { - Context context.Context - Filters []string - Client *kubernetes.Client - AIClient ai.IAI - Results []common.Result - Errors []string - Namespace string - Cache cache.ICache - Explain bool - MaxConcurrency int + Context context.Context + Filters []string + Client *kubernetes.Client + AIClient ai.IAI + Results []common.Result + Errors []string + Namespace string + Cache cache.ICache + Explain bool + MaxConcurrency int + AnalysisAIProvider string // The name of the AI Provider used for this analysis } type AnalysisStatus string @@ -55,6 +56,7 @@ const ( ) type JsonOutput struct { + Provider string `json:"provider"` Errors AnalysisErrors `json:"errors"` Status AnalysisStatus `json:"status"` Problems int `json:"problems"` @@ -74,6 +76,12 @@ func NewAnalysis(backend string, language string, filters []string, namespace st os.Exit(1) } + // Backend string will have high priority than a default provider + // Backend as "openai" represents the default CLI argument passed through + if configAI.DefaultProvider != "" && backend == "openai" { + backend = configAI.DefaultProvider + } + var aiProvider ai.AIProvider for _, provider := range configAI.Providers { if backend == provider.Name { @@ -105,14 +113,15 @@ func NewAnalysis(backend string, language string, filters []string, namespace st } return &Analysis{ - Context: ctx, - Filters: filters, - Client: client, - AIClient: aiClient, - Namespace: namespace, - Cache: cache.New(noCache), - Explain: explain, - MaxConcurrency: maxConcurrency, + Context: ctx, + Filters: filters, + Client: client, + AIClient: aiClient, + Namespace: namespace, + Cache: cache.New(noCache), + Explain: explain, + MaxConcurrency: maxConcurrency, + AnalysisAIProvider: backend, }, nil } diff --git a/pkg/analysis/output.go b/pkg/analysis/output.go index e445966..bfbc663 100644 --- a/pkg/analysis/output.go +++ b/pkg/analysis/output.go @@ -42,6 +42,7 @@ func (a *Analysis) jsonOutput() ([]byte, error) { } result := JsonOutput{ + Provider: a.AnalysisAIProvider, Problems: problems, Results: a.Results, Errors: a.Errors, @@ -56,6 +57,10 @@ func (a *Analysis) jsonOutput() ([]byte, error) { func (a *Analysis) textOutput() ([]byte, error) { var output strings.Builder + + // Print the AI provider used for this analysis + output.WriteString(fmt.Sprintf("AI Provider: %s\n", color.YellowString(a.AnalysisAIProvider))) + if len(a.Errors) != 0 { output.WriteString("\n") output.WriteString(color.YellowString("Warnings : \n"))