feat: integration refactor (#684)

* feat: more significant refactor

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

* feat: more significant refactor

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

* feat: reworked the integration activate/deactivation

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

* chore: updated schema for list integrations

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

* fix: error with incorrect error being swallowed

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

* feat: added namespace check

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

* chore: fixed issue with namespace and skip install validation

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-28 07:43:05 +01:00 committed by GitHub
parent ddeff9fae4
commit 69fe2db8ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 177 additions and 54 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea
__debug* __debug*
.DS_Store .DS_Store
k8sgpt* k8sgpt*

4
go.mod
View File

@ -24,8 +24,8 @@ require (
require github.com/adrg/xdg v0.4.0 require github.com/adrg/xdg v0.4.0
require ( require (
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.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-20230919114723-34e017906403.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/aws/aws-sdk-go v1.45.16
github.com/cohere-ai/cohere-go v0.2.0 github.com/cohere-ai/cohere-go v0.2.0
) )

10
go.sum
View File

@ -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-20230927080702-a2be8a73637d.1 h1:uXlT8FiRD+JL0qzZJ0m5Zmw5HpKyDFs204y27zuT7RA=
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/grpc/go v1.3.0-20230927080702-a2be8a73637d.1/go.mod h1:p9CUiOwgt2bvcr0goNK7NgMfButIVGhKnv8cyWW7FOM=
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.28.1-20230927080702-a2be8a73637d.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-20230927080702-a2be8a73637d.1 h1:Snnz9mUZNxMFpd+l5m1zaVdIVAplVmdFxYVn5/f4UoI=
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/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.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=

9
pkg/cache/cache.go vendored
View File

@ -1,7 +1,8 @@
package cache package cache
import ( import (
"errors" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -63,17 +64,17 @@ func RemoveRemoteCache(bucketName string) error {
var cacheInfo CacheProvider var cacheInfo CacheProvider
err := viper.UnmarshalKey("cache", &cacheInfo) err := viper.UnmarshalKey("cache", &cacheInfo)
if err != nil { if err != nil {
return err return status.Error(codes.Internal, "cache unmarshal")
} }
if cacheInfo.BucketName == "" { if cacheInfo.BucketName == "" {
return errors.New("Error: no cache is configured") return status.Error(codes.Internal, "no cache configured")
} }
cacheInfo = CacheProvider{} cacheInfo = CacheProvider{}
viper.Set("cache", cacheInfo) viper.Set("cache", cacheInfo)
err = viper.WriteConfig() err = viper.WriteConfig()
if err != nil { if err != nil {
return err return status.Error(codes.Internal, "unable to write config")
} }
return nil return nil

View File

@ -32,6 +32,8 @@ type IIntegration interface {
AddAnalyzer(*map[string]common.IAnalyzer) AddAnalyzer(*map[string]common.IAnalyzer)
GetAnalyzerName() []string GetAnalyzerName() []string
// An integration must keep record of its deployed namespace (if not using --no-install)
GetNamespace() (string, error)
OwnsAnalyzer(string) bool OwnsAnalyzer(string) bool
@ -86,7 +88,6 @@ func (*Integration) Activate(name string, namespace string, activeFilters []stri
return err return err
} }
} }
mergedFilters := activeFilters mergedFilters := activeFilters
mergedFilters = append(mergedFilters, integrations[name].GetAnalyzerName()...) mergedFilters = append(mergedFilters, integrations[name].GetAnalyzerName()...)
uniqueFilters, _ := util.RemoveDuplicates(mergedFilters) uniqueFilters, _ := util.RemoveDuplicates(mergedFilters)

View File

@ -16,6 +16,8 @@ package trivy
import ( import (
"context" "context"
"fmt" "fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/k8sgpt-ai/k8sgpt/pkg/common" "github.com/k8sgpt-ai/k8sgpt/pkg/common"
helmclient "github.com/mittwald/go-helm-client" 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 { func (t *Trivy) OwnsAnalyzer(analyzer string) bool {
for _, a := range t.GetAnalyzerName() { for _, a := range t.GetAnalyzerName() {
@ -67,7 +83,6 @@ func (t *Trivy) Deploy(namespace string) error {
Name: RepoShortName, Name: RepoShortName,
URL: Repo, URL: Repo,
} }
// Add a chart-repository to the client. // Add a chart-repository to the client.
if err := t.helm.AddOrUpdateChartRepo(chartRepo); err != nil { if err := t.helm.AddOrUpdateChartRepo(chartRepo); err != nil {
panic(err) panic(err)

View File

@ -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": [ "integrations": [
"trivy" "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
```

View File

@ -1,64 +1,45 @@
package server package server
import ( import (
"context"
"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" "context"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache" "github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration" "google.golang.org/grpc/codes"
"github.com/spf13/viper" "google.golang.org/grpc/status"
) )
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.Integrations != nil { resp, err := h.syncIntegration(ctx, i)
coreFilters, _, _ := analyzer.ListFilters() if err != nil {
// Update filters return resp, err
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 { if i.Cache != nil {
// Remote cache // Remote cache
if i.Cache.BucketName == "" || i.Cache.Region == "" { 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) err := cache.AddRemoteCache(i.Cache.BucketName, i.Cache.Region)
if err != nil { if err != nil {
return &schemav1.AddConfigResponse{ return resp, err
Status: err.Error(),
}, err
} }
} }
return &schemav1.AddConfigResponse{ return resp, nil
Status: "Configuration updated.",
}, nil
} }
func (h *handler) RemoveConfig(ctx context.Context, i *schemav1.RemoveConfigRequest) (*schemav1.RemoveConfigResponse, error, func (h *handler) RemoveConfig(ctx context.Context, i *schemav1.RemoveConfigRequest) (*schemav1.RemoveConfigResponse, error,
) { ) {
err := cache.RemoveRemoteCache(i.Cache.BucketName) err := cache.RemoveRemoteCache(i.Cache.BucketName)
if err != nil { if err != nil {
return &schemav1.RemoveConfigResponse{ return &schemav1.RemoveConfigResponse{}, err
Status: err.Error(),
}, 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{ return &schemav1.RemoveConfigResponse{
Status: "Successfully removed the remote cache", Status: "Successfully removed the remote cache",
}, nil }, nil

View File

@ -1,24 +1,144 @@
package server package server
import ( import (
"context"
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" 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/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) { func (*handler) ListIntegrations(ctx context.Context, req *schemav1.ListIntegrationsRequest) (*schemav1.ListIntegrationsResponse, error) {
integrationProvider := integration.NewIntegration() integrationProvider := integration.NewIntegration()
integrations := integrationProvider.List() // Update the requester with the status of Trivy
resp := &schemav1.ListIntegrationsResponse{ trivy, err := integrationProvider.Get(trivyName)
Integrations: make([]string, 0), 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{
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 { for _, i := range integrations {
b, _ := integrationProvider.IsActivate(i) b, _ := integrationProvider.IsActivate(i)
if b { 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
} }