diff --git a/api/v1beta1/provider_types.go b/api/v1beta1/provider_types.go index b5eb1ddd1..76a560c39 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;webex;sentry;azureeventhub;telegram;lark;matrix; + // +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;github;gitlab;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie; // +required Type string `json:"type"` @@ -80,6 +80,7 @@ const ( TelegramProvider string = "telegram" LarkProvider string = "lark" Matrix string = "matrix" + OpsgenieProvider string = "opsgenie" ) // ProviderStatus defines the observed state of Provider diff --git a/docs/spec/v1beta1/provider.md b/docs/spec/v1beta1/provider.md index ca191f495..f9fab527e 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;webex;sentry;azureeventhub;telegram;lark;matrix; + // +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;github;gitlab;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie // +required Type string `json:"type"` @@ -56,6 +56,7 @@ Notification providers: * Matrix * Azure Event Hub * Generic webhook +* Opsgenie Git commit status providers: diff --git a/internal/notifier/factory.go b/internal/notifier/factory.go index 95e3c94ae..8093b8fc5 100644 --- a/internal/notifier/factory.go +++ b/internal/notifier/factory.go @@ -83,6 +83,8 @@ func (f Factory) Notifier(provider string) (Interface, error) { n, err = NewLark(f.URL) case v1beta1.Matrix: n, err = NewMatrix(f.URL, f.Token, f.Channel) + case v1beta1.OpsgenieProvider: + n, err = NewOpsgenie(f.URL, f.ProxyURL, f.CertPool, f.Token) default: err = fmt.Errorf("provider %s not supported", provider) } diff --git a/internal/notifier/opsgenie.go b/internal/notifier/opsgenie.go new file mode 100644 index 000000000..9d6445a75 --- /dev/null +++ b/internal/notifier/opsgenie.go @@ -0,0 +1,77 @@ +/* +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 ( + "crypto/x509" + "fmt" + "net/url" + + "github.com/fluxcd/pkg/runtime/events" + "github.com/hashicorp/go-retryablehttp" +) + +type Opsgenie struct { + URL string + ProxyURL string + CertPool *x509.CertPool + ApiKey string +} + +type OpsgenieAlert struct { + Message string `json:"message"` + Description string `json:"description"` + Details map[string]string `json:"details"` +} + +// NewSlack validates the Slack URL and returns a Slack object +func NewOpsgenie(hookURL string, proxyURL string, certPool *x509.CertPool, token string) (*Opsgenie, error) { + _, err := url.ParseRequestURI(hookURL) + if err != nil { + return nil, fmt.Errorf("invalid Opsgenie hook URL %s", hookURL) + } + + return &Opsgenie{ + URL: hookURL, + ProxyURL: proxyURL, + CertPool: certPool, + ApiKey: token, + }, nil +} + +// Post opsgenie alert message +func (s *Opsgenie) Post(event events.Event) error { + // Skip any update events + if isCommitStatus(event.Metadata, "update") { + return nil + } + + payload := OpsgenieAlert{ + Message: event.InvolvedObject.Kind + "/" + event.InvolvedObject.Name, + Description: event.Message, + Details: event.Metadata, + } + + err := postMessage(s.URL, s.ProxyURL, s.CertPool, payload, func(req *retryablehttp.Request) { + req.Header.Set("Authorization", "GenieKey "+s.ApiKey) + }) + + if err != nil { + return fmt.Errorf("postMessage failed: %w", err) + } + return nil +} diff --git a/internal/notifier/opsgenie_test.go b/internal/notifier/opsgenie_test.go new file mode 100644 index 000000000..506d76340 --- /dev/null +++ b/internal/notifier/opsgenie_test.go @@ -0,0 +1,45 @@ +/* +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 TestOpsgenie_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 OpsgenieAlert + err = json.Unmarshal(b, &payload) + require.NoError(t, err) + + })) + defer ts.Close() + + opsgenie, err := NewOpsgenie(ts.URL, "", nil, "token") + require.NoError(t, err) + + err = opsgenie.Post(testEvent()) + require.NoError(t, err) +}