@@ -18,7 +18,9 @@ package controllers
18
18
19
19
import (
20
20
"context"
21
+ "encoding/json"
21
22
"fmt"
23
+ "regexp"
22
24
"strconv"
23
25
"strings"
24
26
@@ -128,7 +130,13 @@ func sendSlackNotification(ctx context.Context, c client.Client, clusterNamespac
128
130
l := logger .WithValues ("channel" , info .channelID )
129
131
l .V (logs .LogInfo ).Info ("send slack message" )
130
132
131
- message , _ := getNotificationMessage (clusterNamespace , clusterName , clusterType , conditions , logger )
133
+ message , passing := getNotificationMessage (clusterNamespace , clusterName , clusterType , conditions , logger )
134
+
135
+ msgSlack , err := composeSlackMessage (message , passing )
136
+ if err != nil {
137
+ l .V (logs .LogInfo ).Info ("failed to format slack message: %v" , err )
138
+ return err
139
+ }
132
140
133
141
api := slack .New (info .token )
134
142
if api == nil {
@@ -137,7 +145,8 @@ func sendSlackNotification(ctx context.Context, c client.Client, clusterNamespac
137
145
138
146
l .V (logs .LogDebug ).Info (fmt .Sprintf ("Sending message to channel %s" , info .channelID ))
139
147
140
- _ , _ , err = api .PostMessage (info .channelID , slack .MsgOptionText (message , false ))
148
+ _ , _ , err = api .PostMessage (info .channelID , slack .MsgOptionText ("ProjectSveltos Updates" , false ),
149
+ slack .MsgOptionAttachments (msgSlack ))
141
150
if err != nil {
142
151
l .V (logs .LogInfo ).Info (fmt .Sprintf ("Failed to send message. Error: %v" , err ))
143
152
return err
@@ -155,7 +164,13 @@ func sendWebexNotification(ctx context.Context, c client.Client, clusterNamespac
155
164
return err
156
165
}
157
166
158
- message , _ := getNotificationMessage (clusterNamespace , clusterName , clusterType , conditions , logger )
167
+ message , passing := getNotificationMessage (clusterNamespace , clusterName , clusterType , conditions , logger )
168
+
169
+ formattedMessage , err := composeWebexMessage (message , passing , logger )
170
+ if err != nil {
171
+ logger .V (logs .LogInfo ).Info ("failed to format webex message: %v" , err )
172
+ return err
173
+ }
159
174
160
175
webexClient := webexteams .NewClient ()
161
176
if webexClient == nil {
@@ -172,6 +187,10 @@ func sendWebexNotification(ctx context.Context, c client.Client, clusterNamespac
172
187
webexMessage := & webexteams.MessageCreateRequest {
173
188
RoomID : info .room ,
174
189
Markdown : message ,
190
+ Attachments : []webexteams.Attachment {{
191
+ ContentType : webexContentType ,
192
+ Content : formattedMessage ,
193
+ }},
175
194
}
176
195
177
196
_ , resp , err := webexClient .Messages .CreateMessage (webexMessage )
@@ -199,7 +218,14 @@ func sendDiscordNotification(ctx context.Context, c client.Client, clusterNamesp
199
218
l := logger .WithValues ("channel" , info .channelID )
200
219
l .V (logs .LogInfo ).Info ("send discord message" )
201
220
202
- message , _ := getNotificationMessage (clusterNamespace , clusterName , clusterType , conditions , logger )
221
+ message , passing := getNotificationMessage (clusterNamespace , clusterName , clusterType , conditions , logger )
222
+
223
+ // Format Message
224
+ discordReply , err := composeDiscordMessage (message , passing )
225
+ if err != nil {
226
+ l .V (logs .LogInfo ).Info ("failed to format discord message: %v" , err )
227
+ return err
228
+ }
203
229
204
230
// Create a new Discord session using the provided token
205
231
dg , err := discordgo .New ("Bot " + info .token )
@@ -208,9 +234,10 @@ func sendDiscordNotification(ctx context.Context, c client.Client, clusterNamesp
208
234
return err
209
235
}
210
236
211
- // Create a new message with both a text content and the file attachment
237
+ // Send message with formatted message in embeds
212
238
_ , err = dg .ChannelMessageSendComplex (info .channelID , & discordgo.MessageSend {
213
- Content : message ,
239
+ Content : "ProjectSveltos Updates" ,
240
+ Embeds : discordReply ,
214
241
})
215
242
216
243
return err
@@ -228,7 +255,28 @@ func sendTeamsNotification(ctx context.Context, c client.Client, clusterNamespac
228
255
l := logger .WithValues ("webhookUrl" , info .webhookUrl )
229
256
l .V (logs .LogInfo ).Info ("send teams message" )
230
257
231
- message , _ := getNotificationMessage (clusterNamespace , clusterName , clusterType , conditions , logger )
258
+ message , passing := getNotificationMessage (clusterNamespace , clusterName , clusterType , conditions , logger )
259
+
260
+ // Format message using adaptive cards
261
+ card , err := composeTeamsMessage (message , passing , logger )
262
+ if err != nil {
263
+ l .V (logs .LogInfo ).Info ("failed to format teams message: %v" , err )
264
+ return err
265
+ }
266
+
267
+ // Create message and attach card
268
+ teamsMessage := & adaptivecard.Message {Type : adaptivecard .TypeMessage }
269
+ if err := teamsMessage .Attach (card ); err != nil {
270
+ l .V (logs .LogInfo ).Info ("failed to add teams card: %v" , err )
271
+ return err
272
+ }
273
+
274
+ // Prepare message
275
+ if err := teamsMessage .Prepare (); err != nil {
276
+ l .V (logs .LogInfo ).Info ("failed to prepare teams message payload: %v" , err )
277
+ return err
278
+ }
279
+
232
280
teamsClient := goteamsnotify .NewTeamsClient ()
233
281
234
282
// Validate Teams Webhook expected format
@@ -237,14 +285,7 @@ func sendTeamsNotification(ctx context.Context, c client.Client, clusterNamespac
237
285
return err
238
286
}
239
287
240
- // Create adaptive card with the clusterName as the title of the message
241
- teamsMessage , err := adaptivecard .NewSimpleMessage (message , clusterName , true )
242
- if err != nil {
243
- l .V (logs .LogInfo ).Info ("failed to create Teams message: %v" , err )
244
- return err
245
- }
246
-
247
- // Send the meesage with the user provided webhook URL
288
+ // Send the message with the user provided webhook URL
248
289
if teamsClient .Send (info .webhookUrl , teamsMessage ) != nil {
249
290
l .V (logs .LogInfo ).Info ("failed to send Teams message: %v" , err )
250
291
return err
@@ -302,18 +343,18 @@ func getNotificationMessage(clusterNamespace, clusterName string, clusterType li
302
343
conditions []libsveltosv1beta1.Condition , logger logr.Logger ) (string , bool ) {
303
344
304
345
passing := true
305
- message := fmt .Sprintf ("cluster %s:%s/%s \n " , clusterType , clusterNamespace , clusterName )
346
+ message := fmt .Sprintf ("Cluster %s:%s/%s \n " , clusterType , clusterNamespace , clusterName )
306
347
for i := range conditions {
307
348
c := & conditions [i ]
308
349
if c .Status != corev1 .ConditionTrue {
309
350
passing = false
310
- message += fmt .Sprintf ("liveness check %q failing \n " , c .Type )
351
+ message += fmt .Sprintf ("Liveness check %q failing \n " , c .Type )
311
352
message += fmt .Sprintf ("%s \n " , c .Message )
312
353
}
313
354
}
314
355
315
356
if passing {
316
- message += "all liveness checks are passing"
357
+ message += "All liveness checks are passing"
317
358
logger .V (logs .LogDebug ).Info ("all liveness checks are passing" )
318
359
} else {
319
360
logger .V (logs .LogDebug ).Info ("some of the liveness checks are not passing" )
@@ -488,3 +529,172 @@ func getTelegramInfo(ctx context.Context, c client.Client, n *libsveltosv1beta1.
488
529
489
530
return & telegramInfo {token : string (authToken ), chatID : chatID }, nil
490
531
}
532
+
533
+ func composeDiscordMessage (message string , passing bool ) ([]* discordgo.MessageEmbed , error ) {
534
+ lines := strings .Split (message , "\n " )
535
+ if len (lines ) == 0 {
536
+ embed := []* discordgo.MessageEmbed {{
537
+ Type : discordgo .EmbedTypeRich ,
538
+ Title : "no message" ,
539
+ }}
540
+ return embed , fmt .Errorf ("empty message" )
541
+ }
542
+
543
+ description := "Failing some checks."
544
+ color := discordRed
545
+ if passing {
546
+ description = "Passing!"
547
+ color = discordGreen
548
+ }
549
+
550
+ content := []* discordgo.MessageEmbedField {}
551
+ title := lines [0 ]
552
+ name := ""
553
+ val := ""
554
+ empty := true
555
+
556
+ fail_reg := regexp .MustCompile (failedTestRegexp )
557
+
558
+ for _ , line := range lines [1 :] {
559
+ if fail_reg .MatchString (line ) {
560
+ if name != "" {
561
+ content = append (content , & discordgo.MessageEmbedField {Name : name , Value : val })
562
+ empty = true
563
+ }
564
+ name = line
565
+ } else if empty {
566
+ val = line
567
+ empty = false
568
+ } else {
569
+ val += "\n " + line
570
+ }
571
+ }
572
+ content = append (content , & discordgo.MessageEmbedField {Name : name , Value : val })
573
+
574
+ embed := []* discordgo.MessageEmbed {{
575
+ Type : discordgo .EmbedTypeRich ,
576
+ Title : title ,
577
+ Description : description ,
578
+ Color : color ,
579
+ Fields : content ,
580
+ }}
581
+ return embed , nil
582
+ }
583
+
584
+ func composeTeamsMessage (message string , passing bool , logger logr.Logger ) (adaptivecard.Card , error ) {
585
+ card := adaptivecard .NewCard ()
586
+
587
+ lines := strings .Split (message , "\n " )
588
+ if len (lines ) == 0 {
589
+ return card , fmt .Errorf ("empty message" )
590
+ }
591
+
592
+ titleBlock := adaptivecard .NewTextBlock (lines [0 ], true )
593
+ titleBlock .Weight = adaptivecard .WeightBolder
594
+ titleBlock .Size = adaptivecard .SizeLarge
595
+
596
+ // Adding title to card
597
+ if err := card .AddElement (false , titleBlock ); err != nil {
598
+ logger .V (logs .LogDebug ).Info ("error adding card" )
599
+ }
600
+
601
+ if passing {
602
+ titleBlock .Text = "Passing! \n "
603
+ titleBlock .Color = adaptivecard .ColorGood
604
+ } else {
605
+ titleBlock .Text = "Failing some checks. \n "
606
+ titleBlock .Color = adaptivecard .ColorAttention
607
+ }
608
+
609
+ // Adding msg summary -- all tests passing or some failing
610
+ if err := card .AddElement (false , titleBlock ); err != nil {
611
+ logger .V (logs .LogDebug ).Info ("error adding card" )
612
+ }
613
+
614
+ // Adding remaining msg to card
615
+ fail_regex := regexp .MustCompile (failedTestRegexp )
616
+
617
+ for _ , line := range lines [1 :] {
618
+ textblock := adaptivecard .NewTextBlock (line , true )
619
+
620
+ if fail_regex .MatchString (line ) {
621
+ textblock .Color = adaptivecard .ColorAttention
622
+ textblock .Separator = true
623
+ } else {
624
+ textblock .Separator = false
625
+ textblock .Color = adaptivecard .ColorDefault
626
+ }
627
+
628
+ if err := card .AddElement (false , textblock ); err != nil {
629
+ logger .V (logs .LogDebug ).Info ("error adding card" )
630
+ }
631
+ }
632
+ return card , nil
633
+ }
634
+
635
+ func composeWebexMessage (message string , passing bool , logger logr.Logger ) (map [string ]interface {}, error ) {
636
+ cardData := map [string ]interface {}{}
637
+
638
+ // using addaptivecard from teams formatting
639
+ card , err := composeTeamsMessage (message , passing , logger )
640
+ if err != nil {
641
+ return cardData , err
642
+ }
643
+
644
+ // fixing version/schema for webex compatibility
645
+ card .Version = webexAdaptiveCardVersion
646
+ card .Schema = webexAdaptiveCardSchema
647
+
648
+ // convert adaptiveCard to map[string]interface{}
649
+ jsonBytes , err := json .MarshalIndent (card , "" , " " )
650
+ if err != nil {
651
+ logger .V (logs .LogDebug ).Info ("Error Marshaling Card" )
652
+ return cardData , err
653
+ }
654
+
655
+ err = json .Unmarshal (jsonBytes , & cardData )
656
+ if err != nil {
657
+ logger .V (logs .LogDebug ).Info ("Error Unmarshaling Card" )
658
+ return cardData , err
659
+ }
660
+
661
+ // Remove added field for teams adaptive card : not required for webex
662
+ delete (cardData , webexCardFieldMSTeams )
663
+
664
+ return cardData , nil
665
+ }
666
+
667
+ func composeSlackMessage (message string , passing bool ) (slack.Attachment , error ) {
668
+ attachment := slack.Attachment {
669
+ MarkdownIn : []string {"text" },
670
+ }
671
+
672
+ lines := strings .Split (message , "\n " )
673
+ if len (lines ) == 0 {
674
+ return attachment , fmt .Errorf ("empty message" )
675
+ }
676
+
677
+ // adding color to summarize msg
678
+ color := slackRed
679
+ if passing {
680
+ color = slackGreen
681
+ }
682
+ attachment .Color = color
683
+
684
+ // adding title
685
+ attachment .Title = lines [0 ]
686
+
687
+ // adding the remaining msg
688
+ markdownText := strings.Builder {}
689
+ fail_reg := regexp .MustCompile (failedTestRegexp )
690
+
691
+ for _ , line := range lines [1 :] {
692
+ if fail_reg .MatchString (line ) {
693
+ markdownText .WriteString ("*" + line + "*\n " )
694
+ } else {
695
+ markdownText .WriteString (line + "\n " )
696
+ }
697
+ }
698
+ attachment .Text = markdownText .String ()
699
+ return attachment , nil
700
+ }
0 commit comments