Improve notification mechanism
This commit is contained in:
parent
61d5311ac2
commit
75f95be65a
|
|
@ -3,8 +3,14 @@ package notifier
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mailgun/mailgun-go/v3"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
||||||
|
|
||||||
|
"github.com/mailgun/mailgun-go/v3"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mailgun is service for sending emails
|
// Mailgun is service for sending emails
|
||||||
|
|
@ -15,8 +21,19 @@ type Mailgun struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send is function for sending directly to API
|
// Send is function for sending directly to API
|
||||||
func (m Mailgun) Send(secret string, i *Information) error {
|
func (m Mailgun) Send(n *Notification) error {
|
||||||
mg := mailgun.NewMailgun(m.Domain, secret)
|
var selector v1alpha2.SecretKeySelector
|
||||||
|
secret := &corev1.Secret{}
|
||||||
|
|
||||||
|
i := n.Information
|
||||||
|
|
||||||
|
err := n.K8sClient.Get(context.TODO(), types.NamespacedName{Name: selector.Name, Namespace: n.Jenkins.Namespace}, secret)
|
||||||
|
if err != nil {
|
||||||
|
n.Logger.V(log.VWarn).Info(fmt.Sprintf("Failed to get secret with name `%s`. %+v", selector.Name, err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mg := mailgun.NewMailgun(m.Domain, secret.StringData[selector.Name])
|
||||||
|
|
||||||
content := `
|
content := `
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
|
@ -45,14 +62,14 @@ func (m Mailgun) Send(secret string, i *Information) error {
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
|
||||||
content = fmt.Sprintf(content, getStatusColor(i.Status, m), i.CrName, i.ConfigurationType, getStatusColor(i.Status, m), getStatusName(i.Status))
|
content = fmt.Sprintf(content, getStatusColor(i.LogLevel, m), i.CrName, i.ConfigurationType, getStatusColor(i.LogLevel, m), string(i.LogLevel))
|
||||||
|
|
||||||
msg := mg.NewMessage(fmt.Sprintf("Jenkins Operator Notifier <%s>", m.From), "Jenkins Operator Status", "", m.Recipient)
|
msg := mg.NewMessage(fmt.Sprintf("Jenkins Operator Notifier <%s>", m.From), "Jenkins Operator Status", "", m.Recipient)
|
||||||
msg.SetHtml(content)
|
msg.SetHtml(content)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
_, _, err := mg.Send(ctx, msg)
|
_, _, err = mg.Send(ctx, msg)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,22 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Teams is Microsoft Teams Service
|
// Teams is Microsoft Teams Service
|
||||||
type Teams struct{}
|
type Teams struct {
|
||||||
|
apiURL string
|
||||||
|
}
|
||||||
|
|
||||||
// TeamsMessage is representation of json message structure
|
// TeamsMessage is representation of json message structure
|
||||||
type TeamsMessage struct {
|
type TeamsMessage struct {
|
||||||
|
|
@ -31,20 +41,16 @@ type TeamsFact struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send is function for sending directly to API
|
// Send is function for sending directly to API
|
||||||
func (t Teams) Send(secret string, i *Information) error {
|
func (t Teams) Send(n *Notification) error {
|
||||||
err := i.Error
|
var selector v1alpha2.SecretKeySelector
|
||||||
var errMessage string
|
secret := &corev1.Secret{}
|
||||||
|
|
||||||
if err != nil {
|
i := n.Information
|
||||||
errMessage = err.Error()
|
|
||||||
} else {
|
|
||||||
errMessage = noErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := json.Marshal(TeamsMessage{
|
msg, err := json.Marshal(TeamsMessage{
|
||||||
Type: "MessageCard",
|
Type: "MessageCard",
|
||||||
Context: "https://schema.org/extensions",
|
Context: "https://schema.org/extensions",
|
||||||
ThemeColor: getStatusColor(i.Status, t),
|
ThemeColor: getStatusColor(i.LogLevel, t),
|
||||||
Title: titleText,
|
Title: titleText,
|
||||||
Sections: []TeamsSection{
|
Sections: []TeamsSection{
|
||||||
{
|
{
|
||||||
|
|
@ -58,20 +64,33 @@ func (t Teams) Send(secret string, i *Information) error {
|
||||||
Value: i.ConfigurationType,
|
Value: i.ConfigurationType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: statusFieldName,
|
Name: loggingLevelFieldName,
|
||||||
Value: getStatusName(i.Status),
|
Value: string(i.LogLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: namespaceFieldName,
|
||||||
|
Value: i.Namespace,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Text: errMessage,
|
Text: i.Message,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if t.apiURL == "" {
|
||||||
|
err := n.K8sClient.Get(context.TODO(), types.NamespacedName{Name: selector.Name, Namespace: n.Jenkins.Namespace}, secret)
|
||||||
|
if err != nil {
|
||||||
|
n.Logger.V(log.VWarn).Info(fmt.Sprintf("Failed to get secret with name `%s`. %+v", selector.Name, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.apiURL = secret.StringData[selector.Name]
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
request, err := http.NewRequest("POST", secret, bytes.NewBuffer(msg))
|
request, err := http.NewRequest("POST", t.apiURL, bytes.NewBuffer(msg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -81,10 +100,7 @@ func (t Teams) Send(secret string, i *Information) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = resp.Body.Close()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,25 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTeams_Send(t *testing.T) {
|
func TestTeams_Send(t *testing.T) {
|
||||||
teams := Teams{}
|
|
||||||
|
|
||||||
i := &Information{
|
i := &Information{
|
||||||
ConfigurationType: testConfigurationType,
|
ConfigurationType: testConfigurationType,
|
||||||
CrName: testCrName,
|
CrName: testCrName,
|
||||||
Status: testStatus,
|
Message: testMessage,
|
||||||
Error: testError,
|
MessageVerbose: testMessageVerbose,
|
||||||
|
Namespace: testNamespace,
|
||||||
|
LogLevel: testLoggingLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
notification := &Notification{
|
||||||
|
Information: i,
|
||||||
}
|
}
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -28,32 +33,28 @@ func TestTeams_Send(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, message.Title, titleText)
|
assert.Equal(t, message.Title, titleText)
|
||||||
assert.Equal(t, message.ThemeColor, getStatusColor(i.Status, teams))
|
assert.Equal(t, message.ThemeColor, getStatusColor(i.LogLevel, Teams{}))
|
||||||
|
|
||||||
mainSection := message.Sections[0]
|
mainSection := message.Sections[0]
|
||||||
|
|
||||||
assert.Equal(t, mainSection.Text, noErrorMessage)
|
assert.Equal(t, mainSection.Text, i.Message)
|
||||||
|
|
||||||
for _, fact := range mainSection.Facts {
|
for _, fact := range mainSection.Facts {
|
||||||
switch fact.Name {
|
switch fact.Name {
|
||||||
case configurationTypeFieldName:
|
case configurationTypeFieldName:
|
||||||
if fact.Value != i.ConfigurationType {
|
assert.Equal(t, fact.Value, i.ConfigurationType)
|
||||||
t.Fatalf("%s is not equal! Must be %s", configurationTypeFieldName, i.ConfigurationType)
|
|
||||||
}
|
|
||||||
case crNameFieldName:
|
case crNameFieldName:
|
||||||
if fact.Value != i.CrName {
|
assert.Equal(t, fact.Value, i.CrName)
|
||||||
t.Fatalf("%s is not equal! Must be %s", crNameFieldName, i.CrName)
|
case messageFieldName:
|
||||||
}
|
assert.Equal(t, fact.Value, i.Message)
|
||||||
case statusFieldName:
|
case loggingLevelFieldName:
|
||||||
if fact.Value != getStatusName(i.Status) {
|
assert.Equal(t, fact.Value, string(i.LogLevel))
|
||||||
t.Fatalf("%s is not equal! Must be %s", statusFieldName, getStatusName(i.Status))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
teams := Teams{apiURL: server.URL}
|
||||||
|
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
if err := teams.Send(server.URL, i); err != nil {
|
assert.NoError(t, teams.Send(notification))
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,160 +1,132 @@
|
||||||
package notifier
|
package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
|
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LogWarn is warning log entry
|
||||||
|
LogWarn LoggingLevel = "warn"
|
||||||
|
|
||||||
|
// LogInfo is info log entry
|
||||||
|
LogInfo LoggingLevel = "info"
|
||||||
|
|
||||||
|
titleText = "Operator reconciled."
|
||||||
|
messageFieldName = "Message"
|
||||||
|
loggingLevelFieldName = "Logging Level"
|
||||||
|
crNameFieldName = "CR Name"
|
||||||
|
configurationTypeFieldName = "Configuration Type"
|
||||||
|
namespaceFieldName = "Namespace"
|
||||||
|
footerContent = "Powered by Jenkins Operator <3"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testConfigurationType = "test-configuration"
|
testConfigurationType = "test-configuration"
|
||||||
testCrName = "test-cr"
|
testCrName = "test-cr"
|
||||||
testStatus Status = 1
|
testNamespace = "test-namespace"
|
||||||
testError error
|
testMessage = "test-message"
|
||||||
|
testMessageVerbose = "detail-test-message"
|
||||||
|
testLoggingLevel = LogWarn
|
||||||
|
|
||||||
client = http.Client{}
|
client = http.Client{}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// StatusSuccess contains value for success state
|
|
||||||
StatusSuccess = 0
|
|
||||||
|
|
||||||
// StatusError contains value for error state
|
|
||||||
StatusError = 1
|
|
||||||
|
|
||||||
noErrorMessage = "No errors has found."
|
|
||||||
|
|
||||||
titleText = "Operator reconciled."
|
|
||||||
statusMessageFieldName = "Status message"
|
|
||||||
statusFieldName = "Status"
|
|
||||||
crNameFieldName = "CR Name"
|
|
||||||
configurationTypeFieldName = "Configuration Type"
|
|
||||||
footerContent = "Powered by Jenkins Operator <3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Status represents the state of operator
|
|
||||||
type Status int
|
|
||||||
|
|
||||||
// StatusColor is useful for better UX
|
// StatusColor is useful for better UX
|
||||||
type StatusColor string
|
type StatusColor string
|
||||||
|
|
||||||
|
// LoggingLevel is type for selecting different logging levels
|
||||||
|
type LoggingLevel string
|
||||||
|
|
||||||
// Information represents details about operator status
|
// Information represents details about operator status
|
||||||
type Information struct {
|
type Information struct {
|
||||||
ConfigurationType string
|
ConfigurationType string
|
||||||
|
Namespace string
|
||||||
CrName string
|
CrName string
|
||||||
Status Status
|
LogLevel LoggingLevel
|
||||||
Error error
|
Message string
|
||||||
|
MessageVerbose string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notification contains message which will be sent
|
// Notification contains message which will be sent
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
Jenkins *v1alpha2.Jenkins
|
Jenkins *v1alpha2.Jenkins
|
||||||
K8sClient k8sclient.Client
|
K8sClient k8sclient.Client
|
||||||
Logger logr.Logger
|
Logger logr.Logger
|
||||||
|
|
||||||
// Recipient is mobile number or email address
|
|
||||||
// It's not used in Slack or Microsoft Teams
|
|
||||||
Recipient string
|
|
||||||
|
|
||||||
Information *Information
|
Information *Information
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service is skeleton for additional services
|
// Service is skeleton for additional services
|
||||||
type Service interface {
|
type service interface {
|
||||||
Send(secret string, i *Information) error
|
Send(i *Notification) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen is goroutine that listens for incoming messages and sends it
|
// Listen is goroutine that listens for incoming messages and sends it
|
||||||
func Listen(notification chan *Notification) {
|
func Listen(notification chan *Notification) {
|
||||||
n := <-notification
|
<-notification
|
||||||
if len(n.Jenkins.Spec.Notification) > 0 {
|
for n := range notification {
|
||||||
for _, endpoint := range n.Jenkins.Spec.Notification {
|
if len(n.Jenkins.Spec.Notification) > 0 {
|
||||||
var err error
|
for _, endpoint := range n.Jenkins.Spec.Notification {
|
||||||
var service Service
|
var err error
|
||||||
var selector v1alpha2.SecretKeySelector
|
var svc service
|
||||||
secret := &corev1.Secret{}
|
|
||||||
|
|
||||||
if endpoint.Slack != (v1alpha2.Slack{}) {
|
if endpoint.Slack != (v1alpha2.Slack{}) {
|
||||||
n.Logger.V(log.VDebug).Info("Slack detected")
|
svc = Slack{}
|
||||||
service = Slack{}
|
} else if endpoint.Teams != (v1alpha2.Teams{}) {
|
||||||
selector = endpoint.Slack.URLSecretKeySelector
|
svc = Teams{}
|
||||||
} else if endpoint.Teams != (v1alpha2.Teams{}) {
|
} else if endpoint.Mailgun != (v1alpha2.Mailgun{}) {
|
||||||
n.Logger.V(log.VDebug).Info("Microsoft Teams detected")
|
svc = Mailgun{
|
||||||
service = Teams{}
|
Domain: endpoint.Mailgun.Domain,
|
||||||
selector = endpoint.Teams.URLSecretKeySelector
|
Recipient: endpoint.Mailgun.Recipient,
|
||||||
} else if endpoint.Mailgun != (v1alpha2.Mailgun{}) {
|
From: endpoint.Mailgun.From,
|
||||||
n.Logger.V(log.VDebug).Info("Mailgun detected")
|
}
|
||||||
service = Mailgun{
|
} else {
|
||||||
Domain: endpoint.Mailgun.Domain,
|
n.Logger.V(log.VWarn).Info("Notification service not found or not defined")
|
||||||
Recipient: endpoint.Mailgun.Recipient,
|
|
||||||
From: endpoint.Mailgun.From,
|
|
||||||
}
|
}
|
||||||
selector = endpoint.Mailgun.APIKeySecretKeySelector
|
|
||||||
} else {
|
|
||||||
n.Logger.Info("Notification service not found or not defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = n.K8sClient.Get(context.TODO(), types.NamespacedName{Name: selector.Name, Namespace: n.Jenkins.Namespace}, secret)
|
err = notify(svc, n)
|
||||||
if err != nil {
|
|
||||||
n.Logger.Info(fmt.Sprintf("Failed to get secret with name `%s`. %+v", selector.Name, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
n.Logger.V(log.VDebug).Info(fmt.Sprintf("Endpoint URL: %s", string(secret.Data[selector.Key])))
|
if err != nil {
|
||||||
err = notify(service, string(secret.Data[selector.Key]), n.Information)
|
n.Logger.V(log.VWarn).Info(fmt.Sprintf("Failed to send notifications. %+v", err))
|
||||||
|
} else {
|
||||||
if err != nil {
|
n.Logger.V(log.VDebug).Info("Sent notification")
|
||||||
n.Logger.Info(fmt.Sprintf("Failed to send notifications. %+v", err))
|
}
|
||||||
} else {
|
|
||||||
n.Logger.Info("Sent notification")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatusName(status Status) string {
|
func getStatusColor(logLevel LoggingLevel, svc service) StatusColor {
|
||||||
switch status {
|
switch svc.(type) {
|
||||||
case StatusSuccess:
|
|
||||||
return "Success"
|
|
||||||
case StatusError:
|
|
||||||
return "Error"
|
|
||||||
default:
|
|
||||||
return "Undefined"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStatusColor(status Status, service Service) StatusColor {
|
|
||||||
switch service.(type) {
|
|
||||||
case Slack:
|
case Slack:
|
||||||
switch status {
|
switch logLevel {
|
||||||
case StatusSuccess:
|
case LogInfo:
|
||||||
return "good"
|
return "#439FE0"
|
||||||
case StatusError:
|
case LogWarn:
|
||||||
return "danger"
|
return "danger"
|
||||||
default:
|
default:
|
||||||
return "#c8c8c8"
|
return "#c8c8c8"
|
||||||
}
|
}
|
||||||
case Teams:
|
case Teams:
|
||||||
switch status {
|
switch logLevel {
|
||||||
case StatusSuccess:
|
case LogInfo:
|
||||||
return "54A254"
|
return "439FE0"
|
||||||
case StatusError:
|
case LogWarn:
|
||||||
return "E81123"
|
return "E81123"
|
||||||
default:
|
default:
|
||||||
return "C8C8C8"
|
return "C8C8C8"
|
||||||
}
|
}
|
||||||
case Mailgun:
|
case Mailgun:
|
||||||
switch status {
|
switch logLevel {
|
||||||
case StatusSuccess:
|
case LogInfo:
|
||||||
return "green"
|
return "blue"
|
||||||
case StatusError:
|
case LogWarn:
|
||||||
return "red"
|
return "red"
|
||||||
default:
|
default:
|
||||||
return "gray"
|
return "gray"
|
||||||
|
|
@ -164,15 +136,15 @@ func getStatusColor(status Status, service Service) StatusColor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func notify(service Service, secret string, i *Information) error {
|
func notify(svc service, n *Notification) error {
|
||||||
var err error
|
var err error
|
||||||
switch svc := service.(type) {
|
switch s := svc.(type) {
|
||||||
case Slack:
|
case Slack:
|
||||||
err = svc.Send(secret, i)
|
err = s.Send(n)
|
||||||
case Teams:
|
case Teams:
|
||||||
err = svc.Send(secret, i)
|
err = s.Send(n)
|
||||||
case Mailgun:
|
case Mailgun:
|
||||||
err = svc.Send(secret, i)
|
err = s.Send(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,22 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Slack is messaging service
|
// Slack is messaging service
|
||||||
type Slack struct{}
|
type Slack struct {
|
||||||
|
apiURL string
|
||||||
|
}
|
||||||
|
|
||||||
// SlackMessage is representation of json message
|
// SlackMessage is representation of json message
|
||||||
type SlackMessage struct {
|
type SlackMessage struct {
|
||||||
|
|
@ -34,26 +44,31 @@ type SlackField struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send is function for sending directly to API
|
// Send is function for sending directly to API
|
||||||
func (s Slack) Send(secret string, i *Information) error {
|
func (s Slack) Send(n *Notification) error {
|
||||||
err := i.Error
|
var selector v1alpha2.SecretKeySelector
|
||||||
var errMessage string
|
secret := &corev1.Secret{}
|
||||||
|
|
||||||
if err != nil {
|
i := n.Information
|
||||||
errMessage = err.Error()
|
|
||||||
} else {
|
if s.apiURL == "" {
|
||||||
errMessage = noErrorMessage
|
err := n.K8sClient.Get(context.TODO(), types.NamespacedName{Name: selector.Name, Namespace: n.Jenkins.Namespace}, secret)
|
||||||
|
if err != nil {
|
||||||
|
n.Logger.V(log.VWarn).Info(fmt.Sprintf("Failed to get secret with name `%s`. %+v", selector.Name, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.apiURL = secret.StringData[selector.Name]
|
||||||
}
|
}
|
||||||
|
|
||||||
slackMessage, err := json.Marshal(SlackMessage{
|
slackMessage, err := json.Marshal(SlackMessage{
|
||||||
Attachments: []SlackAttachment{
|
Attachments: []SlackAttachment{
|
||||||
{
|
{
|
||||||
Fallback: "",
|
Fallback: "",
|
||||||
Color: getStatusColor(i.Status, s),
|
Color: getStatusColor(i.LogLevel, s),
|
||||||
Text: titleText,
|
Text: titleText,
|
||||||
Fields: []SlackField{
|
Fields: []SlackField{
|
||||||
{
|
{
|
||||||
Title: statusMessageFieldName,
|
Title: messageFieldName,
|
||||||
Value: errMessage,
|
Value: i.Message,
|
||||||
Short: false,
|
Short: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -66,6 +81,16 @@ func (s Slack) Send(secret string, i *Information) error {
|
||||||
Value: i.ConfigurationType,
|
Value: i.ConfigurationType,
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Title: loggingLevelFieldName,
|
||||||
|
Value: string(i.LogLevel),
|
||||||
|
Short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Title: namespaceFieldName,
|
||||||
|
Value: i.Namespace,
|
||||||
|
Short: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Footer: footerContent,
|
Footer: footerContent,
|
||||||
},
|
},
|
||||||
|
|
@ -76,7 +101,7 @@ func (s Slack) Send(secret string, i *Information) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
request, err := http.NewRequest("POST", secret, bytes.NewBuffer(slackMessage))
|
request, err := http.NewRequest("POST", s.apiURL, bytes.NewBuffer(slackMessage))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -86,10 +111,6 @@ func (s Slack) Send(secret string, i *Information) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = resp.Body.Close()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,25 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSlack_Send(t *testing.T) {
|
func TestSlack_Send(t *testing.T) {
|
||||||
slack := Slack{}
|
|
||||||
|
|
||||||
i := &Information{
|
i := &Information{
|
||||||
ConfigurationType: testConfigurationType,
|
ConfigurationType: testConfigurationType,
|
||||||
CrName: testCrName,
|
CrName: testCrName,
|
||||||
Status: testStatus,
|
Message: testMessage,
|
||||||
Error: testError,
|
MessageVerbose: testMessageVerbose,
|
||||||
|
Namespace: testNamespace,
|
||||||
|
LogLevel: testLoggingLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
notification := &Notification{
|
||||||
|
Information: i,
|
||||||
}
|
}
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -34,27 +39,23 @@ func TestSlack_Send(t *testing.T) {
|
||||||
for _, field := range mainAttachment.Fields {
|
for _, field := range mainAttachment.Fields {
|
||||||
switch field.Title {
|
switch field.Title {
|
||||||
case configurationTypeFieldName:
|
case configurationTypeFieldName:
|
||||||
if field.Value != i.ConfigurationType {
|
assert.Equal(t, field.Value, i.ConfigurationType)
|
||||||
t.Fatalf("%s is not equal! Must be %s", configurationTypeFieldName, i.ConfigurationType)
|
|
||||||
}
|
|
||||||
case crNameFieldName:
|
case crNameFieldName:
|
||||||
if field.Value != i.CrName {
|
assert.Equal(t, field.Value, i.CrName)
|
||||||
t.Fatalf("%s is not equal! Must be %s", crNameFieldName, i.CrName)
|
case messageFieldName:
|
||||||
}
|
assert.Equal(t, field.Value, i.Message)
|
||||||
case statusMessageFieldName:
|
case loggingLevelFieldName:
|
||||||
if field.Value != noErrorMessage {
|
assert.Equal(t, field.Value, string(i.LogLevel))
|
||||||
t.Fatalf("Error thrown but not expected")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, mainAttachment.Footer, footerContent)
|
assert.Equal(t, mainAttachment.Footer, footerContent)
|
||||||
assert.Equal(t, mainAttachment.Color, getStatusColor(i.Status, slack))
|
assert.Equal(t, mainAttachment.Color, getStatusColor(i.LogLevel, Slack{}))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
if err := slack.Send(server.URL, i); err != nil {
|
slack := Slack{apiURL: server.URL}
|
||||||
t.Fatal(err)
|
|
||||||
}
|
assert.NoError(t, slack.Send(notification))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue