diff --git a/.gitignore b/.gitignore index a1a2f7c013..720837dd96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea __debug* .DS_Store k8sgpt* diff --git a/go.mod b/go.mod index 4a514f2d5c..e76845652b 100644 --- a/go.mod +++ b/go.mod @@ -24,8 +24,8 @@ require ( require github.com/adrg/xdg v0.4.0 require ( - buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230919114723-34e017906403.1 - buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230919114723-34e017906403.1 + buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230927080702-a2be8a73637d.1 + buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230927080702-a2be8a73637d.1 github.com/aws/aws-sdk-go v1.45.16 github.com/cohere-ai/cohere-go v0.2.0 ) diff --git a/go.sum b/go.sum index 906918f37d..55e345c947 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ -buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230919114723-34e017906403.1 h1:OMpJ48yTsJ12DDJlhpNXTZOfNEfkrcAwGqgSvL1vg7U= -buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230919114723-34e017906403.1/go.mod h1:cc42fuhIhL3qTsCrT4dK0kZ5u6hm02WJraREmSVZHmA= -buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20230919114723-34e017906403.4/go.mod h1:i/s4ALHwKvjA1oGNKpoHg0FpEOTbufoOm/NdTE6YQAE= -buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230919114723-34e017906403.1 h1:rn//G20ZMgHwnfl7shj5zmpDgzS8aZsoVkeJ7+fMkfo= -buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230919114723-34e017906403.1/go.mod h1:gtnk2yAUexdY5nTuUg0SH5WCCGvpKzr7pd3Xbi7MWjE= +buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230927080702-a2be8a73637d.1 h1:uXlT8FiRD+JL0qzZJ0m5Zmw5HpKyDFs204y27zuT7RA= +buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230927080702-a2be8a73637d.1/go.mod h1:p9CUiOwgt2bvcr0goNK7NgMfButIVGhKnv8cyWW7FOM= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20230927080702-a2be8a73637d.4/go.mod h1:i/s4ALHwKvjA1oGNKpoHg0FpEOTbufoOm/NdTE6YQAE= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230927080702-a2be8a73637d.1 h1:Snnz9mUZNxMFpd+l5m1zaVdIVAplVmdFxYVn5/f4UoI= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230927080702-a2be8a73637d.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/cache/cache.go b/pkg/cache/cache.go index 96f69fcc3d..16858e980d 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -1,7 +1,8 @@ package cache import ( - "errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/spf13/viper" ) @@ -63,17 +64,17 @@ func RemoveRemoteCache(bucketName string) error { var cacheInfo CacheProvider err := viper.UnmarshalKey("cache", &cacheInfo) if err != nil { - return err + return status.Error(codes.Internal, "cache unmarshal") } if cacheInfo.BucketName == "" { - return errors.New("Error: no cache is configured") + return status.Error(codes.Internal, "no cache configured") } cacheInfo = CacheProvider{} viper.Set("cache", cacheInfo) err = viper.WriteConfig() if err != nil { - return err + return status.Error(codes.Internal, "unable to write config") } return nil diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index a50533c95e..cd5c0ef06b 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -32,6 +32,8 @@ type IIntegration interface { AddAnalyzer(*map[string]common.IAnalyzer) GetAnalyzerName() []string + // An integration must keep record of its deployed namespace (if not using --no-install) + GetNamespace() (string, error) OwnsAnalyzer(string) bool @@ -86,7 +88,6 @@ func (*Integration) Activate(name string, namespace string, activeFilters []stri return err } } - mergedFilters := activeFilters mergedFilters = append(mergedFilters, integrations[name].GetAnalyzerName()...) uniqueFilters, _ := util.RemoveDuplicates(mergedFilters) diff --git a/pkg/integration/trivy/trivy.go b/pkg/integration/trivy/trivy.go index 1e5f0783c4..4acfea79ea 100644 --- a/pkg/integration/trivy/trivy.go +++ b/pkg/integration/trivy/trivy.go @@ -16,6 +16,8 @@ package trivy import ( "context" "fmt" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/k8sgpt-ai/k8sgpt/pkg/common" helmclient "github.com/mittwald/go-helm-client" @@ -51,6 +53,20 @@ func (t *Trivy) GetAnalyzerName() []string { } } +// This doesnt work +func (t *Trivy) GetNamespace() (string, error) { + releases, err := t.helm.ListDeployedReleases() + if err != nil { + return "", err + } + for _, rel := range releases { + if rel.Name == ReleaseName { + return rel.Namespace, nil + } + } + return "", status.Error(codes.NotFound, "trivy release not found") +} + func (t *Trivy) OwnsAnalyzer(analyzer string) bool { for _, a := range t.GetAnalyzerName() { @@ -67,7 +83,6 @@ func (t *Trivy) Deploy(namespace string) error { Name: RepoShortName, URL: Repo, } - // Add a chart-repository to the client. if err := t.helm.AddOrUpdateChartRepo(chartRepo); err != nil { panic(err) diff --git a/pkg/server/README.md b/pkg/server/README.md index f548151299..e63fea10c0 100644 --- a/pkg/server/README.md +++ b/pkg/server/README.md @@ -16,7 +16,7 @@ grpcurl -plaintext -d '{"namespace": "k8sgpt", "explain" : "true"}' localhost:80 ``` ``` -grpcurl -plaintext localhost:8080 schema.v1.ServerService/ListIntegrations +grpcurl -plaintext localhost:8080 schema.v1.ServerService/ListIntegrations { "integrations": [ "trivy" @@ -24,3 +24,7 @@ grpcurl -plaintext localhost:8080 schema.v1.ServerService/ListIntegrations } ``` + +``` +grpcurl -plaintext -d '{"integrations":{"trivy":{"enabled":"true","namespace":"default","skipInstall":"false"}}}' localhost:8080 schema.v1.ServerService/AddConfig +``` diff --git a/pkg/server/config.go b/pkg/server/config.go index 59f9c278be..cd94c22229 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -1,64 +1,45 @@ package server import ( - "context" - "errors" - schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" - "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" + "context" "github.com/k8sgpt-ai/k8sgpt/pkg/cache" - "github.com/k8sgpt-ai/k8sgpt/pkg/integration" - "github.com/spf13/viper" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func (h *handler) AddConfig(ctx context.Context, i *schemav1.AddConfigRequest) (*schemav1.AddConfigResponse, error, ) { - 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 - } + resp, err := h.syncIntegration(ctx, i) + if err != nil { + return resp, err } + if i.Cache != nil { // Remote cache if i.Cache.BucketName == "" || i.Cache.Region == "" { - return &schemav1.AddConfigResponse{}, errors.New("BucketName & Region are required") + return resp, status.Error(codes.InvalidArgument, "cache arguments") } err := cache.AddRemoteCache(i.Cache.BucketName, i.Cache.Region) if err != nil { - return &schemav1.AddConfigResponse{ - Status: err.Error(), - }, err + return resp, err } } - return &schemav1.AddConfigResponse{ - Status: "Configuration updated.", - }, nil + return resp, nil } func (h *handler) RemoveConfig(ctx context.Context, i *schemav1.RemoveConfigRequest) (*schemav1.RemoveConfigResponse, error, ) { err := cache.RemoveRemoteCache(i.Cache.BucketName) if err != nil { - return &schemav1.RemoveConfigResponse{ - Status: err.Error(), - }, err + return &schemav1.RemoveConfigResponse{}, err } + // Remove any integrations is a TBD as it would be nice to make this more granular + // Currently integrations can be removed in the AddConfig sync + return &schemav1.RemoveConfigResponse{ Status: "Successfully removed the remote cache", }, nil diff --git a/pkg/server/integration.go b/pkg/server/integration.go index 197e008e2c..901df9485e 100644 --- a/pkg/server/integration.go +++ b/pkg/server/integration.go @@ -1,24 +1,144 @@ package server import ( - "context" - schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" + "context" + "fmt" + "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" "github.com/k8sgpt-ai/k8sgpt/pkg/integration" + "github.com/spf13/viper" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + trivyName = "trivy" ) +// syncIntegration is aware of the following events +// A new integration added +// An integration removed from the Integration block +func (h *handler) syncIntegration(ctx context.Context, + i *schemav1.AddConfigRequest) (*schemav1.AddConfigResponse, error, +) { + response := &schemav1.AddConfigResponse{} + integrationProvider := integration.NewIntegration() + if i.Integrations == nil { + // If there are locally activate integrations, disable them + err := h.deactivateAllIntegrations(integrationProvider) + if err != nil { + return response, status.Error(codes.NotFound, "deactivation error") + } + return response, nil + } + coreFilters, _, _ := analyzer.ListFilters() + // Update filters + activeFilters := viper.GetStringSlice("active_filters") + if len(activeFilters) == 0 { + activeFilters = coreFilters + } + var err error = status.Error(codes.OK, "") + deactivateFunc := func(integrationRef integration.IIntegration) error { + namespace, err := integrationRef.GetNamespace() + if err != nil { + return err + } + err = integrationProvider.Deactivate(trivyName, namespace) + if err != nil { + return status.Error(codes.NotFound, "integration already deactivated") + } + return nil + } + integrationRef, err := integrationProvider.Get(trivyName) + if err != nil { + return response, status.Error(codes.NotFound, "provider get failure") + } + if i.Integrations.Trivy != nil { + switch i.Integrations.Trivy.Enabled { + case true: + if b, err := integrationProvider.IsActivate(trivyName); err != nil { + return response, status.Error(codes.Internal, "integration activation error") + } else { + if !b { + err := integrationProvider.Activate(trivyName, i.Integrations.Trivy.Namespace, + activeFilters, i.Integrations.Trivy.SkipInstall) + if err != nil { + return nil, err + } + } else { + return response, status.Error(codes.AlreadyExists, "integration already active") + } + } + case false: + err = deactivateFunc(integrationRef) + if err != nil { + return nil, err + } + // This break is included purely for static analysis to pass + } + } else { + // If Trivy has been removed, disable it + err = deactivateFunc(integrationRef) + if err != nil { + return nil, err + } + } + + return response, err +} + func (*handler) ListIntegrations(ctx context.Context, req *schemav1.ListIntegrationsRequest) (*schemav1.ListIntegrationsResponse, error) { integrationProvider := integration.NewIntegration() - integrations := integrationProvider.List() + // Update the requester with the status of Trivy + trivy, err := integrationProvider.Get(trivyName) + active := trivy.IsActivate() + var skipInstall bool + var namespace string = "" + if active { + namespace, err = trivy.GetNamespace() + if err != nil { + return nil, status.Error(codes.NotFound, "namespace not found") + } + if namespace == "" { + skipInstall = true + } + } + + if err != nil { + return nil, status.Error(codes.NotFound, "trivy integration") + } resp := &schemav1.ListIntegrationsResponse{ - Integrations: make([]string, 0), + Trivy: &schemav1.Trivy{ + Enabled: active, + Namespace: namespace, + SkipInstall: skipInstall, + }, } + + return resp, nil +} + +func (*handler) deactivateAllIntegrations(integrationProvider *integration.Integration) error { + integrations := integrationProvider.List() for _, i := range integrations { b, _ := integrationProvider.IsActivate(i) if b { - resp.Integrations = append(resp.Integrations, i) + in, err := integrationProvider.Get(i) + namespace, err := in.GetNamespace() + if err != nil { + return err + } + if err == nil { + if namespace != "" { + integrationProvider.Deactivate(i, namespace) + } else { + fmt.Printf("Skipping deactivation of %s, not installed\n", i) + } + } else { + return err + } } } - return resp, nil + return nil }