From f99884f7819ffd7bbab9cfdb0c0e89f8b71635ae Mon Sep 17 00:00:00 2001 From: Pulkit Kathuria Date: Wed, 10 Jul 2024 10:25:37 +0900 Subject: [PATCH 1/6] Update code to support new Microsoft Teams webhook JSON format --- alert_test.go | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++ ms_teams.go | 81 ++++++++++++++++------------ 2 files changed, 193 insertions(+), 34 deletions(-) diff --git a/alert_test.go b/alert_test.go index 717890d..453adb5 100644 --- a/alert_test.go +++ b/alert_test.go @@ -282,3 +282,149 @@ func TestAlert_isThrottlingEnabled(t *testing.T) { }) } } + +func TestNewMsTeam(t *testing.T) { + tests := []struct { + name string + err error + expandos *Expandos + want MsTeam + }{ + { + name: "default", + err: errors.New("test error"), + expandos: &Expandos{ + MsTeamsAlertCardSubject: "Test Alert", + MsTeamsCardSubject: "Test Card", + MsTeamsError: "Test Error", + }, + want: MsTeam{ + Type: "AdaptiveCard", + Version: "1.2", + Body: []BodyStruct{ + { + Type: "TextBlock", + Text: "Test Alert", + Items: []ItemStruct{ + { + Type: "TextBlock", + Text: "Test Card", + Weight: "Bolder", + Size: "Medium", + }, + { + Type: "TextBlock", + Text: "Error: Test Error", + Weight: "Lighter", + Size: "Small", + }, + }, + }, + }, + Actions: []ActionStruct{ + { + Type: "Action.OpenUrl", + Title: "View Details", + URL: os.Getenv("MS_TEAMS_WEBHOOK"), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewMsTeam(tt.err, tt.expandos) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewMsTeam() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMsTeam_Send(t *testing.T) { + tests := []struct { + name string + card MsTeam + wantErr bool + }{ + { + name: "send_success", + card: MsTeam{ + Type: "AdaptiveCard", + Version: "1.2", + Body: []BodyStruct{ + { + Type: "TextBlock", + Text: "Test Alert", + Items: []ItemStruct{ + { + Type: "TextBlock", + Text: "Test Card", + Weight: "Bolder", + Size: "Medium", + }, + { + Type: "TextBlock", + Text: "Error: Test Error", + Weight: "Lighter", + Size: "Small", + }, + }, + }, + }, + Actions: []ActionStruct{ + { + Type: "Action.OpenUrl", + Title: "View Details", + URL: os.Getenv("MS_TEAMS_WEBHOOK"), + }, + }, + }, + wantErr: false, + }, + { + name: "send_failure", + card: MsTeam{ + Type: "AdaptiveCard", + Version: "1.2", + Body: []BodyStruct{ + { + Type: "TextBlock", + Text: "Test Alert", + Items: []ItemStruct{ + { + Type: "TextBlock", + Text: "Test Card", + Weight: "Bolder", + Size: "Medium", + }, + { + Type: "TextBlock", + Text: "Error: Test Error", + Weight: "Lighter", + Size: "Small", + }, + }, + }, + }, + Actions: []ActionStruct{ + { + Type: "Action.OpenUrl", + Title: "View Details", + URL: "", + }, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.card.Send(); (err != nil) != tt.wantErr { + t.Errorf("MsTeam.Send() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/ms_teams.go b/ms_teams.go index 4eefb61..59e4bef 100644 --- a/ms_teams.go +++ b/ms_teams.go @@ -12,28 +12,34 @@ import ( "time" ) -// MsTeam is MessageCard for Team notification +// MsTeam is AdaptiveCard for Team notification type MsTeam struct { - Type string `json:"@type"` - Context string `json:"@context"` - Summary string `json:"summary"` - ThemeColor string `json:"themeColor"` - Title string `json:"title"` - Sections []SectionStruct `json:"sections"` + Type string `json:"type"` + Version string `json:"version"` + Body []BodyStruct `json:"body"` + Actions []ActionStruct `json:"actions"` } -// SectionStruct is sub-struct of MsTeam -type SectionStruct struct { - ActivityTitle string `json:"activityTitle"` - ActivitySubtitle string `json:"activitySubtitle"` - ActivityImage string `json:"activityImage"` - Facts []FactStruct `json:"facts"` +// BodyStruct is sub-struct of MsTeam +type BodyStruct struct { + Type string `json:"type"` + Text string `json:"text"` + Items []ItemStruct `json:"items"` } -// FactStruct is sub-struct of SectionStruct -type FactStruct struct { - Name string `json:"name"` - Value string `json:"value"` +// ItemStruct is sub-struct of BodyStruct +type ItemStruct struct { + Type string `json:"type"` + Text string `json:"text"` + Weight string `json:"weight"` + Size string `json:"size"` +} + +// ActionStruct is sub-struct of MsTeam +type ActionStruct struct { + Type string `json:"type"` + Title string `json:"title"` + URL string `json:"url"` } // NewMsTeam is used to create MsTeam @@ -55,28 +61,35 @@ func NewMsTeam(err error, expandos *Expandos) MsTeam { } notificationCard := MsTeam{ - Type: "MessageCard", - Context: "http://schema.org/extensions", - Summary: summary, - ThemeColor: os.Getenv("ALERT_THEME_COLOR"), - Title: title, - Sections: []SectionStruct{ - SectionStruct{ - ActivityTitle: summary, - ActivitySubtitle: fmt.Sprintf("error has occured on %v", os.Getenv("APP_NAME")), - ActivityImage: "", - Facts: []FactStruct{ - FactStruct{ - Name: "Environment:", - Value: os.Getenv("APP_ENV"), + Type: "AdaptiveCard", + Version: "1.2", + Body: []BodyStruct{ + BodyStruct{ + Type: "TextBlock", + Text: title, + Items: []ItemStruct{ + ItemStruct{ + Type: "TextBlock", + Text: summary, + Weight: "Bolder", + Size: "Medium", }, - FactStruct{ - Name: "ERROR", - Value: errMsg, + ItemStruct{ + Type: "TextBlock", + Text: fmt.Sprintf("Error: %s", errMsg), + Weight: "Lighter", + Size: "Small", }, }, }, }, + Actions: []ActionStruct{ + ActionStruct{ + Type: "Action.OpenUrl", + Title: "View Details", + URL: os.Getenv("MS_TEAMS_WEBHOOK"), + }, + }, } return notificationCard } From ae8397521951dafc0510433ca8ebd5bf41480a43 Mon Sep 17 00:00:00 2001 From: Gabriel Vasile Date: Mon, 22 Jul 2024 14:06:34 +0900 Subject: [PATCH 2/6] Revert "Update code to support new Microsoft Teams webhook JSON format" This reverts commit f99884f7819ffd7bbab9cfdb0c0e89f8b71635ae. --- alert_test.go | 146 -------------------------------------------------- ms_teams.go | 81 ++++++++++++---------------- 2 files changed, 34 insertions(+), 193 deletions(-) diff --git a/alert_test.go b/alert_test.go index 453adb5..717890d 100644 --- a/alert_test.go +++ b/alert_test.go @@ -282,149 +282,3 @@ func TestAlert_isThrottlingEnabled(t *testing.T) { }) } } - -func TestNewMsTeam(t *testing.T) { - tests := []struct { - name string - err error - expandos *Expandos - want MsTeam - }{ - { - name: "default", - err: errors.New("test error"), - expandos: &Expandos{ - MsTeamsAlertCardSubject: "Test Alert", - MsTeamsCardSubject: "Test Card", - MsTeamsError: "Test Error", - }, - want: MsTeam{ - Type: "AdaptiveCard", - Version: "1.2", - Body: []BodyStruct{ - { - Type: "TextBlock", - Text: "Test Alert", - Items: []ItemStruct{ - { - Type: "TextBlock", - Text: "Test Card", - Weight: "Bolder", - Size: "Medium", - }, - { - Type: "TextBlock", - Text: "Error: Test Error", - Weight: "Lighter", - Size: "Small", - }, - }, - }, - }, - Actions: []ActionStruct{ - { - Type: "Action.OpenUrl", - Title: "View Details", - URL: os.Getenv("MS_TEAMS_WEBHOOK"), - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := NewMsTeam(tt.err, tt.expandos) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewMsTeam() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMsTeam_Send(t *testing.T) { - tests := []struct { - name string - card MsTeam - wantErr bool - }{ - { - name: "send_success", - card: MsTeam{ - Type: "AdaptiveCard", - Version: "1.2", - Body: []BodyStruct{ - { - Type: "TextBlock", - Text: "Test Alert", - Items: []ItemStruct{ - { - Type: "TextBlock", - Text: "Test Card", - Weight: "Bolder", - Size: "Medium", - }, - { - Type: "TextBlock", - Text: "Error: Test Error", - Weight: "Lighter", - Size: "Small", - }, - }, - }, - }, - Actions: []ActionStruct{ - { - Type: "Action.OpenUrl", - Title: "View Details", - URL: os.Getenv("MS_TEAMS_WEBHOOK"), - }, - }, - }, - wantErr: false, - }, - { - name: "send_failure", - card: MsTeam{ - Type: "AdaptiveCard", - Version: "1.2", - Body: []BodyStruct{ - { - Type: "TextBlock", - Text: "Test Alert", - Items: []ItemStruct{ - { - Type: "TextBlock", - Text: "Test Card", - Weight: "Bolder", - Size: "Medium", - }, - { - Type: "TextBlock", - Text: "Error: Test Error", - Weight: "Lighter", - Size: "Small", - }, - }, - }, - }, - Actions: []ActionStruct{ - { - Type: "Action.OpenUrl", - Title: "View Details", - URL: "", - }, - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.card.Send(); (err != nil) != tt.wantErr { - t.Errorf("MsTeam.Send() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/ms_teams.go b/ms_teams.go index 59e4bef..4eefb61 100644 --- a/ms_teams.go +++ b/ms_teams.go @@ -12,34 +12,28 @@ import ( "time" ) -// MsTeam is AdaptiveCard for Team notification +// MsTeam is MessageCard for Team notification type MsTeam struct { - Type string `json:"type"` - Version string `json:"version"` - Body []BodyStruct `json:"body"` - Actions []ActionStruct `json:"actions"` + Type string `json:"@type"` + Context string `json:"@context"` + Summary string `json:"summary"` + ThemeColor string `json:"themeColor"` + Title string `json:"title"` + Sections []SectionStruct `json:"sections"` } -// BodyStruct is sub-struct of MsTeam -type BodyStruct struct { - Type string `json:"type"` - Text string `json:"text"` - Items []ItemStruct `json:"items"` +// SectionStruct is sub-struct of MsTeam +type SectionStruct struct { + ActivityTitle string `json:"activityTitle"` + ActivitySubtitle string `json:"activitySubtitle"` + ActivityImage string `json:"activityImage"` + Facts []FactStruct `json:"facts"` } -// ItemStruct is sub-struct of BodyStruct -type ItemStruct struct { - Type string `json:"type"` - Text string `json:"text"` - Weight string `json:"weight"` - Size string `json:"size"` -} - -// ActionStruct is sub-struct of MsTeam -type ActionStruct struct { - Type string `json:"type"` - Title string `json:"title"` - URL string `json:"url"` +// FactStruct is sub-struct of SectionStruct +type FactStruct struct { + Name string `json:"name"` + Value string `json:"value"` } // NewMsTeam is used to create MsTeam @@ -61,35 +55,28 @@ func NewMsTeam(err error, expandos *Expandos) MsTeam { } notificationCard := MsTeam{ - Type: "AdaptiveCard", - Version: "1.2", - Body: []BodyStruct{ - BodyStruct{ - Type: "TextBlock", - Text: title, - Items: []ItemStruct{ - ItemStruct{ - Type: "TextBlock", - Text: summary, - Weight: "Bolder", - Size: "Medium", + Type: "MessageCard", + Context: "http://schema.org/extensions", + Summary: summary, + ThemeColor: os.Getenv("ALERT_THEME_COLOR"), + Title: title, + Sections: []SectionStruct{ + SectionStruct{ + ActivityTitle: summary, + ActivitySubtitle: fmt.Sprintf("error has occured on %v", os.Getenv("APP_NAME")), + ActivityImage: "", + Facts: []FactStruct{ + FactStruct{ + Name: "Environment:", + Value: os.Getenv("APP_ENV"), }, - ItemStruct{ - Type: "TextBlock", - Text: fmt.Sprintf("Error: %s", errMsg), - Weight: "Lighter", - Size: "Small", + FactStruct{ + Name: "ERROR", + Value: errMsg, }, }, }, }, - Actions: []ActionStruct{ - ActionStruct{ - Type: "Action.OpenUrl", - Title: "View Details", - URL: os.Getenv("MS_TEAMS_WEBHOOK"), - }, - }, } return notificationCard } From b8489998d49b66a650cdd43e5c76f6fe7e2ea3a5 Mon Sep 17 00:00:00 2001 From: Gabriel Vasile Date: Mon, 22 Jul 2024 14:10:31 +0900 Subject: [PATCH 3/6] update to use AdaptiveCards format --- ms_teams.go | 139 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 36 deletions(-) diff --git a/ms_teams.go b/ms_teams.go index 4eefb61..0724d8e 100644 --- a/ms_teams.go +++ b/ms_teams.go @@ -5,42 +5,82 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "os" "time" ) -// MsTeam is MessageCard for Team notification +// MsTeam is Adaptive Card for Team notification type MsTeam struct { - Type string `json:"@type"` - Context string `json:"@context"` - Summary string `json:"summary"` - ThemeColor string `json:"themeColor"` - Title string `json:"title"` - Sections []SectionStruct `json:"sections"` + Type string `json:"type"` + Attachments []attachment `json:"attachments"` } -// SectionStruct is sub-struct of MsTeam -type SectionStruct struct { - ActivityTitle string `json:"activityTitle"` - ActivitySubtitle string `json:"activitySubtitle"` - ActivityImage string `json:"activityImage"` - Facts []FactStruct `json:"facts"` +type attachment struct { + ContentType string `json:"contentType"` + ContentURL *string `json:"contentUrl"` + Content cardContent `json:"content"` } -// FactStruct is sub-struct of SectionStruct -type FactStruct struct { - Name string `json:"name"` +type cardContent struct { + Schema string `json:"$schema"` + Type string `json:"type"` + Version string `json:"version"` + AccentColor string `json:"accentColor"` + Body []interface{} `json:"body"` + Actions []action `json:"actions"` + MSTeams msTeams `json:"msteams"` +} + +type textBlock struct { + Type string `json:"type"` + Text string `json:"text"` + ID string `json:"id,omitempty"` + Size string `json:"size,omitempty"` + Weight string `json:"weight,omitempty"` + Color string `json:"color,omitempty"` +} + +type fact struct { + Title string `json:"title"` Value string `json:"value"` } +type factSet struct { + Type string `json:"type"` + Facts []fact `json:"facts"` + ID string `json:"id"` +} + +type codeBlock struct { + Type string `json:"type"` + CodeSnippet string `json:"codeSnippet"` + FontType string `json:"fontType"` + Wrap bool `json:"wrap"` +} + +type action struct { + Type string `json:"type"` + Title string `json:"title"` + URL string `json:"url"` +} + +type msTeams struct { + Width string `json:"width"` +} + // NewMsTeam is used to create MsTeam func NewMsTeam(err error, expandos *Expandos) MsTeam { title := os.Getenv("ALERT_CARD_SUBJECT") summary := os.Getenv("MS_TEAMS_CARD_SUBJECT") errMsg := fmt.Sprintf("%+v", err) + hostname, err := os.Hostname() + if err != nil { + hostname = "hostname_unknown" + } + hostname += " " + os.Getenv("APP_NAME") // apply expandos on card if expandos != nil { if expandos.MsTeamsAlertCardSubject != "" { @@ -54,31 +94,58 @@ func NewMsTeam(err error, expandos *Expandos) MsTeam { } } - notificationCard := MsTeam{ - Type: "MessageCard", - Context: "http://schema.org/extensions", - Summary: summary, - ThemeColor: os.Getenv("ALERT_THEME_COLOR"), - Title: title, - Sections: []SectionStruct{ - SectionStruct{ - ActivityTitle: summary, - ActivitySubtitle: fmt.Sprintf("error has occured on %v", os.Getenv("APP_NAME")), - ActivityImage: "", - Facts: []FactStruct{ - FactStruct{ - Name: "Environment:", - Value: os.Getenv("APP_ENV"), + return MsTeam{ + Type: "message", + Attachments: []attachment{ + { + ContentType: "application/vnd.microsoft.card.adaptive", + ContentURL: nil, + Content: cardContent{ + Schema: "http://adaptivecards.io/schemas/adaptive-card.json", + Type: "AdaptiveCard", + Version: "1.4", + AccentColor: "bf0000", + Body: []interface{}{ + textBlock{ + Type: "TextBlock", + Text: title, + ID: "title", + Size: "large", + Weight: "bolder", + Color: "accent", + }, + factSet{ + Type: "FactSet", + Facts: []fact{ + { + Title: "Title:", + Value: title, + }, + { + Title: "Summary:", + Value: summary, + }, + { + Title: "Hostname:", + Value: hostname, + }, + }, + ID: "acFactSet", + }, + codeBlock{ + Type: "CodeBlock", + CodeSnippet: errMsg, + FontType: "monospace", + Wrap: true, + }, }, - FactStruct{ - Name: "ERROR", - Value: errMsg, + MSTeams: msTeams{ + Width: "Full", }, }, }, }, } - return notificationCard } // Send is implementation of interface AlertNotification's Send() From cbdf0c4b36517d4c083775efd2788ad801f4e928 Mon Sep 17 00:00:00 2001 From: Gabriel Vasile Date: Mon, 22 Jul 2024 14:10:54 +0900 Subject: [PATCH 4/6] check webhook http response code --- ms_teams.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ms_teams.go b/ms_teams.go index 0724d8e..42540ff 100644 --- a/ms_teams.go +++ b/ms_teams.go @@ -192,12 +192,12 @@ func (card *MsTeam) Send() (err error) { defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - if string(respBody) != "1" { - return errors.New("cannot push to MSTeams") + if resp.StatusCode != http.StatusAccepted { + respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024)) + if err != nil { + return err + } + return fmt.Errorf("unexpected response from webhook: %s", string(respBody)) } return } From 5b10304e776f8935d275688f881ade7d3f99ec79 Mon Sep 17 00:00:00 2001 From: Gabriel Vasile Date: Mon, 22 Jul 2024 14:57:51 +0900 Subject: [PATCH 5/6] vup to github.com/rakutentech/go-alertnotification/v2 Following the changes in #19, several structs are not exported anymore. This requires upgrading the major version. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ef1598b..bb7109b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/rakutentech/go-alertnotification +module github.com/rakutentech/go-alertnotification/v2 go 1.21 From 022b223207a40b005f7d05b7d4a6b01d101b2526 Mon Sep 17 00:00:00 2001 From: Gabriel Vasile Date: Mon, 22 Jul 2024 15:01:37 +0900 Subject: [PATCH 6/6] upgrade deps --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bb7109b..85cf15d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/GitbookIO/diskache v0.0.0-20161028144708-bfb81bf58cb1 - github.com/joho/godotenv v1.3.0 + github.com/joho/godotenv v1.5.1 ) require github.com/GitbookIO/syncgroup v0.0.0-20200915204659-4f0b2961ab10 // indirect diff --git a/go.sum b/go.sum index 1b40e6e..6ae9fa8 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ github.com/GitbookIO/diskache v0.0.0-20161028144708-bfb81bf58cb1 h1:1ui53h0HCYjy github.com/GitbookIO/diskache v0.0.0-20161028144708-bfb81bf58cb1/go.mod h1:TTHndD25/UJVOyBl/vOq2g5RIg4bidGlmtzb+4Zr+Nw= github.com/GitbookIO/syncgroup v0.0.0-20200915204659-4f0b2961ab10 h1:G9KsBi5RxXROehPm+TSvTrFXShD613GLKrv9ctY1hFE= github.com/GitbookIO/syncgroup v0.0.0-20200915204659-4f0b2961ab10/go.mod h1:QEGLOlzj5q/UbkPM0viAulgbdRUpsU3/6HVA9YUA9BU= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=