Compare commits

..

5 Commits

Author SHA1 Message Date
AlexsJones
7eba7ff788 chore: removed file
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-06 21:40:38 +01:00
AlexsJones
1c0c7bac73 feat: http server
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-06 21:35:47 +01:00
Thomas Schuetz
e449cb6023 chore: add fakeai provider (#218)
* chore: add fakeai provider

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* chore: add fakeai provider

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: name of openai provider

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: renamed fakeai backend to noopai

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: renamed fakeai backend to noopai

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

---------

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
Co-authored-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-06 21:10:01 +02:00
HOLLEVILLE Matthis
1069f0c610 Merge pull request #221 from arbreezy/feat/statefulset-analyzer 2023-04-06 16:27:52 +02:00
Aris Boutselis
c041ce2bbb feat: introduce StatefulSet analyser.
Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io>
2023-04-06 13:31:58 +01:00
12 changed files with 266 additions and 12 deletions

View File

@@ -6,6 +6,7 @@ import (
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"github.com/k8sgpt-ai/k8sgpt/cmd/serve"
"k8s.io/client-go/util/homedir"
"github.com/fatih/color"
@@ -54,13 +55,14 @@ func init() {
rootCmd.AddCommand(analyze.AnalyzeCmd)
rootCmd.AddCommand(filters.FiltersCmd)
rootCmd.AddCommand(generate.GenerateCmd)
rootCmd.AddCommand(serve.ServeCmd)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.yaml)")
rootCmd.PersistentFlags().StringVar(&kubecontext, "kubecontext", "", "Kubernetes context to use. Only required if out-of-cluster.")
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", kubeconfigPath, "Path to a kubeconfig. Only required if out-of-cluster.")
// Cobra also supports local flags, which will only run
// when this action is called directly.
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
viper.Set("rootCmd", rootCmd)
}
// initConfig reads in config file and ENV variables if set.

54
cmd/serve/serve.go Normal file
View File

@@ -0,0 +1,54 @@
package serve
import (
"io/ioutil"
"log"
"net/http"
"os"
"github.com/julienschmidt/httprouter"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
port string
)
// generateCmd represents the auth command
var ServeCmd = &cobra.Command{
Use: "serve",
Short: "",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
rootCmd := viper.Get("rootCmd").(*cobra.Command)
// Start http server
router := httprouter.New()
router.GET("/:command", func(w http.ResponseWriter,
r *http.Request, p httprouter.Params) {
// find the command
command := p.ByName("command")
cmd, string, err := rootCmd.Find([]string{command})
if err != nil {
w.Write([]byte(err.Error()))
}
old := os.Stdout // keep backup of the real stdout
rd, d, _ := os.Pipe()
os.Stdout = d
cmd.Run(cmd, string)
d.Close()
out, _ := ioutil.ReadAll(rd)
os.Stdout = old // restoring the real stdout
w.Write(out)
})
log.Fatal(http.ListenAndServe(":8080", router))
},
}
func init() {
// add flag for backend
ServeCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to serve on")
}

View File

@@ -1,6 +1,8 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
@@ -10,7 +12,7 @@ var versionCmd = &cobra.Command{
Short: "Print the version number of k8sgpt",
Long: `All software has versions. This is k8sgpt's`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Printf("k8sgpt version %s", version)
fmt.Printf("k8sgpt version %s", version)
},
}

2
go.mod
View File

@@ -10,6 +10,7 @@ require (
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.2
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/term v0.7.0
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
@@ -36,6 +37,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect

3
go.sum
View File

@@ -174,6 +174,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -279,6 +281,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=

View File

@@ -8,13 +8,26 @@ type IAI interface {
Configure(token string, model string, language string) error
GetCompletion(ctx context.Context, prompt string) (string, error)
Parse(ctx context.Context, prompt []string, nocache bool) (string, error)
GetName() string
}
func NewClient(provider string) IAI {
switch provider {
case "openai":
return &OpenAIClient{}
case "noopai":
return &NoOpAIClient{}
default:
return &OpenAIClient{}
}
}
type AIConfiguration struct {
Providers []AIProvider `mapstructure:"providers"`
}
type AIProvider struct {
Name string `mapstructure:"name"`
Model string `mapstructure:"model"`
Password string `mapstructure:"password"`
}

55
pkg/ai/noopai.go Normal file
View File

@@ -0,0 +1,55 @@
package ai
import (
"context"
"encoding/base64"
"fmt"
"github.com/fatih/color"
"github.com/spf13/viper"
"strings"
)
type NoOpAIClient struct {
client string
language string
model string
}
func (c *NoOpAIClient) Configure(token string, model string, language string) error {
c.language = language
c.client = fmt.Sprintf("I am a noop client with the token %s ", token)
c.model = model
return nil
}
func (c *NoOpAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
// Create a completion request
response := "I am a noop response to the prompt " + prompt
return response, nil
}
func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, nocache bool) (string, error) {
// parse the text with the AI backend
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
response, err := a.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", err
}
if !viper.IsSet(sEnc) {
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
if err := viper.WriteConfig(); err != nil {
color.Red("error writing config: %v", err)
return "", nil
}
}
return response, nil
}
func (a *NoOpAIClient) GetName() string {
return "noopai"
}

View File

@@ -20,16 +20,6 @@ const (
prompt_c = "Reading the following %s error message and it's accompanying log message %s, how would you simplify this message?"
)
type AIConfiguration struct {
Providers []AIProvider `mapstructure:"providers"`
}
type AIProvider struct {
Name string `mapstructure:"name"`
Model string `mapstructure:"model"`
Password string `mapstructure:"password"`
}
type OpenAIClient struct {
client *openai.Client
language string
@@ -100,3 +90,7 @@ func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool)
}
return response, nil
}
func (a *OpenAIClient) GetName() string {
return "openai"
}

View File

@@ -10,6 +10,7 @@ var coreAnalyzerMap = map[string]IAnalyzer{
"PersistentVolumeClaim": PvcAnalyzer{},
"Service": ServiceAnalyzer{},
"Ingress": IngressAnalyzer{},
"StatefulSet": StatefulSetAnalyzer{},
}
var additionalAnalyzerMap = map[string]IAnalyzer{

View File

@@ -0,0 +1,49 @@
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type StatefulSetAnalyzer struct{}
func (StatefulSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
list, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
for _, sts := range list.Items {
var failures []string
// get serviceName
serviceName := sts.Spec.ServiceName
_, err := a.Client.GetClient().CoreV1().Services(sts.Namespace).Get(a.Context, serviceName, metav1.GetOptions{})
if err != nil {
failures = append(failures, fmt.Sprintf("StatefulSet uses the service %s/%s which does not exist.", sts.Namespace, serviceName))
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", sts.Namespace, sts.Name)] = PreAnalysis{
StatefulSet: sts,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
Kind: "StatefulSet",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.StatefulSet.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -0,0 +1,78 @@
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestStatefulSetAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := statefulSetAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
func TestStatefulSetAnalyzerWithoutService(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: appsv1.StatefulSetSpec{
ServiceName: "example-svc",
},
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := statefulSetAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
var errorFound bool
want := "StatefulSet uses the service default/example-svc which does not exist."
for _, analysis := range analysisResults {
for _, got := range analysis.Error {
if want == got {
errorFound = true
}
}
if errorFound {
break
}
}
if !errorFound {
t.Errorf("Error expected: '%v', not found in StatefulSet's analysis results", want)
}
}

View File

@@ -29,6 +29,7 @@ type PreAnalysis struct {
Ingress networkv1.Ingress
HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler
PodDisruptionBudget policyv1.PodDisruptionBudget
StatefulSet appsv1.StatefulSet
}
type Result struct {