From ab064b940cdb39a1588816221b20191e68263c61 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Sat, 16 Sep 2023 17:12:09 +0100 Subject: [PATCH] feat: serve/integration capability (#645) * chore: updated schema for integrations support (#616) Signed-off-by: Alex Jones wip: enabling integration activation Signed-off-by: Alex Jones wip: enabling integration activation Signed-off-by: Alex Jones * wip Signed-off-by: Alex Jones * feat: skipinstall fixed Signed-off-by: Alex Jones * feat: fixed filters for integrations but its ugly Signed-off-by: Alex Jones * chore: updated library Signed-off-by: Alex Jones * chore: updated go mod Signed-off-by: Alex Jones * chore: updated go mod Signed-off-by: Alex Jones --------- Signed-off-by: Alex Jones --- .gitignore | 1 + cmd/filters/list.go | 11 ++++++-- go.sum | 2 ++ pkg/integration/integration.go | 47 +++++++++++++++++++--------------- pkg/integration/trivy/trivy.go | 9 +++++++ pkg/server/README.md | 17 ++++++++++++ pkg/server/config.go | 42 +++++++++++++++++++++++++----- pkg/util/util.go | 2 +- 8 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 pkg/server/README.md diff --git a/.gitignore b/.gitignore index 94bc3e2a3f..a1a2f7c013 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +__debug* .DS_Store k8sgpt* !charts/k8sgpt diff --git a/cmd/filters/list.go b/cmd/filters/list.go index 4f9b08a88a..1332773254 100644 --- a/cmd/filters/list.go +++ b/cmd/filters/list.go @@ -18,6 +18,7 @@ import ( "github.com/fatih/color" "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "github.com/k8sgpt-ai/k8sgpt/pkg/integration" "github.com/k8sgpt-ai/k8sgpt/pkg/util" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -30,7 +31,7 @@ var listCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { activeFilters := viper.GetStringSlice("active_filters") coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters() - + integration := integration.NewIntegration() availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...) if len(activeFilters) == 0 { @@ -41,10 +42,16 @@ var listCmd = &cobra.Command{ for _, filter := range activeFilters { // if the filter is an integration, mark this differently + // but if the integration is inactive, remove if util.SliceContainsString(integrationFilters, filter) { fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter)) } 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)) + } } } diff --git a/go.sum b/go.sum index 12adcceec4..1a9540e3ed 100644 --- a/go.sum +++ b/go.sum @@ -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.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-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.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index 6e9d0c2363..a50533c95e 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -15,10 +15,8 @@ package integration import ( "errors" - "os" - "strings" + "fmt" - "github.com/fatih/color" "github.com/k8sgpt-ai/k8sgpt/pkg/common" "github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy" "github.com/k8sgpt-ai/k8sgpt/pkg/util" @@ -35,6 +33,8 @@ type IIntegration interface { GetAnalyzerName() []string + OwnsAnalyzer(string) bool + IsActivate() bool } @@ -64,34 +64,38 @@ func (*Integration) Get(name string) (IIntegration, error) { 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 { if _, ok := integrations[name]; !ok { 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 err := integrations[name].Deploy(namespace); err != nil { 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 { - color.Red("Error writing config file: %s", err.Error()) - os.Exit(1) + return fmt.Errorf("error writing config file: %s", err.Error()) + } return nil @@ -111,6 +115,7 @@ func (*Integration) Deactivate(name string, namespace string) error { activeFilters = append(activeFilters[:x], activeFilters[x+1:]...) } } + } 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) if err := viper.WriteConfig(); err != nil { - color.Red("Error writing config file: %s", err.Error()) - os.Exit(1) + return fmt.Errorf("error writing config file: %s", err.Error()) + } return nil diff --git a/pkg/integration/trivy/trivy.go b/pkg/integration/trivy/trivy.go index 8b939697d4..3679cb3c37 100644 --- a/pkg/integration/trivy/trivy.go +++ b/pkg/integration/trivy/trivy.go @@ -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 { // Add the repository diff --git a/pkg/server/README.md b/pkg/server/README.md new file mode 100644 index 0000000000..3b67d59d73 --- /dev/null +++ b/pkg/server/README.md @@ -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 +``` + diff --git a/pkg/server/config.go b/pkg/server/config.go index 0d6cf7edb3..59f9c278be 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -5,20 +5,46 @@ import ( "errors" 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/integration" + "github.com/spf13/viper" ) 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 err != nil { - return &schemav1.AddConfigResponse{}, err + if i.Integrations != nil { + coreFilters, _, _ := analyzer.ListFilters() + // 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{ Status: "Configuration updated.", }, nil @@ -28,7 +54,9 @@ func (h *handler) RemoveConfig(ctx context.Context, i *schemav1.RemoveConfigRequ ) { err := cache.RemoveRemoteCache(i.Cache.BucketName) if err != nil { - return &schemav1.RemoveConfigResponse{}, err + return &schemav1.RemoveConfigResponse{ + Status: err.Error(), + }, err } return &schemav1.RemoveConfigResponse{ diff --git a/pkg/util/util.go b/pkg/util/util.go index 317a7de51e..c1a8168903 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -15,12 +15,12 @@ package util import ( "context" + "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "fmt" - "math/rand" "os" "regexp"