diff --git a/pkg/ai/iai.go b/pkg/ai/iai.go index 5e5365b4db..993da5927b 100644 --- a/pkg/ai/iai.go +++ b/pkg/ai/iai.go @@ -15,12 +15,14 @@ package ai import ( "context" + + "github.com/k8sgpt-ai/k8sgpt/pkg/cache" ) type IAI interface { Configure(config IAIConfig, language string) error GetCompletion(ctx context.Context, prompt string) (string, error) - Parse(ctx context.Context, prompt []string, nocache bool) (string, error) + Parse(ctx context.Context, prompt []string, cache cache.Cache) (string, error) GetName() string } diff --git a/pkg/ai/noopai.go b/pkg/ai/noopai.go index 261ac4b41b..bb39fe9c08 100644 --- a/pkg/ai/noopai.go +++ b/pkg/ai/noopai.go @@ -20,8 +20,8 @@ import ( "strings" "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/pkg/cache" "github.com/k8sgpt-ai/k8sgpt/pkg/util" - "github.com/spf13/viper" ) type NoOpAIClient struct { @@ -44,7 +44,7 @@ func (c *NoOpAIClient) GetCompletion(ctx context.Context, prompt string) (string return response, nil } -func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, nocache bool) (string, error) { +func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, cache cache.Cache) (string, error) { // parse the text with the AI backend inputKey := strings.Join(prompt, " ") // Check for cached data @@ -57,13 +57,13 @@ func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, nocache bool) return "", err } - if !viper.IsSet(cacheKey) { - viper.Set(cacheKey, base64.StdEncoding.EncodeToString([]byte(response))) - if err := viper.WriteConfig(); err != nil { - color.Red("error writing config: %v", err) - return "", nil - } + err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response))) + + if err != nil { + color.Red("error storing value to cache: %v", err) + return "", nil } + return response, nil } diff --git a/pkg/ai/openai.go b/pkg/ai/openai.go index 42d3d6f987..a631ebe2ac 100644 --- a/pkg/ai/openai.go +++ b/pkg/ai/openai.go @@ -20,12 +20,12 @@ import ( "fmt" "strings" + "github.com/k8sgpt-ai/k8sgpt/pkg/cache" "github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/sashabaranov/go-openai" "github.com/fatih/color" - "github.com/spf13/viper" ) type OpenAIClient struct { @@ -70,15 +70,20 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string return resp.Choices[0].Message.Content, nil } -func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool) (string, error) { +func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, cache cache.Cache) (string, error) { inputKey := strings.Join(prompt, " ") // Check for cached data sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey)) cacheKey := util.GetCacheKey(a.GetName(), a.language, sEnc) // find in viper cache - if viper.IsSet(cacheKey) && !nocache { + if cache.Exists(cacheKey) { // retrieve data from cache - response := viper.GetString(cacheKey) + response, err := cache.Load(cacheKey) + + if err != nil { + return "", err + } + if response == "" { color.Red("error retrieving cached data") return "", nil @@ -96,13 +101,13 @@ func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool) return "", err } - if !viper.IsSet(cacheKey) || nocache { - viper.Set(cacheKey, base64.StdEncoding.EncodeToString([]byte(response))) - if err := viper.WriteConfig(); err != nil { - color.Red("error writing config: %v", err) - return "", nil - } + err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response))) + + if err != nil { + color.Red("error storing value to cache: %v", err) + return "", nil } + return response, nil } diff --git a/pkg/analysis/analysis.go b/pkg/analysis/analysis.go index d95dd23b4e..f8551d56c3 100644 --- a/pkg/analysis/analysis.go +++ b/pkg/analysis/analysis.go @@ -24,6 +24,7 @@ import ( "github.com/fatih/color" "github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "github.com/k8sgpt-ai/k8sgpt/pkg/cache" "github.com/k8sgpt-ai/k8sgpt/pkg/common" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/util" @@ -38,7 +39,7 @@ type Analysis struct { AIClient ai.IAI Results []common.Result Namespace string - NoCache bool + Cache cache.Cache Explain bool } @@ -104,7 +105,7 @@ func NewAnalysis(backend string, language string, filters []string, namespace st Client: client, AIClient: aiClient, Namespace: namespace, - NoCache: noCache, + Cache: cache.New(noCache), Explain: explain, }, nil } @@ -185,7 +186,7 @@ func (a *Analysis) GetAIResults(output string, anonymize bool) error { } texts = append(texts, failure.Text) } - parsedText, err := a.AIClient.Parse(a.Context, texts, a.NoCache) + parsedText, err := a.AIClient.Parse(a.Context, texts, a.Cache) if err != nil { // FIXME: can we avoid checking if output is json multiple times? // maybe implement the progress bar better? diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go new file mode 100644 index 0000000000..7c919534cb --- /dev/null +++ b/pkg/cache/cache.go @@ -0,0 +1,15 @@ +package cache + +type Cache interface { + Store(key string, data string) error + Load(key string) (string, error) + Exists(key string) bool +} + +func New(noCache bool) Cache { + if noCache { + return &NoopCache{} + } + + return &FileBasedCache{} +} diff --git a/pkg/cache/file_based.go b/pkg/cache/file_based.go new file mode 100644 index 0000000000..dd61b98c3d --- /dev/null +++ b/pkg/cache/file_based.go @@ -0,0 +1,58 @@ +package cache + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/adrg/xdg" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" +) + +var _ (Cache) = (*FileBasedCache)(nil) + +type FileBasedCache struct{} + +func (*FileBasedCache) Exists(key string) bool { + path, err := xdg.CacheFile(filepath.Join("k8sgpt", key)) + + if err != nil { + fmt.Fprintln(os.Stderr, "warning: error while testing if cache key exists:", err) + return false + } + + exists, err := util.FileExists(path) + + if err != nil { + fmt.Fprintln(os.Stderr, "warning: error while testing if cache key exists:", err) + return false + } + + return exists +} + +func (*FileBasedCache) Load(key string) (string, error) { + path, err := xdg.CacheFile(filepath.Join("k8sgpt", key)) + + if err != nil { + return "", err + } + + data, err := os.ReadFile(path) + + if err != nil { + return "", err + } + + return string(data), nil +} + +func (*FileBasedCache) Store(key string, data string) error { + path, err := xdg.CacheFile(filepath.Join("k8sgpt", key)) + + if err != nil { + return err + } + + return os.WriteFile(path, []byte(data), 0600) +} diff --git a/pkg/cache/noop.go b/pkg/cache/noop.go new file mode 100644 index 0000000000..8ec58986ae --- /dev/null +++ b/pkg/cache/noop.go @@ -0,0 +1,17 @@ +package cache + +var _ (Cache) = (*NoopCache)(nil) + +type NoopCache struct{} + +func (c *NoopCache) Store(key string, data string) error { + return nil +} + +func (c *NoopCache) Load(key string) (string, error) { + return "", nil +} + +func (c *NoopCache) Exists(key string) bool { + return false +}