From 246077ec23b0bfbec7719ae9e6cbeca2ab29d1b4 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Sun, 15 Oct 2023 20:30:23 +0100 Subject: [PATCH] feat: initial integration support (#225) * feat: initial integration support Signed-off-by: Alex Jones * chore: added timeout Signed-off-by: Alex Jones * chore: added timeout Signed-off-by: Alex Jones * chore: wip Signed-off-by: Alex Jones * chore: wip Signed-off-by: Alex Jones * chore: updated rbac Signed-off-by: Alex Jones * chore: updated schema dependencies Signed-off-by: Alex Jones * chore: resolved mod issues Signed-off-by: Alex Jones --------- Signed-off-by: Alex Jones --- README.md | 6 ++- api/v1alpha1/k8sgpt_types.go | 10 +++++ api/v1alpha1/zz_generated.deepcopy.go | 40 +++++++++++++++++ config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml | 12 +++++ config/manager/kustomization.yaml | 2 +- config/rbac/role.yaml | 8 +--- config/samples/core_v1alpha1_k8sgpt.yaml | 17 +++++--- controllers/k8sgpt_controller.go | 11 ++++- go.mod | 2 +- go.sum | 4 +- pkg/client/client.go | 3 +- pkg/client/integration.go | 46 ++++++++++++++++++++ pkg/resources/k8sgpt.go | 9 +++- 13 files changed, 147 insertions(+), 23 deletions(-) create mode 100644 pkg/client/integration.go diff --git a/README.md b/README.md index 7ef6f910..85bc0dbc 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,11 @@ spec: # anonymized: false # language: english noCache: false - version: v0.3.8 + version: v0.3.17 + #integrations: + # trivy: + # enabled: true + # namespace: trivy-system # filters: # - Ingress # sink: diff --git a/api/v1alpha1/k8sgpt_types.go b/api/v1alpha1/k8sgpt_types.go index b720c357..441012f2 100644 --- a/api/v1alpha1/k8sgpt_types.go +++ b/api/v1alpha1/k8sgpt_types.go @@ -68,6 +68,15 @@ type AISpec struct { Language string `json:"language,omitempty"` } +type Trivy struct { + Enabled bool `json:"enabled,omitempty"` + SkipInstall bool `json:"skipInstall,omitempty"` + Namespace string `json:"namespace,omitempty"` +} +type Integrations struct { + Trivy *Trivy `json:"trivy,omitempty"` +} + // K8sGPTSpec defines the desired state of K8sGPT type K8sGPTSpec struct { Version string `json:"version,omitempty"` @@ -77,6 +86,7 @@ type K8sGPTSpec struct { Sink *WebhookRef `json:"sink,omitempty"` AI *AISpec `json:"ai,omitempty"` RemoteCache *RemoteCacheRef `json:"remoteCache,omitempty"` + Integrations *Integrations `json:"integrations,omitempty"` } const ( diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c131490b..3ef00f24 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -115,6 +115,26 @@ func (in *Failure) DeepCopy() *Failure { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Integrations) DeepCopyInto(out *Integrations) { + *out = *in + if in.Trivy != nil { + in, out := &in.Trivy, &out.Trivy + *out = new(Trivy) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Integrations. +func (in *Integrations) DeepCopy() *Integrations { + if in == nil { + return nil + } + out := new(Integrations) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *K8sGPT) DeepCopyInto(out *K8sGPT) { *out = *in @@ -202,6 +222,11 @@ func (in *K8sGPTSpec) DeepCopyInto(out *K8sGPTSpec) { *out = new(RemoteCacheRef) (*in).DeepCopyInto(*out) } + if in.Integrations != nil { + in, out := &in.Integrations, &out.Integrations + *out = new(Integrations) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8sGPTSpec. @@ -375,6 +400,21 @@ func (in *Sensitive) DeepCopy() *Sensitive { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Trivy) DeepCopyInto(out *Trivy) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Trivy. +func (in *Trivy) DeepCopy() *Trivy { + if in == nil { + return nil + } + out := new(Trivy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WebhookRef) DeepCopyInto(out *WebhookRef) { *out = *in diff --git a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml index d071c423..9a536326 100644 --- a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml +++ b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml @@ -81,6 +81,18 @@ spec: items: type: string type: array + integrations: + properties: + trivy: + properties: + enabled: + type: boolean + namespace: + type: string + skipInstall: + type: boolean + type: object + type: object noCache: type: boolean remoteCache: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 6a5a4bbe..97592351 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: ghcr.io/k8sgpt-ai/k8sgpt-operator - newTag: latest + newTag: feature-integration-support-4 diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 229e2d2d..47fa5889 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -10,13 +10,7 @@ rules: resources: - '*' verbs: - - create - - delete - - get - - list - - patch - - update - - watch + - '*' - apiGroups: - core.k8sgpt.ai resources: diff --git a/config/samples/core_v1alpha1_k8sgpt.yaml b/config/samples/core_v1alpha1_k8sgpt.yaml index 56687150..594af65f 100644 --- a/config/samples/core_v1alpha1_k8sgpt.yaml +++ b/config/samples/core_v1alpha1_k8sgpt.yaml @@ -12,9 +12,14 @@ spec: name: k8sgpt-sample-secret key: openai-api-key noCache: false - version: v0.3.5 - remoteCache: - credentials: - name: k8sgpt-sample-cache-secret - bucketName: k8sgpt-debug-test - region: eu-west-2 \ No newline at end of file + version: v0.3.17 +# remoteCache: +# credentials: +# name: k8sgpt-sample-cache-secret +# bucketName: k8sgpt-debug-test +# region: eu-west-2 + integrations: + trivy: + enabled: false + namespace: trivy-system + skipInstall: false diff --git a/controllers/k8sgpt_controller.go b/controllers/k8sgpt_controller.go index a939a428..dccfd350 100644 --- a/controllers/k8sgpt_controller.go +++ b/controllers/k8sgpt_controller.go @@ -75,8 +75,8 @@ type K8sGPTReconciler struct { // +kubebuilder:rbac:groups=core.k8sgpt.ai,resources=k8sgpts/status,verbs=get;update;patch // +kubebuilder:rbac:groups=core.k8sgpt.ai,resources=k8sgpts/finalizers,verbs=update // +kubebuilder:rbac:groups=core.k8sgpt.ai,resources=results,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups="*",resources="*",verbs=get;list;watch;create;update;patch;delete - +// +kubebuilder:rbac:groups="*",resources="*",verbs="*" +// +kubebuilder:rbac:groups="apiextensions.k8s.io",resources="*",verbs="*" func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) @@ -178,6 +178,13 @@ func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return r.finishReconcile(err, false) } } + if k8sgptConfig.Spec.Integrations != nil { + err = k8sgptClient.AddIntegration(k8sgptConfig) + if err != nil { + k8sgptReconcileErrorCount.Inc() + return r.finishReconcile(err, false) + } + } response, err := k8sgptClient.ProcessAnalysis(deployment, k8sgptConfig) if err != nil { diff --git a/go.mod b/go.mod index 1c0d0b9a..8d56fbe0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20231002095256-194bc640518b.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-20231002095256-194bc640518b.1 github.com/onsi/ginkgo/v2 v2.12.1 github.com/onsi/gomega v1.28.0 github.com/prometheus/client_golang v1.17.0 diff --git a/go.sum b/go.sum index 988cec0a..f41e1065 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20231002095256-194bc640518b.1 h1:xYEAhcdWh89HNtbM5Uv4b2xu+4/MkNffR9JNrnnEjXU= buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20231002095256-194bc640518b.1/go.mod h1:j2QJ3L7VTtI+VeK6t03h9X934FolVTb3FwXUc76bQMQ= buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20231002095256-194bc640518b.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/protocolbuffers/go v1.31.0-20231002095256-194bc640518b.1 h1:Bt8mnCodD/BqChxt/r3xYayGLoOAn334qC1tN7VqUTE= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20231002095256-194bc640518b.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/client/client.go b/pkg/client/client.go index e4ff946e..8bf2ffd9 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -39,9 +39,8 @@ func NewClient(address string) (*Client, error) { // Connect to the K8sGPT server and create a new client conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { - return nil, fmt.Errorf("failed to dial K8sGPT server: %v", err) + return nil, fmt.Errorf("failed to create context: %v", err) } - client := &Client{conn: conn} return client, nil diff --git a/pkg/client/integration.go b/pkg/client/integration.go new file mode 100644 index 00000000..b1180d81 --- /dev/null +++ b/pkg/client/integration.go @@ -0,0 +1,46 @@ +package client + +import ( + "context" + "fmt" + + 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" + "github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1" +) + +func (c *Client) AddIntegration(config *v1alpha1.K8sGPT) error { + + // Check if the integration is active already + client := rpc.NewServerServiceClient(c.conn) + req := &schemav1.ListIntegrationsRequest{} + + resp, err := client.ListIntegrations(context.Background(), + req) + if err != nil { + return err + } + + if resp.Trivy.Enabled == config.Spec.Integrations.Trivy.Enabled { + fmt.Println("Skipping trivy installation, already enabled") + return nil + } + // If the integration is inactive, make it active + // Equally, if the flag has been deactivated we should also account for this + // TODO: Currently this only support trivy + configUpdatereq := &schemav1.AddConfigRequest{ + Integrations: &schemav1.Integrations{ + Trivy: &schemav1.Trivy{ + Enabled: config.Spec.Integrations.Trivy.Enabled, + SkipInstall: config.Spec.Integrations.Trivy.SkipInstall, + Namespace: config.Spec.Integrations.Trivy.Namespace, + }, + }, + } + _, err = client.AddConfig(context.Background(), configUpdatereq) + if err != nil { + return fmt.Errorf("failed to call AddConfig RPC: %v", err) + } + + return nil +} diff --git a/pkg/resources/k8sgpt.go b/pkg/resources/k8sgpt.go index 99c76157..103fd727 100644 --- a/pkg/resources/k8sgpt.go +++ b/pkg/resources/k8sgpt.go @@ -155,7 +155,14 @@ func GetClusterRole(config v1alpha1.K8sGPT) (*r1.ClusterRole, error) { { APIGroups: []string{"*"}, Resources: []string{"*"}, - Verbs: []string{"list", "get", "watch"}, + // This is necessary for the creation of integrations + Verbs: []string{"create", "list", "get", "watch", "delete"}, + }, + // Allow creation of custom resources + { + APIGroups: []string{"apiextensions.k8s.io"}, + Resources: []string{"*"}, + Verbs: []string{"*"}, }, }, }