@@ -78,10 +78,73 @@ func sendTestSlackWebhook(webhookURL string) error {
7878 return nil
7979}
8080
81+ func sendTestTeamsWebhook (webhookURL string ) error {
82+ messageCard := map [string ]interface {}{
83+ "@type" : "MessageCard" ,
84+ "@context" : "http://schema.org/extensions" ,
85+ "themeColor" : "0076D7" ,
86+ "summary" : "Digger Drift Detection Test" ,
87+ "sections" : []map [string ]interface {}{
88+ {
89+ "activityTitle" : "Digger Drift Detection" ,
90+ "activitySubtitle" : "Test Notification" ,
91+ "activityText" : "This is a test notification to verify your MS Teams integration is working correctly." ,
92+ "facts" : []map [string ]string {
93+ {"name" : "Project" , "value" : "Dev environment" },
94+ {"name" : "Status" , "value" : "🟡 Drift detected" },
95+ },
96+ },
97+ {
98+ "activityTitle" : "Environment Status" ,
99+ "activitySubtitle" : "Current Status Overview" ,
100+ "facts" : []map [string ]string {
101+ {"name" : "Dev environment" , "value" : "🟡 Drift detected" },
102+ {"name" : "Staging environment" , "value" : "⚪ Acknowledged drift" },
103+ {"name" : "Prod environment" , "value" : "🟢 No drift" },
104+ },
105+ },
106+ {
107+ "activityTitle" : "Note" ,
108+ "activityText" : "✅ This is a test notification" ,
109+ },
110+ },
111+ "potentialAction" : []map [string ]interface {}{
112+ {
113+ "@type" : "OpenUri" ,
114+ "name" : "View Dashboard" ,
115+ "targets" : []map [string ]interface {}{
116+ {"os" : "default" , "uri" : os .Getenv ("DIGGER_APP_URL" )},
117+ },
118+ },
119+ },
120+ }
121+
122+ jsonPayload , err := json .Marshal (messageCard )
123+ if err != nil {
124+ return fmt .Errorf ("error marshalling JSON: %v" , err )
125+ }
126+
127+ resp , err := http .Post (webhookURL , "application/json" , bytes .NewBuffer (jsonPayload ))
128+ if err != nil {
129+ return fmt .Errorf ("error sending POST request: %v" , err )
130+ }
131+ defer resp .Body .Close ()
132+
133+ if resp .StatusCode != http .StatusOK {
134+ return fmt .Errorf ("non-OK HTTP status: %s" , resp .Status )
135+ }
136+
137+ return nil
138+ }
139+
81140type TestSlackNotificationForUrl struct {
82141 SlackNotificationUrl string `json:"notification_url"`
83142}
84143
144+ type TestTeamsNotificationForUrl struct {
145+ TeamsNotificationUrl string `json:"notification_url"`
146+ }
147+
85148func (mc MainController ) SendTestSlackNotificationForUrl (c * gin.Context ) {
86149 var request TestSlackNotificationForUrl
87150 err := c .BindJSON (& request )
@@ -102,6 +165,26 @@ func (mc MainController) SendTestSlackNotificationForUrl(c *gin.Context) {
102165 c .String (200 , "ok" )
103166}
104167
168+ func (mc MainController ) SendTestTeamsNotificationForUrl (c * gin.Context ) {
169+ var request TestTeamsNotificationForUrl
170+ err := c .BindJSON (& request )
171+ if err != nil {
172+ log .Printf ("Error binding JSON: %v" , err )
173+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Error binding JSON" })
174+ return
175+ }
176+ teamsNotificationUrl := request .TeamsNotificationUrl
177+
178+ err = sendTestTeamsWebhook (teamsNotificationUrl )
179+ if err != nil {
180+ log .Printf ("Error sending teams notification: %v" , err )
181+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Error sending teams notification" })
182+ return
183+ }
184+
185+ c .String (200 , "ok" )
186+ }
187+
105188func sectionBlockForProject (project models.Project ) (* slack.SectionBlock , error ) {
106189 switch project .DriftStatus {
107190 case models .DriftStatusNoDrift :
@@ -217,6 +300,128 @@ func (mc MainController) SendRealSlackNotificationForOrg(c *gin.Context) {
217300 c .String (200 , "ok" )
218301}
219302
303+ func createTeamsMessageCardForProjects (projects []* models.Project ) map [string ]interface {} {
304+ facts := []map [string ]string {
305+ {"name" : "Project" , "value" : "Status" },
306+ }
307+
308+ var sections []map [string ]interface {}
309+
310+ for _ , project := range projects {
311+ if project .DriftEnabled {
312+ var statusValue string
313+
314+ switch project .DriftStatus {
315+ case models .DriftStatusNoDrift :
316+ statusValue = "🟢 No Drift"
317+ case models .DriftStatusAcknowledgeDrift :
318+ statusValue = "⚪ Acknowledged Drift"
319+ case models .DriftStatusNewDrift :
320+ statusValue = "🟡 Drift detected"
321+ default :
322+ statusValue = "❓ Unknown"
323+ }
324+
325+ facts = append (facts , map [string ]string {
326+ "name" : project .Name ,
327+ "value" : statusValue ,
328+ })
329+ }
330+ }
331+
332+ section := map [string ]interface {}{
333+ "activityTitle" : "Digger Drift Detection Report" ,
334+ "activitySubtitle" : fmt .Sprintf ("Found %d projects with drift enabled" , len (facts )- 1 ),
335+ "facts" : facts ,
336+ }
337+
338+ sections = append (sections , section )
339+
340+ messageCard := map [string ]interface {}{
341+ "@type" : "MessageCard" ,
342+ "@context" : "http://schema.org/extensions" ,
343+ "themeColor" : "0076D7" ,
344+ "summary" : "Digger Drift Detection Report" ,
345+ "sections" : sections ,
346+ "potentialAction" : []map [string ]interface {}{
347+ {
348+ "@type" : "OpenUri" ,
349+ "name" : "View Dashboard" ,
350+ "targets" : []map [string ]interface {}{
351+ {"os" : "default" , "uri" : os .Getenv ("DIGGER_APP_URL" )},
352+ },
353+ },
354+ },
355+ }
356+
357+ return messageCard
358+ }
359+
360+ func (mc MainController ) SendRealTeamsNotificationForOrg (c * gin.Context ) {
361+ var request RealSlackNotificationForOrgRequest
362+ err := c .BindJSON (& request )
363+ if err != nil {
364+ log .Printf ("Error binding JSON: %v" , err )
365+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Error binding JSON" })
366+ return
367+ }
368+ orgId := request .OrgId
369+
370+ org , err := models .DB .GetOrganisationById (orgId )
371+ if err != nil {
372+ log .Printf ("could not get org %v err: %v" , orgId , err )
373+ c .JSON (http .StatusInternalServerError , gin.H {"error" : fmt .Sprintf ("Could not get org %v" , orgId )})
374+ return
375+ }
376+
377+ teamsNotificationUrl := org .DriftTeamsWebhookUrl
378+
379+ projects , err := models .DB .LoadProjectsForOrg (orgId )
380+ if err != nil {
381+ log .Printf ("could not load projects for org %v err: %v" , orgId , err )
382+ c .JSON (http .StatusInternalServerError , gin.H {"error" : fmt .Sprintf ("Could not load projects for org %v" , orgId )})
383+ return
384+ }
385+
386+ numOfProjectsWithDriftEnabled := 0
387+ for _ , project := range projects {
388+ if project .DriftEnabled {
389+ numOfProjectsWithDriftEnabled ++
390+ }
391+ }
392+
393+ if numOfProjectsWithDriftEnabled == 0 {
394+ log .Printf ("no projects with drift enabled for org: %v, succeeding" , orgId )
395+ c .String (200 , "ok" )
396+ return
397+ }
398+
399+ messageCard := createTeamsMessageCardForProjects (projects )
400+
401+ jsonPayload , err := json .Marshal (messageCard )
402+ if err != nil {
403+ log .Printf ("error marshalling teams message card: %v" , err )
404+ c .JSON (500 , gin.H {"error" : "error marshalling teams message card" })
405+ return
406+ }
407+
408+ resp , err := http .Post (teamsNotificationUrl , "application/json" , bytes .NewBuffer (jsonPayload ))
409+ if err != nil {
410+ log .Printf ("error sending teams webhook: %v" , err )
411+ c .JSON (500 , gin.H {"error" : "error sending teams webhook" })
412+ return
413+ }
414+ defer resp .Body .Close ()
415+
416+ if resp .StatusCode != http .StatusOK {
417+ log .Printf ("teams webhook got unexpected status for org: %v - status: %v" , org .ID , resp .StatusCode )
418+ c .JSON (500 , gin.H {"error" : "teams webhook got unexpected status" })
419+ return
420+ }
421+
422+ c .String (200 , "ok" )
423+ }
424+
220425func (mc MainController ) ProcessAllNotifications (c * gin.Context ) {
221426 diggerHostname := os .Getenv ("DIGGER_HOSTNAME" )
222427 webhookSecret := os .Getenv ("DIGGER_WEBHOOK_SECRET" )
@@ -233,6 +438,13 @@ func (mc MainController) ProcessAllNotifications(c *gin.Context) {
233438 return
234439 }
235440
441+ sendTeamsNotificationUrl , err := url .JoinPath (diggerHostname , "_internal/send_teams_notification_for_org" )
442+ if err != nil {
443+ log .Printf ("could not form teams drift url: %v" , err )
444+ c .JSON (500 , gin.H {"error" : "could not form teams drift url" })
445+ return
446+ }
447+
236448 for _ , org := range orgs {
237449 if org .DriftEnabled == false {
238450 continue
@@ -280,6 +492,45 @@ func (mc MainController) ProcessAllNotifications(c *gin.Context) {
280492 if statusCode != 200 {
281493 log .Printf ("send slack notification got unexpected status for org: %v - status: %v" , org .ID , statusCode )
282494 }
495+
496+ // Send MS Teams notification if webhook URL is configured
497+ if org .DriftTeamsWebhookUrl != "" {
498+ fmt .Println ("Sending teams notification for org ID: " , org .ID )
499+ teamsPayload := RealSlackNotificationForOrgRequest {OrgId : org .ID }
500+
501+ // Convert payload to JSON
502+ teamsJsonPayload , err := json .Marshal (teamsPayload )
503+ if err != nil {
504+ fmt .Println ("Process Teams notification: error marshaling JSON:" , err )
505+ continue
506+ }
507+
508+ // Create a new request for MS Teams
509+ teamsReq , err := http .NewRequest ("POST" , sendTeamsNotificationUrl , bytes .NewBuffer (teamsJsonPayload ))
510+ if err != nil {
511+ fmt .Println ("Process teams notification: Error creating request:" , err )
512+ continue
513+ }
514+
515+ // Set headers
516+ teamsReq .Header .Set ("Content-Type" , "application/json" )
517+ teamsReq .Header .Set ("Authorization" , fmt .Sprintf ("Bearer %v" , webhookSecret ))
518+
519+ // Send the request
520+ teamsClient := & http.Client {}
521+ teamsResp , err := teamsClient .Do (teamsReq )
522+ if err != nil {
523+ fmt .Println ("Error sending teams request:" , err )
524+ continue
525+ }
526+ teamsResp .Body .Close ()
527+
528+ // Get the status code
529+ teamsStatusCode := teamsResp .StatusCode
530+ if teamsStatusCode != 200 {
531+ log .Printf ("send teams notification got unexpected status for org: %v - status: %v" , org .ID , teamsStatusCode )
532+ }
533+ }
283534 }
284535 }
285536
0 commit comments