Repository: fluxcd/notification-controller
Pull request #168 (2021-03-22 21:50:29)

Add support for Webex as an alert provider
Fix: #133


Patch:
diff --git a/api/v1beta1/provider_types.go b/api/v1beta1/provider_types.go
index 63ac893..71d027e 100644
--- a/api/v1beta1/provider_types.go
+++ b/api/v1beta1/provider_types.go
@@ -28,7 +28,7 @@ const (
 // ProviderSpec defines the desired state of Provider
 type ProviderSpec struct {
 	// Type of provider
-	// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;github;gitlab;bitbucket;azuredevops;googlechat
+	// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;github;gitlab;bitbucket;azuredevops;googlechat;webex
 	// +required
 	Type string `json:"type"`
 
@@ -69,6 +69,7 @@ const (
 	BitbucketProvider   string = "bitbucket"
 	AzureDevOpsProvider string = "azuredevops"
 	GoogleChatProvider  string = "googlechat"
+	WebexProvider       string = "webex"
 )
 
 // ProviderStatus defines the observed state of Provider
diff --git a/config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml b/config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml
index 511361f..0a64b85 100644
--- a/config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml
+++ b/config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml
@@ -80,6 +80,7 @@ spec:
                 - bitbucket
                 - azuredevops
                 - googlechat
+                - webex
                 type: string
               username:
                 description: Bot username for this provider
diff --git a/docs/spec/v1beta1/provider.md b/docs/spec/v1beta1/provider.md
index 597adc2..52ca94c 100644
--- a/docs/spec/v1beta1/provider.md
+++ b/docs/spec/v1beta1/provider.md
@@ -9,7 +9,7 @@ Spec:
 ```go
 type ProviderSpec struct {
 	// Type of provider
-	// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;github;gitlab;bitbucket;azuredevops;googlechat
+	// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;github;gitlab;bitbucket;azuredevops;googlechat;webex
 	// +required
 	Type string `json:"type"`
 
@@ -44,6 +44,7 @@ Notification providers:
 * Microsoft Teams
 * Rocket
 * Google Chat
+* Webex
 * Generic webhook
 
 Git commit status providers:
@@ -104,7 +105,7 @@ kubectl create secret generic webhook-url \
 
 Note that the secret must contain an `address` field.
 
-The provider type can be: `slack`, `msteams`, `rocket`, `discord`, `googlechat`, `github`, `gitlab`, `bitbucket`, `azuredevops` or `generic`.
+The provider type can be: `slack`, `msteams`, `rocket`, `discord`, `googlechat`, `webex`, `github`, `gitlab`, `bitbucket`, `azuredevops` or `generic`.
 
 When type `generic` is specified, the notification controller will post the
 incoming [event](event.md) in JSON format to the webhook address.
diff --git a/internal/notifier/factory.go b/internal/notifier/factory.go
index 3fdd7c6..a9f3b3f 100644
--- a/internal/notifier/factory.go
+++ b/internal/notifier/factory.go
@@ -68,6 +68,8 @@ func (f Factory) Notifier(provider string) (Interface, error) {
 		n, err = NewAzureDevOps(f.URL, f.Token)
 	case v1beta1.GoogleChatProvider:
 		n, err = NewGoogleChat(f.URL, f.ProxyURL)
+	case v1beta1.WebexProvider:
+		n, err = NewWebex(f.URL, f.ProxyURL)
 	default:
 		err = fmt.Errorf("provider %s not supported", provider)
 	}
diff --git a/internal/notifier/webex.go b/internal/notifier/webex.go
new file mode 100644
index 0000000..969cf97
--- /dev/null
+++ b/internal/notifier/webex.go
@@ -0,0 +1,78 @@
+/*
+Copyright 2020 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package notifier
+
+import (
+	"fmt"
+	"net/url"
+	"strings"
+
+	"github.com/fluxcd/pkg/runtime/events"
+)
+
+// Webex holds the hook URL
+type Webex struct {
+	URL      string
+	ProxyURL string
+}
+
+// WebexPayload holds the message text
+type WebexPayload struct {
+	Text     string `json:"text,omitempty"`
+	Markdown string `json:"markdown,omitempty"`
+}
+
+// NewWebex validates the Webex URL and returns a Webex object
+func NewWebex(hookURL, proxyURL string) (*Webex, error) {
+	_, err := url.ParseRequestURI(hookURL)
+	if err != nil {
+		return nil, fmt.Errorf("invalid Webex hook URL %s", hookURL)
+	}
+
+	return &Webex{
+		URL:      hookURL,
+		ProxyURL: proxyURL,
+	}, nil
+}
+
+// Post Webex message
+func (s *Webex) Post(event events.Event) error {
+	// Skip any update events
+	if isCommitStatus(event.Metadata, "update") {
+		return nil
+	}
+
+	objName := fmt.Sprintf("%s/%s.%s", strings.ToLower(event.InvolvedObject.Kind), event.InvolvedObject.Name, event.InvolvedObject.Namespace)
+	markdown := fmt.Sprintf("> **NAME** = %s | **MESSAGE** = %s", objName, event.Message)
+
+	if len(event.Metadata) > 0 {
+		markdown += " | **METADATA** ="
+		for k, v := range event.Metadata {
+			markdown += fmt.Sprintf(" **%s**: %s", k, v)
+		}
+	}
+
+	payload := WebexPayload{
+		Text:     "",
+		Markdown: markdown,
+	}
+
+	if err := postMessage(s.URL, s.ProxyURL, payload); err != nil {
+		return fmt.Errorf("postMessage failed: %w", err)
+	}
+	return nil
+}


Test patch:
diff --git a/internal/notifier/webex_test.go b/internal/notifier/webex_test.go
new file mode 100644
index 0000000..14ba0cf
--- /dev/null
+++ b/internal/notifier/webex_test.go
@@ -0,0 +1,57 @@
+/*
+Copyright 2020 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package notifier
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestWebex_Post(t *testing.T) {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		b, err := ioutil.ReadAll(r.Body)
+		require.NoError(t, err)
+
+		var payload = WebexPayload{}
+		err = json.Unmarshal(b, &payload)
+		require.NoError(t, err)
+		require.Empty(t, payload.Text)
+		require.Equal(t, "> **NAME** = gitrepository/webapp.gitops-system | **MESSAGE** = message | **METADATA** = **test**: metadata", payload.Markdown)
+	}))
+	defer ts.Close()
+
+	webex, err := NewWebex(ts.URL, "")
+	require.NoError(t, err)
+
+	err = webex.Post(testEvent())
+	require.NoError(t, err)
+}
+
+func TestWebex_PostUpdate(t *testing.T) {
+	webex, err := NewWebex("http://localhost", "")
+	require.NoError(t, err)
+
+	event := testEvent()
+	event.Metadata["commit_status"] = "update"
+	err = webex.Post(event)
+	require.NoError(t, err)
+}