From efae4a977f9c6f7acd6fdf2eb6514800a0ad3761 Mon Sep 17 00:00:00 2001 From: Phillip Ahereza Date: Tue, 16 Jan 2024 00:10:50 +0100 Subject: [PATCH] [Feature]: Add a field for a secret to the Sink Type (#317) * add secret to sinks Signed-off-by: Phillip Ahereza * generate deepcopy Signed-off-by: Phillip Ahereza * add backward compat for mattermost sink and secret Signed-off-by: Phillip Ahereza * sort imports Signed-off-by: Phillip Ahereza * change variable name to make it clear Signed-off-by: Phillip Ahereza * update readme Signed-off-by: Phillip Ahereza * chore: minor fix in helm's crd Signed-off-by: Aris Boutselis --------- Signed-off-by: Phillip Ahereza Signed-off-by: Aris Boutselis Co-authored-by: Aris Boutselis Co-authored-by: Aris Boutselis --- README.md | 5 ++++- api/v1alpha1/k8sgpt_types.go | 11 +++++----- api/v1alpha1/zz_generated.deepcopy.go | 7 ++++++- chart/operator/templates/k8sgpt-crd.yaml | 7 +++++++ config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml | 7 +++++++ controllers/k8sgpt_controller.go | 22 ++++++++++++++++++-- pkg/sinks/mattermost.go | 7 +++++-- pkg/sinks/sinkreporter.go | 2 +- pkg/sinks/slack.go | 8 +++++-- 9 files changed, 62 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c0386021..ded0fcad 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,10 @@ spec: # - Ingress # sink: # type: slack - # webhook: + # webhook: # use the sink secret if you want to keep your webhook url private + # secret: + # name: slack-webhook + # key: url #extraOptions: # backstage: # enabled: true diff --git a/api/v1alpha1/k8sgpt_types.go b/api/v1alpha1/k8sgpt_types.go index d74065e6..93352180 100644 --- a/api/v1alpha1/k8sgpt_types.go +++ b/api/v1alpha1/k8sgpt_types.go @@ -65,11 +65,12 @@ type GCSBackend struct { type WebhookRef struct { // +kubebuilder:validation:Enum=slack;mattermost - Type string `json:"type,omitempty"` - Endpoint string `json:"webhook,omitempty"` - Channel string `json:"channel,omitempty"` - UserName string `json:"username,omitempty"` - IconURL string `json:"icon_url,omitempty"` + Type string `json:"type,omitempty"` + Endpoint string `json:"webhook,omitempty"` + Channel string `json:"channel,omitempty"` + UserName string `json:"username,omitempty"` + IconURL string `json:"icon_url,omitempty"` + Secret *SecretRef `json:"secret,omitempty"` } type AISpec struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index cb332441..1ad08c5f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -260,7 +260,7 @@ func (in *K8sGPTSpec) DeepCopyInto(out *K8sGPTSpec) { if in.Sink != nil { in, out := &in.Sink, &out.Sink *out = new(WebhookRef) - **out = **in + (*in).DeepCopyInto(*out) } if in.AI != nil { in, out := &in.AI, &out.AI @@ -505,6 +505,11 @@ func (in *Trivy) DeepCopy() *Trivy { // 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 + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + *out = new(SecretRef) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookRef. diff --git a/chart/operator/templates/k8sgpt-crd.yaml b/chart/operator/templates/k8sgpt-crd.yaml index 3b2664f9..35d8d47a 100644 --- a/chart/operator/templates/k8sgpt-crd.yaml +++ b/chart/operator/templates/k8sgpt-crd.yaml @@ -149,6 +149,13 @@ spec: type: string icon_url: type: string + secret: + properties: + key: + type: string + name: + type: string + type: object type: enum: - slack diff --git a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml index f5bd1da7..16407d4c 100644 --- a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml +++ b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml @@ -149,6 +149,13 @@ spec: type: string icon_url: type: string + secret: + properties: + key: + type: string + name: + type: string + type: object type: enum: - slack diff --git a/controllers/k8sgpt_controller.go b/controllers/k8sgpt_controller.go index c5d983e2..83b2dafe 100644 --- a/controllers/k8sgpt_controller.go +++ b/controllers/k8sgpt_controller.go @@ -29,7 +29,9 @@ import ( "github.com/k8sgpt-ai/k8sgpt-operator/pkg/utils" "github.com/prometheus/client_golang/prometheus" v1 "k8s.io/api/apps/v1" + kcorev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -284,12 +286,28 @@ func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr if len(latestResultList.Items) == 0 { return r.finishReconcile(nil, false) } - sinkEnabled := k8sgptConfig.Spec.Sink != nil && k8sgptConfig.Spec.Sink.Type != "" && k8sgptConfig.Spec.Sink.Endpoint != "" + + sinkEnabled := k8sgptConfig.Spec.Sink != nil && k8sgptConfig.Spec.Sink.Type != "" && (k8sgptConfig.Spec.Sink.Endpoint != "" || k8sgptConfig.Spec.Sink.Secret != nil) var sinkType sinks.ISink if sinkEnabled { + var sinkSecretValue string + + if k8sgptConfig.Spec.Sink.Secret != nil { + secret := &kcorev1.Secret{} + secretNamespacedName := types.NamespacedName{ + Namespace: req.Namespace, + Name: k8sgptConfig.Spec.Sink.Secret.Name, + } + if err := r.Get(ctx, secretNamespacedName, secret); err != nil { + k8sgptReconcileErrorCount.Inc() + return r.finishReconcile(fmt.Errorf("could not find sink secret: %w", err), false) + } + + sinkSecretValue = string(secret.Data[k8sgptConfig.Spec.Sink.Secret.Key]) + } sinkType = sinks.NewSink(k8sgptConfig.Spec.Sink.Type) - sinkType.Configure(*k8sgptConfig, *r.SinkClient) + sinkType.Configure(*k8sgptConfig, *r.SinkClient, sinkSecretValue) } for _, result := range latestResultList.Items { diff --git a/pkg/sinks/mattermost.go b/pkg/sinks/mattermost.go index 5cc6d5d1..c6e10991 100644 --- a/pkg/sinks/mattermost.go +++ b/pkg/sinks/mattermost.go @@ -50,8 +50,11 @@ func buildMattermostMessage(kind, name, details, k8sgptCR, channel, username, ic } } -func (s *MattermostSink) Configure(config v1alpha1.K8sGPT, c Client) { - s.Endpoint = config.Spec.Sink.Endpoint +func (s *MattermostSink) Configure(config v1alpha1.K8sGPT, c Client, sinkSecretValue string) { + s.Endpoint = sinkSecretValue + if s.Endpoint == "" { + s.Endpoint = config.Spec.Sink.Endpoint + } // If no value is given, the default value of the webhook is used if config.Spec.Sink.Channel != "" { s.Channel = config.Spec.Sink.Channel diff --git a/pkg/sinks/sinkreporter.go b/pkg/sinks/sinkreporter.go index 2331b7e0..6f7413e4 100644 --- a/pkg/sinks/sinkreporter.go +++ b/pkg/sinks/sinkreporter.go @@ -8,7 +8,7 @@ import ( ) type ISink interface { - Configure(config v1alpha1.K8sGPT, c Client) + Configure(config v1alpha1.K8sGPT, c Client, sinkSecretValue string) Emit(results v1alpha1.ResultSpec) error } diff --git a/pkg/sinks/slack.go b/pkg/sinks/slack.go index 1449bf8b..bbbee5ea 100644 --- a/pkg/sinks/slack.go +++ b/pkg/sinks/slack.go @@ -43,8 +43,12 @@ func buildSlackMessage(kind, name, details, k8sgptCR string) SlackMessage { } } -func (s *SlackSink) Configure(config v1alpha1.K8sGPT, c Client) { - s.Endpoint = config.Spec.Sink.Endpoint +func (s *SlackSink) Configure(config v1alpha1.K8sGPT, c Client, sinkSecretValue string) { + s.Endpoint = sinkSecretValue + // check if the webhook url is passed as a sinkSecretValue, if not use spec.sink.webhook + if s.Endpoint == "" { + s.Endpoint = config.Spec.Sink.Endpoint + } s.Client = c // take the name of the K8sGPT Custom Resource s.K8sGPT = config.Name