Skip to content

Commit

Permalink
feat: enables remote custom analyzers (#906)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
AlexsJones committed Feb 15, 2024
1 parent 070aa7f commit c8c9dbf
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 2 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions cmd/analyze/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var (
maxConcurrency int
withDoc bool
interactiveMode bool
customAnalysis bool
)

// AnalyzeCmd represents the problems command
Expand Down Expand Up @@ -65,6 +66,9 @@ var AnalyzeCmd = &cobra.Command{
}
defer config.Close()

if customAnalysis {
config.RunCustomAnalysis()
}
config.RunAnalysis()

if explain {
Expand Down Expand Up @@ -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 changes: 26 additions & 2 deletions pkg/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -50,8 +51,10 @@ type Analysis struct {
WithDoc bool
}

type AnalysisStatus string
type AnalysisErrors []string
type (
AnalysisStatus string
AnalysisErrors []string
)

const (
StateOK AnalysisStatus = "OK"
Expand Down Expand Up @@ -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")

Expand Down
57 changes: 57 additions & 0 deletions pkg/custom/client.go
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions pkg/custom/types.go
Original file line number Diff line number Diff line change
@@ -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"`
}

0 comments on commit c8c9dbf

Please sign in to comment.