mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2025-04-27 19:15:24 +00:00
feat: enables remote custom analyzers (#906)
* feat: enables remote custom analyzers Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * chore: fixed test that was broken Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * chore: hiding custom analysis behind a flag Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * chore: resolved govet issue Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * chore: updated Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * chore: updated Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * chore: updated deps Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * chore: updated deps Signed-off-by: Alex Jones <alexsimonjones@gmail.com> --------- Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
This commit is contained in:
parent
070aa7fdd0
commit
c8c9dbfadc
27
README.md
27
README.md
@ -459,6 +459,33 @@ k8sgpt cache remove
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary> Custom Analyzers</summary>
|
||||
|
||||
There may be scenarios where you wish to write your own analyzer in a language of your choice.
|
||||
K8sGPT now supports the ability to do so by abiding by the [schema](https://github.com/k8sgpt-ai/schemas/blob/main/protobuf/schema/v1/analyzer.proto) and serving the analyzer for consumption.
|
||||
To do so, define the analyzer within the K8sGPT configuration and it will add it into the scanning process.
|
||||
In addition to this you will need to enable the following flag on analysis:
|
||||
```
|
||||
k8sgpt analyze --custom-analysis
|
||||
```
|
||||
|
||||
Here is an example local host analyzer in [Rust](https://github.com/k8sgpt-ai/host-analyzer)
|
||||
When this is run on `localhost:8080` the K8sGPT config can pick it up with the following additions:
|
||||
|
||||
```
|
||||
custom_analyzers:
|
||||
- name: host-analyzer
|
||||
connection:
|
||||
url: localhost
|
||||
port: 8080
|
||||
```
|
||||
|
||||
This now gives the ability to pass through hostOS information ( from this analyzer example ) to K8sGPT to use as context with normal analysis.
|
||||
|
||||
_See the docs on how to write a custom analyzer_
|
||||
|
||||
</details>
|
||||
|
||||
## Documentation
|
||||
|
||||
|
@ -37,6 +37,7 @@ var (
|
||||
maxConcurrency int
|
||||
withDoc bool
|
||||
interactiveMode bool
|
||||
customAnalysis bool
|
||||
)
|
||||
|
||||
// AnalyzeCmd represents the problems command
|
||||
@ -65,6 +66,9 @@ var AnalyzeCmd = &cobra.Command{
|
||||
}
|
||||
defer config.Close()
|
||||
|
||||
if customAnalysis {
|
||||
config.RunCustomAnalysis()
|
||||
}
|
||||
config.RunAnalysis()
|
||||
|
||||
if explain {
|
||||
@ -131,4 +135,7 @@ func init() {
|
||||
AnalyzeCmd.Flags().BoolVarP(&withDoc, "with-doc", "d", false, "Give me the official documentation of the involved field")
|
||||
// interactive mode flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&interactiveMode, "interactive", "i", false, "Enable interactive mode that allows further conversation with LLM about the problem. Works only with --explain flag")
|
||||
// custom analysis flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&customAnalysis, "custom-analysis", "z", false, "Enable custom analyzers")
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"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/custom"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
@ -50,8 +51,10 @@ type Analysis struct {
|
||||
WithDoc bool
|
||||
}
|
||||
|
||||
type AnalysisStatus string
|
||||
type AnalysisErrors []string
|
||||
type (
|
||||
AnalysisStatus string
|
||||
AnalysisErrors []string
|
||||
)
|
||||
|
||||
const (
|
||||
StateOK AnalysisStatus = "OK"
|
||||
@ -147,6 +150,27 @@ func NewAnalysis(
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Analysis) RunCustomAnalysis() {
|
||||
var customAnalyzers []custom.CustomAnalyzer
|
||||
if err := viper.UnmarshalKey("custom_analyzers", &customAnalyzers); err != nil {
|
||||
a.Errors = append(a.Errors, err.Error())
|
||||
}
|
||||
|
||||
for _, cAnalyzer := range customAnalyzers {
|
||||
|
||||
canClient, err := custom.NewClient(cAnalyzer.Connection)
|
||||
if err != nil {
|
||||
a.Errors = append(a.Errors, fmt.Sprintf("Client creation error for %s analyzer", cAnalyzer.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
result, err := canClient.Run()
|
||||
if err != nil {
|
||||
a.Results = append(a.Results, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Analysis) RunAnalysis() {
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
|
||||
|
57
pkg/custom/client.go
Normal file
57
pkg/custom/client.go
Normal file
@ -0,0 +1,57 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc"
|
||||
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
c *grpc.ClientConn
|
||||
analyzerClient rpc.AnalyzerServiceClient
|
||||
}
|
||||
|
||||
func NewClient(c Connection) (*Client, error) {
|
||||
|
||||
conn, err := grpc.Dial(fmt.Sprintf("%s:%s", c.Url, c.Port), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := rpc.NewAnalyzerServiceClient(conn)
|
||||
return &Client{
|
||||
c: conn,
|
||||
analyzerClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cli *Client) Run() (common.Result, error) {
|
||||
var result common.Result
|
||||
req := &schemav1.AnalyzerRunRequest{}
|
||||
res, err := cli.analyzerClient.Run(context.Background(), req)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if res.Result != nil {
|
||||
|
||||
// We should refactor this, because Error and Failure do not map 1:1 from K8sGPT/schema
|
||||
var errorsFound []common.Failure
|
||||
for _, e := range res.Result.Error {
|
||||
errorsFound = append(errorsFound, common.Failure{
|
||||
Text: e.Text,
|
||||
// TODO: Support sensitive data
|
||||
})
|
||||
}
|
||||
|
||||
result.Name = res.Result.Name
|
||||
result.Kind = res.Result.Kind
|
||||
result.Details = res.Result.Details
|
||||
result.ParentObject = res.Result.ParentObject
|
||||
result.Error = errorsFound
|
||||
}
|
||||
return result, nil
|
||||
}
|
10
pkg/custom/types.go
Normal file
10
pkg/custom/types.go
Normal file
@ -0,0 +1,10 @@
|
||||
package custom
|
||||
|
||||
type Connection struct {
|
||||
Url string `json:"url"`
|
||||
Port string `json:"port"`
|
||||
}
|
||||
type CustomAnalyzer struct {
|
||||
Name string `json:"name"`
|
||||
Connection Connection `json:"connection"`
|
||||
}
|
Loading…
Reference in New Issue
Block a user