kubernetes-operator/pkg/notifications/slack/slack.go

154 lines
3.6 KiB
Go

package slack
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/notifications/event"
"github.com/jenkinsci/kubernetes-operator/pkg/notifications/provider"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
)
const (
infoColor = "#439FE0"
warningColor = "danger"
defaultColor = "#c8c8c8"
)
// Slack is a Slack notification service.
type Slack struct {
httpClient http.Client
k8sClient k8sclient.Client
config v1alpha2.Notification
}
// New returns instance of Slack.
func New(k8sClient k8sclient.Client, config v1alpha2.Notification, httpClient http.Client) *Slack {
return &Slack{k8sClient: k8sClient, config: config, httpClient: httpClient}
}
// Message is representation of json message.
type Message struct {
Text string `json:"text"`
Attachments []Attachment `json:"attachments"`
}
// Attachment is representation of json attachment.
type Attachment struct {
Fallback string `json:"fallback"`
Color event.StatusColor `json:"color"`
Pretext string `json:"pretext"`
Title string `json:"title"`
Text string `json:"text"`
Fields []Field `json:"fields"`
Footer string `json:"footer"`
}
// Field is representation of json field.
type Field struct {
Title string `json:"title"`
Value string `json:"value"`
Short bool `json:"short"`
}
func (s Slack) getStatusColor(logLevel v1alpha2.NotificationLevel) event.StatusColor {
switch logLevel {
case v1alpha2.NotificationLevelInfo:
return infoColor
case v1alpha2.NotificationLevelWarning:
return warningColor
default:
return defaultColor
}
}
func (s Slack) generateMessage(e event.Event) Message {
var messageStringBuilder strings.Builder
if s.config.Verbose {
for _, msg := range e.Reason.Verbose() {
messageStringBuilder.WriteString(fmt.Sprintf("\n - %s \n", msg))
}
} else {
for _, msg := range e.Reason.Short() {
messageStringBuilder.WriteString(fmt.Sprintf("\n - %s \n", msg))
}
}
sm := Message{
Attachments: []Attachment{
{
Title: provider.NotificationTitle(e),
Fallback: "",
Color: s.getStatusColor(e.Level),
Fields: []Field{
{
Title: "",
Value: messageStringBuilder.String(),
Short: false,
},
{
Title: provider.NamespaceFieldName,
Value: e.Jenkins.Namespace,
Short: true,
},
{
Title: provider.CrNameFieldName,
Value: e.Jenkins.Name,
Short: true,
},
{
Title: provider.PhaseFieldName,
Value: string(e.Phase),
Short: true,
},
},
},
},
}
return sm
}
// Send is function for sending directly to API.
func (s Slack) Send(e event.Event) error {
secret := &corev1.Secret{}
selector := s.config.Slack.WebHookURLSecretKeySelector
err := s.k8sClient.Get(context.TODO(), types.NamespacedName{Name: selector.Name, Namespace: e.Jenkins.Namespace}, secret)
if err != nil {
return err
}
slackMessage, err := json.Marshal(s.generateMessage(e))
if err != nil {
return err
}
secretValue := string(secret.Data[selector.Key])
if secretValue == "" {
return errors.Errorf("Slack WebHook URL is empty in secret '%s/%s[%s]", e.Jenkins.Namespace, selector.Name, selector.Key)
}
request, err := http.NewRequest("POST", secretValue, bytes.NewBuffer(slackMessage))
if err != nil {
return err
}
resp, err := s.httpClient.Do(request)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
return nil
}