Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mattermost sink #303

Merged
merged 4 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,19 @@ EOF

</details>

<details>
<summary>sink (integrations) </summary>

Optional parameters available for sink.
('type', 'webhook' are required parameters.)

| tool | channel | icon_url | username |
|------------|---------|----------|----------|
| Slack | | | |
| Mattermost | ✔️ | ✔️ | ✔️ |

</details>

## Helm values

For details please see [here](chart/operator/values.yaml)
Expand Down
5 changes: 4 additions & 1 deletion api/v1alpha1/k8sgpt_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,12 @@ type GCSBackend struct {
}

type WebhookRef struct {
// +kubebuilder:validation:Enum=slack
// +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 AISpec struct {
Expand Down
7 changes: 7 additions & 0 deletions chart/operator/templates/k8sgpt-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,16 @@ spec:
type: string
sink:
properties:
channel:
type: string
icon_url:
type: string
type:
enum:
- slack
- mattermost
type: string
username:
type: string
webhook:
type: string
Expand Down
7 changes: 7 additions & 0 deletions config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,16 @@ spec:
type: string
sink:
properties:
channel:
type: string
icon_url:
type: string
type:
enum:
- slack
- mattermost
type: string
username:
type: string
webhook:
type: string
Expand Down
107 changes: 107 additions & 0 deletions pkg/sinks/mattermost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package sinks

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

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

var _ ISink = (*MattermostSink)(nil)

type MattermostSink struct {
Endpoint string
K8sGPT string
Client Client
Channel string
UserName string
IconURL string
}

type MattermostMessage struct {
Text string `json:"text"`
Channel string `json:"channel,omitempty"`
UserName string `json:"username,omitempty"`
IconURL string `json:"icon_url,omitempty"`
Attachments []attachment `json:"attachments"`
}

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

func buildMattermostMessage(kind, name, details, k8sgptCR, channel, username, iconURL string) MattermostMessage {
return MattermostMessage{
Text: fmt.Sprintf(">*[%s] K8sGPT analysis of the %s %s*", k8sgptCR, kind, name),
Channel: channel,
UserName: username,
IconURL: iconURL,
Attachments: []attachment{
attachment{
Text: details,
Color: "danger",
Title: "Report",
},
},
}
}

func (s *MattermostSink) Configure(config v1alpha1.K8sGPT, c Client) {
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
}
// If no value is given, the default value of the webhook is used
if config.Spec.Sink.UserName != "" {
s.UserName = config.Spec.Sink.UserName
}
// If no value is given, the default value of the webhook is used
if config.Spec.Sink.IconURL != "" {
s.IconURL = config.Spec.Sink.IconURL
}
s.Client = c
// take the name of the K8sGPT Custom Resource
s.K8sGPT = config.Name
}

func (s *MattermostSink) Emit(results v1alpha1.ResultSpec) error {
details := ""
// If AI is set to False, Details will not have a value, so if it is empty, use the Error text.
if results.Details == "" && len(results.Error) > 0 {
for i, v := range results.Error {
details += fmt.Sprintf("%d. %s\n", i+1, v.Text)
}
} else {
details = results.Details
}
message := buildMattermostMessage(
results.Kind, results.Name, details, s.K8sGPT,
s.Channel, s.UserName, s.IconURL,
)
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
}
4 changes: 3 additions & 1 deletion pkg/sinks/sinkreporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ func NewSink(sinkType string) ISink {
switch sinkType {
case "slack":
return &SlackSink{}
//Introduce more Sink Providers
//Introduce more Sink Providers
case "mattermost":
return &MattermostSink{}
default:
return &SlackSink{}
}
Expand Down