interfaced out ai clients

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
This commit is contained in:
Alex Jones 2023-03-24 12:01:36 +00:00
parent ca8ce8188a
commit bdb2e739d4
11 changed files with 129 additions and 87 deletions

View File

@ -3,7 +3,7 @@
<img alt="Text changing depending on mode. Light: 'So light!' Dark: 'So dark!'" src="./images/banner-black.png" width="600px;"> <img alt="Text changing depending on mode. Light: 'So light!' Dark: 'So dark!'" src="./images/banner-black.png" width="600px;">
</picture> </picture>
_Try it out now_ _Install it now_
``` ```
brew tap k8sgpt-ai/k8sgpt brew tap k8sgpt-ai/k8sgpt
@ -20,12 +20,11 @@ It has SRE experience codified into it's analyzers and helps to pull out the mos
``` ```
# Ensure KUBECONFIG env is set to an active Kubernetes cluster # Ensure KUBECONFIG env is set to an active Kubernetes cluster
k8sgpt auth key <Your OpenAI key> k8sgpt init
k8sgpt auth
k8sgpt find problems k8sgpt find problems
# for more detail # for more detail
k8s find problems --explain k8s find problems --explain
``` ```
### What about kubectl-ai? ### What about kubectl-ai?

View File

@ -1,21 +1,41 @@
package auth package auth
import ( import (
"fmt"
"os"
"strings"
"syscall"
"github.com/fatih/color"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"
) )
// authCmd represents the auth command // authCmd represents the auth command
var AuthCmd = &cobra.Command{ var AuthCmd = &cobra.Command{
Use: "auth", Use: "auth",
Short: "A brief description of your command", Short: "Authenticate with your chosen backend",
Long: `A longer description that spans multiple lines and likely contains examples Long: `Provide the necessary credentials to authenticate with your chosen backend.`,
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
backendType := viper.GetString("backend_type")
fmt.Printf("Enter %s Key: ", backendType)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backendType,
err.Error())
os.Exit(1)
}
password := strings.TrimSpace(string(bytePassword))
viper.Set(fmt.Sprintf("%s_key", backendType), password)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("key added")
}, },
} }

View File

@ -1,42 +0,0 @@
package auth
import (
"fmt"
"os"
"strings"
"syscall"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"
)
// keyCmd represents the key command
var keyCmd = &cobra.Command{
Use: "key",
Short: "Add a key to OpenAI",
Long: `This command will add a key from OpenAI to enable you to interact with the API`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Print("Enter OpenAI API Key: ")
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading OpenAI API Key from stdin: %s", err.Error())
os.Exit(1)
}
password := strings.TrimSpace(string(bytePassword))
viper.Set("openai_api_key", password)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("key added")
},
}
func init() {
AuthCmd.AddCommand(keyCmd)
}

View File

@ -2,6 +2,7 @@ package find
import ( import (
"context" "context"
"fmt"
"os" "os"
"github.com/fatih/color" "github.com/fatih/color"
@ -22,18 +23,38 @@ var problemsCmd = &cobra.Command{
provide you with a list of issues that need to be resolved`, provide you with a list of issues that need to be resolved`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Initialise the openAI client // get backend from file
openAIClient, err := ai.NewClient() backendType := viper.GetString("backend_type")
if err != nil { if backendType == "" {
color.Red("No backend set. Please run k8sgpt init")
os.Exit(1)
}
// get the token with viper
token := viper.GetString(fmt.Sprintf("%s_key", backendType))
// check if nil
if token == "" {
color.Red("No %s key set. Please run k8sgpt auth", backendType)
os.Exit(1)
}
var aiClient ai.IAI
switch backendType {
case "openai":
aiClient = &ai.OpenAIClient{}
if err := aiClient.Configure(token); err != nil {
color.Red("Error: %v", err) color.Red("Error: %v", err)
os.Exit(1) os.Exit(1)
} }
default:
color.Red("Backend not supported")
os.Exit(1)
}
ctx := context.Background() ctx := context.Background()
// Get kubernetes client from viper // Get kubernetes client from viper
client := viper.Get("kubernetesClient").(*kubernetes.Client) client := viper.Get("kubernetesClient").(*kubernetes.Client)
if err := analyzer.RunAnalysis(ctx, client, openAIClient, explain); err != nil { if err := analyzer.RunAnalysis(ctx, client, aiClient, explain); err != nil {
color.Red("Error: %v", err) color.Red("Error: %v", err)
os.Exit(1) os.Exit(1)
} }

42
cmd/init/init.go Normal file
View File

@ -0,0 +1,42 @@
package init
import (
"os"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
backend string
)
// authCmd represents the auth command
var InitCmd = &cobra.Command{
Use: "init",
Short: "Initialise k8sgpt with a backend AI provider",
Long: `Currently only OpenAI is supported.`,
Run: func(cmd *cobra.Command, args []string) {
/*
This is largely a placeholder for now. In the future we will support
multiple backends and this will allow us to set the backend we want to use.
*/
if backend != "openai" {
color.Yellow("Only OpenAI is supported at the moment")
}
viper.Set("backend_type", backend)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("Backend set to %s", backend)
},
}
func init() {
// add flag for backend
InitCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
}

View File

@ -6,6 +6,7 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/cmd/auth" "github.com/k8sgpt-ai/k8sgpt/cmd/auth"
"github.com/k8sgpt-ai/k8sgpt/cmd/find" "github.com/k8sgpt-ai/k8sgpt/cmd/find"
i "github.com/k8sgpt-ai/k8sgpt/cmd/init"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -44,7 +45,7 @@ func init() {
// will be global for your application. // will be global for your application.
rootCmd.AddCommand(auth.AuthCmd) rootCmd.AddCommand(auth.AuthCmd)
rootCmd.AddCommand(find.FindCmd) rootCmd.AddCommand(find.FindCmd)
rootCmd.AddCommand(i.InitCmd)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.git.yaml)") rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.git.yaml)")
rootCmd.PersistentFlags().StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.") rootCmd.PersistentFlags().StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
@ -59,6 +60,7 @@ func init() {
} }
viper.Set("kubernetesClient", kubernetesClient) viper.Set("kubernetesClient", kubernetesClient)
} }
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.

View File

@ -2,36 +2,25 @@ package ai
import ( import (
"context" "context"
"fmt" "errors"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"github.com/spf13/viper"
) )
type Client struct { type OpenAIClient struct {
client *openai.Client client *openai.Client
} }
func (c *Client) GetClient() *openai.Client { func (c *OpenAIClient) Configure(token string) error {
return c.client
}
func NewClient() (*Client, error) {
// get the token with viper
token := viper.GetString("openai_api_key")
// check if nil
if token == "" {
return nil, fmt.Errorf("no OpenAI API Key found")
}
client := openai.NewClient(token) client := openai.NewClient(token)
return &Client{
client: client, if client == nil {
}, nil return errors.New("error creating OpenAI client")
}
return nil
} }
func (c *Client) GetCompletion(ctx context.Context, prompt string) (string, error) { func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
// Create a completion request // Create a completion request
resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo, Model: openai.GPT3Dot5Turbo,

8
pkg/ai/iai.go Normal file
View File

@ -0,0 +1,8 @@
package ai
import "context"
type IAI interface {
Configure(token string) error
GetCompletion(ctx context.Context, prompt string) (string, error)
}

View File

@ -2,11 +2,12 @@ package analyzer
import ( import (
"context" "context"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
) )
func RunAnalysis(ctx context.Context, client *kubernetes.Client, aiClient *ai.Client, explain bool) error { func RunAnalysis(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool) error {
err := AnalyzePod(ctx, client, aiClient, explain) err := AnalyzePod(ctx, client, aiClient, explain)
if err != nil { if err != nil {
return err return err

View File

@ -4,17 +4,18 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strings"
"time"
"github.com/briandowns/spinner" "github.com/briandowns/spinner"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/spf13/viper" "github.com/spf13/viper"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
"time"
) )
func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient *ai.Client, explain bool) error { func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool) error {
// search all namespaces for pods that are not running // search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().Pods("").List(ctx, metav1.ListOptions{}) list, err := client.GetClient().CoreV1().Pods("").List(ctx, metav1.ListOptions{})

View File

@ -4,17 +4,18 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strings"
"time"
"github.com/briandowns/spinner" "github.com/briandowns/spinner"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/spf13/viper" "github.com/spf13/viper"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
"time"
) )
func AnalyzeReplicaSet(ctx context.Context, client *kubernetes.Client, aiClient *ai.Client, explain bool) error { func AnalyzeReplicaSet(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool) error {
// search all namespaces for pods that are not running // search all namespaces for pods that are not running
list, err := client.GetClient().AppsV1().ReplicaSets("").List(ctx, metav1.ListOptions{}) list, err := client.GetClient().AppsV1().ReplicaSets("").List(ctx, metav1.ListOptions{})