Skip to content

Commit e830901

Browse files
committed
Implement RingCentral notifier
1 parent 8276bfa commit e830901

File tree

6 files changed

+185
-0
lines changed

6 files changed

+185
-0
lines changed

artifacts/flagger/crd.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,7 @@ spec:
13931393
- discord
13941394
- rocket
13951395
- gchat
1396+
- ringcentral
13961397
channel:
13971398
description: Alert channel for this provider
13981399
type: string

charts/flagger/crds/crd.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,7 @@ spec:
13931393
- discord
13941394
- rocket
13951395
- gchat
1396+
- ringcentral
13961397
channel:
13971398
description: Alert channel for this provider
13981399
type: string

kustomize/base/flagger/crd.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,7 @@ spec:
13931393
- discord
13941394
- rocket
13951395
- gchat
1396+
- ringcentral
13961397
channel:
13971398
description: Alert channel for this provider
13981399
type: string

pkg/notifier/factory.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ func (f Factory) Notifier(provider string) (Interface, error) {
5656
n, err = NewMSTeams(f.URL, f.ProxyURL)
5757
case "gchat":
5858
n, err = NewGChat(f.URL, f.ProxyURL)
59+
case "ringcentral":
60+
n, err = NewRingCentral(f.URL, f.ProxyURL)
5961
default:
6062
err = fmt.Errorf("provider %s not supported", provider)
6163
}

pkg/notifier/ringcentral.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
Copyright 2020 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package notifier
18+
19+
import (
20+
"fmt"
21+
"net/url"
22+
"strings"
23+
)
24+
25+
// RingCentral holds the incoming webhook URL
26+
type RingCentral struct {
27+
URL string
28+
ProxyURL string
29+
}
30+
31+
// RingCentralPayload holds the message card data
32+
type RingCentralPayload struct {
33+
Activity string `json:"activity"`
34+
Attachments []RingCentralAttachment `json:"attachments"`
35+
}
36+
37+
// RingCentralAttachment holds the canary analysis result
38+
type RingCentralAttachment struct {
39+
Schema string `json:"$schema"`
40+
Type string `json:"type"`
41+
Version string `json:"version"`
42+
Body []map[string]interface{} `json:"body"`
43+
}
44+
45+
// NewRingCentral validates the RingCentral URL and returns a RingCentral object
46+
func NewRingCentral(hookURL string, proxyURL string) (*RingCentral, error) {
47+
_, err := url.ParseRequestURI(hookURL)
48+
if err != nil {
49+
return nil, fmt.Errorf("invalid RingCentral webhook URL %s", hookURL)
50+
}
51+
52+
return &RingCentral{
53+
URL: hookURL,
54+
ProxyURL: proxyURL,
55+
}, nil
56+
}
57+
58+
// Post RingCentral message
59+
func (s *RingCentral) Post(workload string, namespace string, message string, fields []Field, severity string) error {
60+
payload := RingCentralPayload{
61+
Activity: fmt.Sprintf("%s.%s", workload, namespace),
62+
}
63+
64+
var statusEmoji string
65+
switch strings.ToLower(severity) {
66+
case "info":
67+
statusEmoji = "\u2705" // Check Mark
68+
case "error":
69+
statusEmoji = "\u274C" // Cross Mark
70+
default:
71+
statusEmoji = "" // No emoji for other severities
72+
}
73+
74+
body := []map[string]interface{}{
75+
{
76+
"type": "TextBlock",
77+
"text": fmt.Sprintf("%s %s", statusEmoji, message),
78+
"wrap": true,
79+
"weight": "bolder",
80+
"size": "medium",
81+
},
82+
}
83+
84+
for _, f := range fields {
85+
body = append(body, map[string]interface{}{
86+
"type": "FactSet",
87+
"facts": []map[string]string{
88+
{
89+
"title": f.Name,
90+
"value": f.Value,
91+
},
92+
},
93+
})
94+
}
95+
96+
attachment := RingCentralAttachment{
97+
Schema: "http://adaptivecards.io/schemas/adaptive-card.json",
98+
Type: "AdaptiveCard",
99+
Version: "1.0",
100+
Body: body,
101+
}
102+
103+
payload.Attachments = []RingCentralAttachment{attachment}
104+
105+
err := postMessage(s.URL, "", s.ProxyURL, payload)
106+
if err != nil {
107+
return fmt.Errorf("postMessage failed: %w", err)
108+
}
109+
110+
return nil
111+
}

pkg/notifier/ringcentral_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2020 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package notifier
18+
19+
import (
20+
"encoding/json"
21+
"io"
22+
"net/http"
23+
"net/http/httptest"
24+
"testing"
25+
26+
"github.com/stretchr/testify/require"
27+
)
28+
29+
func TestRingCentral_NewRingCentral(t *testing.T) {
30+
_, err := NewRingCentral("invalid-url", "")
31+
require.Error(t, err)
32+
33+
ringCentral, err := NewRingCentral("http://localhost", "")
34+
require.NoError(t, err)
35+
require.Equal(t, "http://localhost", ringCentral.URL)
36+
}
37+
38+
func TestRingCentral_Post(t *testing.T) {
39+
fields := []Field{
40+
{Name: "name1", Value: "value1"},
41+
{Name: "name2", Value: "value2"},
42+
}
43+
44+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45+
b, err := io.ReadAll(r.Body)
46+
require.NoError(t, err)
47+
48+
var payload = RingCentralPayload{}
49+
err = json.Unmarshal(b, &payload)
50+
require.NoError(t, err)
51+
require.Equal(t, "podinfo.test", payload.Activity)
52+
require.Equal(t, len(fields)+1, len(payload.Attachments[0].Body))
53+
require.Equal(t, "http://adaptivecards.io/schemas/adaptive-card.json", payload.Attachments[0].Schema)
54+
require.Equal(t, "AdaptiveCard", payload.Attachments[0].Type)
55+
require.Equal(t, "1.0", payload.Attachments[0].Version)
56+
}))
57+
defer ts.Close()
58+
59+
ringCentral, err := NewRingCentral(ts.URL, "")
60+
require.NoError(t, err)
61+
62+
err = ringCentral.Post("podinfo", "test", "test", fields, "info")
63+
require.NoError(t, err)
64+
err = ringCentral.Post("podinfo", "test", "test", fields, "error")
65+
require.NoError(t, err)
66+
err = ringCentral.Post("podinfo", "test", "test", fields, "warn")
67+
require.NoError(t, err)
68+
69+
}

0 commit comments

Comments
 (0)