feat: serve/integration capability (#645)

* chore: updated schema for integrations support (#616)

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

wip: enabling integration activation

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

wip: enabling integration activation

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

* wip

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

* feat: skipinstall fixed

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

* feat: fixed filters for integrations but its ugly

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

* chore: updated library

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

* chore: updated go mod

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

* chore: updated go mod

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

---------

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
This commit is contained in:
Alex Jones
2023-09-16 17:12:09 +01:00
committed by GitHub
parent 6481590b29
commit ab064b940c
8 changed files with 101 additions and 32 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
__debug*
.DS_Store .DS_Store
k8sgpt* k8sgpt*
!charts/k8sgpt !charts/k8sgpt

View File

@@ -18,6 +18,7 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@@ -30,7 +31,7 @@ var listCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
activeFilters := viper.GetStringSlice("active_filters") activeFilters := viper.GetStringSlice("active_filters")
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters() coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
integration := integration.NewIntegration()
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...) availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
if len(activeFilters) == 0 { if len(activeFilters) == 0 {
@@ -41,10 +42,16 @@ var listCmd = &cobra.Command{
for _, filter := range activeFilters { for _, filter := range activeFilters {
// if the filter is an integration, mark this differently // if the filter is an integration, mark this differently
// but if the integration is inactive, remove
if util.SliceContainsString(integrationFilters, filter) { if util.SliceContainsString(integrationFilters, filter) {
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter)) fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
} else { } else {
fmt.Printf("> %s\n", color.GreenString(filter)) // This strange bit of logic will loop through every integration via
// OwnsAnalyzer subcommand to check the filter and as the integrationFilters...
// was no match, we know this isn't part of an active integration
if _, err := integration.AnalyzerByIntegration(filter); err != nil {
fmt.Printf("> %s\n", color.GreenString(filter))
}
} }
} }

2
go.sum
View File

@@ -3,6 +3,8 @@ buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230620082254-6f80f9533908.1/g
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20230620082254-6f80f9533908.4/go.mod h1:i/s4ALHwKvjA1oGNKpoHg0FpEOTbufoOm/NdTE6YQAE= buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20230620082254-6f80f9533908.4/go.mod h1:i/s4ALHwKvjA1oGNKpoHg0FpEOTbufoOm/NdTE6YQAE=
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230828112343-a9fd9ad20848.1 h1:fScSW5Gzu1OzUYylwpvQwgXX5J9YPKkOQpbkc5c8Q+8= buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230828112343-a9fd9ad20848.1 h1:fScSW5Gzu1OzUYylwpvQwgXX5J9YPKkOQpbkc5c8Q+8=
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230828112343-a9fd9ad20848.1/go.mod h1:gtnk2yAUexdY5nTuUg0SH5WCCGvpKzr7pd3Xbi7MWjE= buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230828112343-a9fd9ad20848.1/go.mod h1:gtnk2yAUexdY5nTuUg0SH5WCCGvpKzr7pd3Xbi7MWjE=
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230830164712-dc062a152c20.1 h1:oD5YCdsVnQgVLSHxium66FFfuxjjvn5u65mnQo6vAyc=
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230830164712-dc062a152c20.1/go.mod h1:gtnk2yAUexdY5nTuUg0SH5WCCGvpKzr7pd3Xbi7MWjE=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=

View File

@@ -15,10 +15,8 @@ package integration
import ( import (
"errors" "errors"
"os" "fmt"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common" "github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy" "github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy"
"github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/k8sgpt-ai/k8sgpt/pkg/util"
@@ -35,6 +33,8 @@ type IIntegration interface {
GetAnalyzerName() []string GetAnalyzerName() []string
OwnsAnalyzer(string) bool
IsActivate() bool IsActivate() bool
} }
@@ -64,34 +64,38 @@ func (*Integration) Get(name string) (IIntegration, error) {
return integrations[name], nil return integrations[name], nil
} }
func (i *Integration) AnalyzerByIntegration(input string) (string, error) {
for _, name := range i.List() {
if integ, err := i.Get(name); err == nil {
if integ.OwnsAnalyzer(input) {
return name, nil
}
}
}
return "", errors.New("analyzerbyintegration: no matches found")
}
func (*Integration) Activate(name string, namespace string, activeFilters []string, skipInstall bool) error { func (*Integration) Activate(name string, namespace string, activeFilters []string, skipInstall bool) error {
if _, ok := integrations[name]; !ok { if _, ok := integrations[name]; !ok {
return errors.New("integration not found") return errors.New("integration not found")
} }
mergedFilters := activeFilters
mergedFilters = append(mergedFilters, integrations[name].GetAnalyzerName()...)
uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(mergedFilters)
// Verify dupplicate
if len(dupplicatedFilters) != 0 {
color.Red("Integration already activated : %s", strings.Join(dupplicatedFilters, ", "))
os.Exit(1)
}
viper.Set("active_filters", uniqueFilters)
if !skipInstall { if !skipInstall {
if err := integrations[name].Deploy(namespace); err != nil { if err := integrations[name].Deploy(namespace); err != nil {
return err return err
} }
} }
mergedFilters := activeFilters
mergedFilters = append(mergedFilters, integrations[name].GetAnalyzerName()...)
uniqueFilters, _ := util.RemoveDuplicates(mergedFilters)
viper.Set("active_filters", uniqueFilters)
if err := viper.WriteConfig(); err != nil { if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error()) return fmt.Errorf("error writing config file: %s", err.Error())
os.Exit(1)
} }
return nil return nil
@@ -111,6 +115,7 @@ func (*Integration) Deactivate(name string, namespace string) error {
activeFilters = append(activeFilters[:x], activeFilters[x+1:]...) activeFilters = append(activeFilters[:x], activeFilters[x+1:]...)
} }
} }
} }
if err := integrations[name].UnDeploy(namespace); err != nil { if err := integrations[name].UnDeploy(namespace); err != nil {
@@ -120,8 +125,8 @@ func (*Integration) Deactivate(name string, namespace string) error {
viper.Set("active_filters", activeFilters) viper.Set("active_filters", activeFilters)
if err := viper.WriteConfig(); err != nil { if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error()) return fmt.Errorf("error writing config file: %s", err.Error())
os.Exit(1)
} }
return nil return nil

View File

@@ -51,6 +51,15 @@ func (t *Trivy) GetAnalyzerName() []string {
} }
} }
func (t *Trivy) OwnsAnalyzer(analyzer string) bool {
for _, a := range t.GetAnalyzerName() {
if analyzer == a {
return true
}
}
return false
}
func (t *Trivy) Deploy(namespace string) error { func (t *Trivy) Deploy(namespace string) error {
// Add the repository // Add the repository

17
pkg/server/README.md Normal file
View File

@@ -0,0 +1,17 @@
# serve
The serve commands allow you to run k8sgpt in a grpc server mode.
This would be enabled typically through `k8sgpt serve` and is how the in-cluster k8sgpt deployment functions when managed by the [k8sgpt-operator](https://github.com/k8sgpt-ai/k8sgpt-operator)
The grpc interface that is served is hosted on [buf](https://buf.build/k8sgpt-ai/schemas) and the repository for this is [here](https://github.com/k8sgpt-ai/schemas)
## grpcurl
A fantastic tool for local debugging and development is `grpcurl`
It allows you to form curl like requests that are http2
e.g.
```
grpcurl -plaintext -d '{"namespace": "k8sgpt", "explain" : "true"}' localhost:8080 schema.v1.ServerService/Analyze
```

View File

@@ -5,20 +5,46 @@ import (
"errors" "errors"
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache" "github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/spf13/viper"
) )
func (h *handler) AddConfig(ctx context.Context, i *schemav1.AddConfigRequest) (*schemav1.AddConfigResponse, error, func (h *handler) AddConfig(ctx context.Context, i *schemav1.AddConfigRequest) (*schemav1.AddConfigResponse, error,
) { ) {
if i.Cache.BucketName == "" || i.Cache.Region == "" {
return nil, errors.New("BucketName & Region are required")
}
err := cache.AddRemoteCache(i.Cache.BucketName, i.Cache.Region) if i.Integrations != nil {
if err != nil { coreFilters, _, _ := analyzer.ListFilters()
return &schemav1.AddConfigResponse{}, err // Update filters
} activeFilters := viper.GetStringSlice("active_filters")
if len(activeFilters) == 0 {
activeFilters = coreFilters
}
integration := integration.NewIntegration()
if i.Integrations.Trivy != nil {
// Enable/Disable Trivy
var err = integration.Activate("trivy", i.Integrations.Trivy.Namespace,
activeFilters, i.Integrations.Trivy.SkipInstall)
return &schemav1.AddConfigResponse{
Status: "",
}, err
}
}
if i.Cache != nil {
// Remote cache
if i.Cache.BucketName == "" || i.Cache.Region == "" {
return &schemav1.AddConfigResponse{}, errors.New("BucketName & Region are required")
}
err := cache.AddRemoteCache(i.Cache.BucketName, i.Cache.Region)
if err != nil {
return &schemav1.AddConfigResponse{
Status: err.Error(),
}, err
}
}
return &schemav1.AddConfigResponse{ return &schemav1.AddConfigResponse{
Status: "Configuration updated.", Status: "Configuration updated.",
}, nil }, nil
@@ -28,7 +54,9 @@ func (h *handler) RemoveConfig(ctx context.Context, i *schemav1.RemoveConfigRequ
) { ) {
err := cache.RemoveRemoteCache(i.Cache.BucketName) err := cache.RemoveRemoteCache(i.Cache.BucketName)
if err != nil { if err != nil {
return &schemav1.RemoveConfigResponse{}, err return &schemav1.RemoveConfigResponse{
Status: err.Error(),
}, err
} }
return &schemav1.RemoveConfigResponse{ return &schemav1.RemoveConfigResponse{

View File

@@ -15,12 +15,12 @@ package util
import ( import (
"context" "context"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math/rand"
"os" "os"
"regexp" "regexp"