Skip to content

Commit

Permalink
feat: generic implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Aris Boutselis <arisboutselis08@gmail.com>
  • Loading branch information
arbreezy committed Jun 8, 2023
1 parent ca0eda8 commit 72b8b31
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 66 deletions.
13 changes: 6 additions & 7 deletions api/v1alpha1/k8sgpt_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ type ExtraOptionsRef struct {
}

type WebhookRef struct {
Webhook string `json:"webhook,omitempty"`
Type string `json:"type,omitempty"`
Endpoint string `json:"webhook,omitempty"`
}

// K8sGPTSpec defines the desired state of K8sGPT
type K8sGPTSpec struct {
// +kubebuilder:default:=openai
// +kubebuilder:validation:Enum=openai;localai;azureopenai
Backend `json:"backend"`
Backend string `json:"backend"`
BaseUrl string `json:"baseUrl,omitempty"`
// +kubebuilder:default:=gpt-3.5-turbo
Model string `json:"model,omitempty"`
Expand All @@ -58,12 +59,10 @@ type K8sGPTSpec struct {
Sink *WebhookRef `json:"sink,omitempty"`
}

type Backend string

const (
OpenAI Backend = "openai"
AzureOpenAI Backend = "azureopenai"
LocalAI Backend = "localai"
OpenAI = "openai"
AzureOpenAI = "azureopenai"
LocalAI = "localai"
)

// K8sGPTStatus defines the observed state of K8sGPT
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/result_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type Sensitive struct {

// ResultSpec defines the desired state of Result
type ResultSpec struct {
Backend `json:"backend"`
Backend string `json:"backend"`
Kind string `json:"kind"`
Name string `json:"name"`
Error []Failure `json:"error"`
Expand Down
2 changes: 2 additions & 0 deletions config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ spec:
type: object
sink:
properties:
type:
type: string
webhook:
type: string
type: object
Expand Down
10 changes: 7 additions & 3 deletions controllers/k8sgpt_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
}

// Configure sink Webhook
sink := sinks.Configure(*k8sgptConfig, *r.SinkClient)
var sinkType sinks.ISink
if k8sgptConfig.Spec.Sink != nil && k8sgptConfig.Spec.Sink.Type != "" {
sinkType = sinks.NewSink(k8sgptConfig.Spec.Sink.Type)
sinkType.Configure(*k8sgptConfig, *r.SinkClient)
}

// Check and see if the instance is new or has a K8sGPT deployment in flight
deployment := v1.Deployment{}
Expand Down Expand Up @@ -272,7 +276,7 @@ func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
k8sgptReconcileErrorCount.Inc()
return r.finishReconcile(err, false)
} else {
sink.Emit(result.Spec)
sinkType.Emit(result.Spec)
k8sgptNumberOfResultsByType.With(prometheus.Labels{
"kind": result.Spec.Kind,
"name": result.Name,
Expand All @@ -293,7 +297,7 @@ func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
k8sgptReconcileErrorCount.Inc()
return r.finishReconcile(err, false)
}
sink.Emit(existingResult.Spec)
sinkType.Emit(existingResult.Spec)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/resources/k8sgpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,15 +262,15 @@ func GetDeployment(config v1alpha1.K8sGPT) (*appsv1.Deployment, error) {
)
}
// Engine is required only when azureopenai is the ai backend
if config.Spec.Engine != "" && config.Spec.Backend == v1alpha1.AzureOpenAI {
if config.Spec.Engine != "" && config.Spec.Backend == "azureopenai" {
engine := v1.EnvVar{
Name: "K8SGPT_ENGINE",
Value: config.Spec.Engine,
}
deployment.Spec.Template.Spec.Containers[0].Env = append(
deployment.Spec.Template.Spec.Containers[0].Env, engine,
)
} else if config.Spec.Engine != "" && config.Spec.Backend != v1alpha1.AzureOpenAI {
} else if config.Spec.Engine != "" && config.Spec.Backend != "azureopenai" {
return &appsv1.Deployment{}, err.New("Engine is supported only by azureopenai provider.")
}
return &deployment, nil
Expand Down
64 changes: 11 additions & 53 deletions pkg/sinks/sinkreporter.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
package sinks

import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"

"github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1"
)

type Webhook struct {
Enabled bool
Endpoint string
Client Client
type ISink interface {
Configure(config v1alpha1.K8sGPT, c Client)
Emit(results v1alpha1.ResultSpec) error
}

type WebhookMsg struct {
Message string `json:"text"`
func NewSink(sinkType string) ISink {
switch sinkType {
case "slack":
return &SlackSink{}
//Introduce more Sink Providers
default:
return &SlackSink{}
}
}

type Client struct {
Expand All @@ -32,47 +34,3 @@ func NewClient(timeout time.Duration) *Client {
hclient: client,
}
}

func Configure(config v1alpha1.K8sGPT, c Client) *Webhook {
if config.Spec.Sink == nil || config.Spec.Sink.Webhook == "" {
return &Webhook{
Enabled: false,
}
}
return &Webhook{
Enabled: true,
Endpoint: config.Spec.Sink.Webhook,
Client: c,
}
}

func (w Webhook) Emit(res v1alpha1.ResultSpec) error {
if !w.Enabled {
// do nothing
return nil
}
msg := WebhookMsg{
Message: res.Details,
}
payload, err := json.Marshal(msg)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, w.Endpoint, strings.NewReader(string(payload)))
if err != nil {
return err
}

req.Header.Set("Content-Type", "application/json")
resp, err := w.Client.hclient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to send report: %s", resp.Status)
}

return nil
}
81 changes: 81 additions & 0 deletions pkg/sinks/slack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package sinks

import (
"bytes"
"encoding/json"
"fmt"
"net/http"

"github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1"
)

var _ ISink = (*SlackSink)(nil)

type SlackSink struct {
Endpoint string
Client Client
}

type SlackMessage struct {
Text string `json:"text"`
Attachments []Attachment `json:"attachments"`
}

type Attachment struct {
Type string `json:"type"`
Text string `json:"text"`
Color string `json:"color"`
Title string `json:"title"`
}

func buildSlackMessage(kind, name, details, backend string) SlackMessage {
return SlackMessage{
Text: fmt.Sprintf("`Analysis from %s of the %s %s`", backend, kind, name),
Attachments: []Attachment{
Attachment{
Type: "mrkdwn",
Text: details,
Color: "danger",
Title: "Report",
},
},
}
}

func (s *SlackSink) Configure(config v1alpha1.K8sGPT, c Client) {
if config.Spec.Sink == nil {
s.Endpoint = ""
}
s.Endpoint = config.Spec.Sink.Endpoint
s.Client = c
}

func (s *SlackSink) Emit(results v1alpha1.ResultSpec) error {
if s.Endpoint == "" {
// emit nothing
return nil
}

message := buildSlackMessage(results.Kind, results.Name, results.Details, results.Backend)
payload, err := json.Marshal(message)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, s.Endpoint, bytes.NewBuffer(payload))
if err != nil {
return err
}

req.Header.Set("Content-Type", "application/json")
resp, err := s.Client.hclient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to send report: %s", resp.Status)
}

return nil
}

0 comments on commit 72b8b31

Please sign in to comment.