From 3ff8bfaa9311ca5923809b89b92299eed558be2c Mon Sep 17 00:00:00 2001 From: Quentin Machu Date: Tue, 15 Dec 2015 11:36:06 -0500 Subject: [PATCH 1/4] notifier: Allow custom notifiers to be registered. --- clair.go | 3 +- cmd/clair/main.go | 1 + config.example.yaml | 16 ++-- config/config.go | 8 +- docs/Notifications.md | 6 +- notifier/notifier.go | 157 +++++++++++++------------------------ notifier/notifiers/http.go | 146 ++++++++++++++++++++++++++++++++++ 7 files changed, 217 insertions(+), 120 deletions(-) create mode 100644 notifier/notifiers/http.go diff --git a/clair.go b/clair.go index 883d6cdc..254b046d 100644 --- a/clair.go +++ b/clair.go @@ -48,8 +48,7 @@ func Boot(config *config.Config) { // Start notifier st.Begin() - notifier := notifier.New(config.Notifier) - go notifier.Serve(st) + go notifier.Run(config.Notifier, st) // Start API st.Begin() diff --git a/cmd/clair/main.go b/cmd/clair/main.go index c08546cf..0df8a0ee 100644 --- a/cmd/clair/main.go +++ b/cmd/clair/main.go @@ -26,6 +26,7 @@ import ( "github.com/coreos/pkg/capnslog" // Register components + _ "github.com/coreos/clair/notifier/notifiers" _ "github.com/coreos/clair/updater/fetchers" _ "github.com/coreos/clair/worker/detectors/os" _ "github.com/coreos/clair/worker/detectors/packages" diff --git a/config.example.yaml b/config.example.yaml index cdefaef6..1255108c 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -29,10 +29,12 @@ updater: # Use 0 to disable the updater entirely. interval: 2h notifier: - # HTTP endpoint that will receive notifications with POST requests. - endpoint: - # Server name and path to certificates to call the endpoint securely with TLS and client certificate auth. - servername: - cafile: - keyfile: - certfile: + # Configuration for HTTP notifier + http: + # Endpoint that will receive notifications with POST requests. + endpoint: + # Server name and path to certificates to call the endpoint securely with TLS and client certificate auth. + servername: + cafile: + keyfile: + certfile: diff --git a/config/config.go b/config/config.go index 5c085994..380015be 100644 --- a/config/config.go +++ b/config/config.go @@ -42,13 +42,9 @@ type UpdaterConfig struct { Interval time.Duration } -// NotifierConfig is the configuration for the Notifier service. +// NotifierConfig is the configuration for the Notifier service and its registered notifiers. type NotifierConfig struct { - Endpoint string - ServerName string - CertFile string - KeyFile string - CAFile string + Params map[string]interface{} `yaml:",inline"` } // APIConfig is the configuration for the API service. diff --git a/docs/Notifications.md b/docs/Notifications.md index 7d0b1666..9ef0d4d1 100644 --- a/docs/Notifications.md +++ b/docs/Notifications.md @@ -2,10 +2,8 @@ This tool can send notifications to external services when specific events happen, such as vulnerability updates. -For now, it only supports transmitting them to an HTTP endpoint using POST requests, but it may be extended quite easily. -To enable the notification system, specify the following command-line arguments: - - --notifier-type=http --notifier-http-url="http://your-notification-endpoint" +For now, it only supports transmitting them to an HTTP endpoint using POST requests, but it can be extended quite easily by registering a new Notifier kind. +To enable the notification system, you simply have to specify the appropriate configuration. See the [example configuration](../config.example.yaml). # Types of notifications diff --git a/notifier/notifier.go b/notifier/notifier.go index c34a0e40..f06e6644 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -17,13 +17,6 @@ package notifier import ( - "bytes" - "crypto/tls" - "crypto/x509" - "encoding/json" - "io/ioutil" - "net/http" - "net/url" "time" "github.com/coreos/pkg/capnslog" @@ -52,66 +45,68 @@ type Notification struct { Content interface{} } -// A Notifier dispatches notifications to an HTTP endpoint. -type Notifier struct { - lockIdentifier string - endpoint string - client *http.Client +var notifiers = make(map[string]Notifier) + +// Notifier represents anything that can transmit notifications. +type Notifier interface { + // Configure attempts to initialize the notifier with the provided configuration. + // It returns whether the notifier is enabled or not. + Configure(*config.NotifierConfig) (bool, error) + // Send transmits the specified notification. + Send(notification *Notification) error } -// New initializes a new Notifier from the specified configuration. -func New(config *config.NotifierConfig) *Notifier { - if config == nil { - return &Notifier{} +// RegisterNotifier makes a Fetcher available by the provided name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func RegisterNotifier(name string, n Notifier) { + if name == "" { + panic("notifier: could not register a Notifier with an empty name") } - // Validate endpoint URL. - if _, err := url.Parse(config.Endpoint); err != nil { - log.Error("could not create a notifier with an invalid endpoint URL") - return &Notifier{} + if n == nil { + panic("notifier: could not register a nil Notifier") } - // Initialize TLS. - tlsConfig, err := loadTLSClientConfig(config) - if err != nil { - log.Fatalf("could not initialize client cert authentication: %s\n", err) - } - if tlsConfig != nil { - log.Info("notifier configured with client certificate authentication") + if _, dup := notifiers[name]; dup { + panic("notifier: RegisterNotifier called twice for " + name) } - httpClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - } - - return &Notifier{ - lockIdentifier: uuid.New(), - endpoint: config.Endpoint, - client: httpClient, - } + notifiers[name] = n } -// Serve starts the Notifier. -func (n *Notifier) Serve(stopper *utils.Stopper) { +// Run starts the Notifier service. +func Run(config *config.NotifierConfig, stopper *utils.Stopper) { defer stopper.End() - // Do not run the updater if the endpoint is empty. - if n.endpoint == "" { - log.Infof("notifier service is disabled.") + // Configure registered notifiers. + for notifierName, notifier := range notifiers { + if configured, err := notifier.Configure(config); configured { + log.Infof("notifier '%s' configured\n", notifierName) + } else { + delete(notifiers, notifierName) + if err != nil { + log.Errorf("could not configure notifier '%s': %s", notifierName, err) + } + } + } + + // Do not run the updater if there is no notifier enabled. + if len(notifiers) == 0 { + log.Infof("notifier service is disabled") return } - // Register healthchecker. - health.RegisterHealthchecker("notifier", n.Healthcheck) + whoAmI := uuid.New() + log.Infof("notifier service started. lock identifier: %s\n", whoAmI) - log.Infof("notifier service started. endpoint: %s. lock identifier: %s\n", n.endpoint, n.lockIdentifier) + // Register healthchecker. + health.RegisterHealthchecker("notifier", Healthcheck) for { // Find task. // TODO(Quentin-M): Combine node and notification. - node, notification := n.findTask(stopper) + node, notification := findTask(whoAmI, stopper) if node == "" && notification == nil { break } @@ -119,10 +114,10 @@ func (n *Notifier) Serve(stopper *utils.Stopper) { // Handle task. done := make(chan bool, 1) go func() { - if n.handleTask(node, notification) { + if handleTask(notification) { database.MarkNotificationAsSent(node) } - database.Unlock(node, n.lockIdentifier) + database.Unlock(node, whoAmI) done <- true }() @@ -133,7 +128,7 @@ func (n *Notifier) Serve(stopper *utils.Stopper) { case <-done: break outer case <-time.After(refreshLockDuration): - database.Lock(node, lockDuration, n.lockIdentifier) + database.Lock(node, lockDuration, whoAmI) } } } @@ -141,7 +136,7 @@ func (n *Notifier) Serve(stopper *utils.Stopper) { log.Info("notifier service stopped") } -func (n *Notifier) findTask(stopper *utils.Stopper) (string, database.Notification) { +func findTask(whoAmI string, stopper *utils.Stopper) (string, database.Notification) { for { // Find a notification to send. node, notification, err := database.FindOneNotificationToSend(database.GetDefaultNotificationWrapper()) @@ -158,14 +153,14 @@ func (n *Notifier) findTask(stopper *utils.Stopper) (string, database.Notificati } // Lock the notification. - if hasLock, _ := database.Lock(node, lockDuration, n.lockIdentifier); hasLock { + if hasLock, _ := database.Lock(node, lockDuration, whoAmI); hasLock { log.Infof("found and locked a notification: %s", notification.GetName()) return node, notification } } } -func (n *Notifier) handleTask(node string, notification database.Notification) bool { +func handleTask(notification database.Notification) bool { // Get notification content. // TODO(Quentin-M): Split big notifications. notificationContent, err := notification.GetContent() @@ -175,25 +170,19 @@ func (n *Notifier) handleTask(node string, notification database.Notification) b } // Create notification. - payload := Notification{ + payload := &Notification{ Name: notification.GetName(), Type: notification.GetType(), Content: notificationContent, } - // Marshal notification. - jsonPayload, err := json.Marshal(payload) - if err != nil { - log.Errorf("could not marshal content of notification '%s': %s", notification.GetName(), err) - return false - } - // Send notification. - resp, err := n.client.Post(n.endpoint, "application/json", bytes.NewBuffer(jsonPayload)) - defer resp.Body.Close() - if err != nil || (resp.StatusCode != 200 && resp.StatusCode != 201) { - log.Errorf("could not send notification '%s': (%d) %s", notification.GetName(), resp.StatusCode, err) - return false + // TODO(Quentin-M): Backoff / MaxRetries + for notifierName, notifier := range notifiers { + if err := notifier.Send(payload); err != nil { + log.Errorf("could not send notification '%s' to notifier '%s': %s", notification.GetName(), notifierName, err) + return false + } } log.Infof("successfully sent notification '%s'\n", notification.GetName()) @@ -201,41 +190,7 @@ func (n *Notifier) handleTask(node string, notification database.Notification) b } // Healthcheck returns the health of the notifier service. -func (n *Notifier) Healthcheck() health.Status { +func Healthcheck() health.Status { queueSize, err := database.CountNotificationsToSend() return health.Status{IsEssential: false, IsHealthy: err == nil, Details: struct{ QueueSize int }{QueueSize: queueSize}} } - -// loadTLSClientConfig initializes a *tls.Config using the given notifier -// configuration. -// -// If no certificates are given, (nil, nil) is returned. -// The CA certificate is optional and falls back to the system default. -func loadTLSClientConfig(cfg *config.NotifierConfig) (*tls.Config, error) { - if cfg.CertFile == "" || cfg.KeyFile == "" { - return nil, nil - } - - cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) - if err != nil { - return nil, err - } - - var caCertPool *x509.CertPool - if cfg.CAFile != "" { - caCert, err := ioutil.ReadFile(cfg.CAFile) - if err != nil { - return nil, err - } - caCertPool = x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - } - - tlsConfig := &tls.Config{ - ServerName: cfg.ServerName, - Certificates: []tls.Certificate{cert}, - RootCAs: caCertPool, - } - - return tlsConfig, nil -} diff --git a/notifier/notifiers/http.go b/notifier/notifiers/http.go new file mode 100644 index 00000000..e57567f8 --- /dev/null +++ b/notifier/notifiers/http.go @@ -0,0 +1,146 @@ +// Copyright 2015 clair 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 notifiers implements several kinds of notifier.Notifier +package notifiers + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + + "gopkg.in/yaml.v2" + + "github.com/coreos/clair/config" + "github.com/coreos/clair/notifier" +) + +// A HTTP notifier dispatches notifications to an HTTP endpoint. +type HTTP struct { + endpoint string + client *http.Client +} + +// A HTTPConfiguration represents the configuration of an HTTP notifier. +type HTTPConfiguration struct { + Endpoint string + ServerName string + CertFile string + KeyFile string + CAFile string +} + +func init() { + notifier.RegisterNotifier("http", &HTTP{}) +} + +func (h *HTTP) Configure(config *config.NotifierConfig) (bool, error) { + // Get configuration + var httpConfig HTTPConfiguration + if config == nil { + return false, nil + } + if _, ok := config.Params["http"]; !ok { + return false, nil + } + yamlConfig, err := yaml.Marshal(config.Params["http"]) + if err != nil { + return false, errors.New("invalid configuration") + } + err = yaml.Unmarshal(yamlConfig, &httpConfig) + if err != nil { + return false, errors.New("invalid configuration") + } + + // Validate endpoint URL. + if httpConfig.Endpoint == "" { + return false, nil + } + if _, err := url.Parse(httpConfig.Endpoint); err != nil { + return false, errors.New("invalid endpoint URL") + } + h.endpoint = httpConfig.Endpoint + + // Initialize TLS. + tlsConfig, err := loadTLSClientConfig(&httpConfig) + if err != nil { + return false, fmt.Errorf("could not initialize client cert auth: %s\n", err) + } + + h.client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + } + return true, nil +} + +func (h *HTTP) Send(notification *notifier.Notification) error { + // Marshal notification. + jsonNotification, err := json.Marshal(notification) + if err != nil { + return fmt.Errorf("could not marshal: %s", err) + } + + // Send notification over HTTP. + resp, err := h.client.Post(h.endpoint, "application/json", bytes.NewBuffer(jsonNotification)) + if err != nil || resp == nil || (resp.StatusCode != 200 && resp.StatusCode != 201) { + if resp != nil { + return fmt.Errorf("(%d) %s", resp.StatusCode, err) + } + return err + } + defer resp.Body.Close() + + return nil +} + +// loadTLSClientConfig initializes a *tls.Config using the given HTTPConfiguration. +// +// If no certificates are given, (nil, nil) is returned. +// The CA certificate is optional and falls back to the system default. +func loadTLSClientConfig(cfg *HTTPConfiguration) (*tls.Config, error) { + if cfg.CertFile == "" || cfg.KeyFile == "" { + return nil, nil + } + + cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) + if err != nil { + return nil, err + } + + var caCertPool *x509.CertPool + if cfg.CAFile != "" { + caCert, err := ioutil.ReadFile(cfg.CAFile) + if err != nil { + return nil, err + } + caCertPool = x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + } + + tlsConfig := &tls.Config{ + ServerName: cfg.ServerName, + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + + return tlsConfig, nil +} From 480589a83abfa6c9249771d535c2a405c7ce3466 Mon Sep 17 00:00:00 2001 From: Quentin Machu Date: Tue, 15 Dec 2015 11:24:58 -0500 Subject: [PATCH 2/4] notifier: retry upon failure --- config.example.yaml | 2 ++ config/config.go | 6 +++++- notifier/notifier.go | 38 +++++++++++++++++++++++++++++++------- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index 1255108c..0c459632 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -29,6 +29,8 @@ updater: # Use 0 to disable the updater entirely. interval: 2h notifier: + # How many attempts will the notifier do when a notifier backend fails. + attempts: 3 # Configuration for HTTP notifier http: # Endpoint that will receive notifications with POST requests. diff --git a/config/config.go b/config/config.go index 380015be..cdb5f608 100644 --- a/config/config.go +++ b/config/config.go @@ -44,7 +44,8 @@ type UpdaterConfig struct { // NotifierConfig is the configuration for the Notifier service and its registered notifiers. type NotifierConfig struct { - Params map[string]interface{} `yaml:",inline"` + Attempts int + Params map[string]interface{} `yaml:",inline"` } // APIConfig is the configuration for the API service. @@ -68,6 +69,9 @@ var DefaultConfig = Config{ HealthPort: 6061, Timeout: 900 * time.Second, }, + Notifier: &NotifierConfig{ + Attempts: 5, + }, } // Load is a shortcut to open a file, read it, and generate a Config. diff --git a/notifier/notifier.go b/notifier/notifier.go index f06e6644..02ce7c61 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -20,6 +20,7 @@ import ( "time" "github.com/coreos/pkg/capnslog" + "github.com/coreos/pkg/timeutil" "github.com/pborman/uuid" "github.com/coreos/clair/config" @@ -31,10 +32,10 @@ import ( var log = capnslog.NewPackageLogger("github.com/coreos/clair", "notifier") const ( - checkInterval = 5 * time.Minute - + checkInterval = 5 * time.Minute refreshLockDuration = time.Minute * 2 lockDuration = time.Minute*8 + refreshLockDuration + maxBackOff = 15 * time.Minute ) // TODO(Quentin-M): Allow registering custom notification handlers. @@ -114,7 +115,7 @@ func Run(config *config.NotifierConfig, stopper *utils.Stopper) { // Handle task. done := make(chan bool, 1) go func() { - if handleTask(notification) { + if handleTask(notification, stopper, config.Attempts) { database.MarkNotificationAsSent(node) } database.Unlock(node, whoAmI) @@ -160,7 +161,7 @@ func findTask(whoAmI string, stopper *utils.Stopper) (string, database.Notificat } } -func handleTask(notification database.Notification) bool { +func handleTask(notification database.Notification, st *utils.Stopper, maxAttempts int) bool { // Get notification content. // TODO(Quentin-M): Split big notifications. notificationContent, err := notification.GetContent() @@ -177,11 +178,34 @@ func handleTask(notification database.Notification) bool { } // Send notification. - // TODO(Quentin-M): Backoff / MaxRetries for notifierName, notifier := range notifiers { - if err := notifier.Send(payload); err != nil { + var attempts int + var backOff time.Duration + for { + // Max attempts exceeded. + if attempts >= maxAttempts { + log.Infof("giving up on sending notification '%s' to notifier '%s': max attempts exceeded (%d)\n", notification.GetName(), notifierName, maxAttempts) + return false + } + + // Backoff. + if backOff > 0 { + log.Infof("waiting %v before retrying to send notification '%s' to notifier '%s' (Attempt %d / %d)\n", backOff, notification.GetName(), notifierName, attempts+1, maxAttempts) + if !st.Sleep(backOff) { + return false + } + } + + // Send using the current notifier. + if err := notifier.Send(payload); err == nil { + // Send has been successful. Go to the next one. + break + } + + // Send failed; increase attempts/backoff and retry. log.Errorf("could not send notification '%s' to notifier '%s': %s", notification.GetName(), notifierName, err) - return false + backOff = timeutil.ExpBackoff(backOff, maxBackOff) + attempts++ } } From 2ea86c53f3e32b5e2781bd5b087d3123a8e61e6c Mon Sep 17 00:00:00 2001 From: Quentin Machu Date: Tue, 15 Dec 2015 13:23:57 -0500 Subject: [PATCH 3/4] notifier: fix a bug that prevented graceful shutdown in certain cases - The notifier was never checking if it should shutdown if there were always at least one notification to send - After a failure, the notifier is supposed to back-off, but when Clair is stopping, the backoff was interrupted immediately and did not retry. Instead it selected a new notification to send (most likely: the same one) and looped quickly/weirdly. --- notifier/notifier.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/notifier/notifier.go b/notifier/notifier.go index 02ce7c61..def94ca7 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -104,20 +104,25 @@ func Run(config *config.NotifierConfig, stopper *utils.Stopper) { // Register healthchecker. health.RegisterHealthchecker("notifier", Healthcheck) - for { + for running := true; running; { // Find task. // TODO(Quentin-M): Combine node and notification. node, notification := findTask(whoAmI, stopper) if node == "" && notification == nil { + // Interrupted while finding a task, Clair is stopping. break } // Handle task. done := make(chan bool, 1) go func() { - if handleTask(notification, stopper, config.Attempts) { + success, interrupted := handleTask(notification, stopper, config.Attempts) + if success { database.MarkNotificationAsSent(node) } + if interrupted { + running = false + } database.Unlock(node, whoAmI) done <- true }() @@ -161,13 +166,13 @@ func findTask(whoAmI string, stopper *utils.Stopper) (string, database.Notificat } } -func handleTask(notification database.Notification, st *utils.Stopper, maxAttempts int) bool { +func handleTask(notification database.Notification, st *utils.Stopper, maxAttempts int) (bool, bool) { // Get notification content. // TODO(Quentin-M): Split big notifications. notificationContent, err := notification.GetContent() if err != nil { log.Warningf("could not get content of notification '%s': %s", notification.GetName(), err) - return false + return false, false } // Create notification. @@ -185,14 +190,14 @@ func handleTask(notification database.Notification, st *utils.Stopper, maxAttemp // Max attempts exceeded. if attempts >= maxAttempts { log.Infof("giving up on sending notification '%s' to notifier '%s': max attempts exceeded (%d)\n", notification.GetName(), notifierName, maxAttempts) - return false + return false, false } // Backoff. if backOff > 0 { log.Infof("waiting %v before retrying to send notification '%s' to notifier '%s' (Attempt %d / %d)\n", backOff, notification.GetName(), notifierName, attempts+1, maxAttempts) if !st.Sleep(backOff) { - return false + return false, true } } @@ -210,7 +215,7 @@ func handleTask(notification database.Notification, st *utils.Stopper, maxAttemp } log.Infof("successfully sent notification '%s'\n", notification.GetName()) - return true + return true, false } // Healthcheck returns the health of the notifier service. From f4a4d417e7fee46add6f32d6284a9a6a8b9ce10d Mon Sep 17 00:00:00 2001 From: Quentin Machu Date: Thu, 17 Dec 2015 13:37:03 -0500 Subject: [PATCH 4/4] notifier: Rename HTTP to Webhook Notifier --- docs/Notifications.md | 2 +- notifier/notifiers/{http.go => webhook.go} | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) rename notifier/notifiers/{http.go => webhook.go} (83%) diff --git a/docs/Notifications.md b/docs/Notifications.md index 9ef0d4d1..a91d4705 100644 --- a/docs/Notifications.md +++ b/docs/Notifications.md @@ -2,7 +2,7 @@ This tool can send notifications to external services when specific events happen, such as vulnerability updates. -For now, it only supports transmitting them to an HTTP endpoint using POST requests, but it can be extended quite easily by registering a new Notifier kind. +For now, it only supports transmitting them to an webhook endpoint using HTTP POST requests, but it can be extended quite easily by registering a new Notifier kind. To enable the notification system, you simply have to specify the appropriate configuration. See the [example configuration](../config.example.yaml). # Types of notifications diff --git a/notifier/notifiers/http.go b/notifier/notifiers/webhook.go similarity index 83% rename from notifier/notifiers/http.go rename to notifier/notifiers/webhook.go index e57567f8..c726f29c 100644 --- a/notifier/notifiers/http.go +++ b/notifier/notifiers/webhook.go @@ -32,14 +32,14 @@ import ( "github.com/coreos/clair/notifier" ) -// A HTTP notifier dispatches notifications to an HTTP endpoint. -type HTTP struct { +// A WebhookNotifier dispatches notifications to a webhook endpoint. +type WebhookNotifier struct { endpoint string client *http.Client } -// A HTTPConfiguration represents the configuration of an HTTP notifier. -type HTTPConfiguration struct { +// A WebhookNotifierConfiguration represents the configuration of a WebhookNotifier. +type WebhookNotifierConfiguration struct { Endpoint string ServerName string CertFile string @@ -48,12 +48,12 @@ type HTTPConfiguration struct { } func init() { - notifier.RegisterNotifier("http", &HTTP{}) + notifier.RegisterNotifier("webhook", &WebhookNotifier{}) } -func (h *HTTP) Configure(config *config.NotifierConfig) (bool, error) { +func (h *WebhookNotifier) Configure(config *config.NotifierConfig) (bool, error) { // Get configuration - var httpConfig HTTPConfiguration + var httpConfig WebhookNotifierConfiguration if config == nil { return false, nil } @@ -92,14 +92,14 @@ func (h *HTTP) Configure(config *config.NotifierConfig) (bool, error) { return true, nil } -func (h *HTTP) Send(notification *notifier.Notification) error { +func (h *WebhookNotifier) Send(notification *notifier.Notification) error { // Marshal notification. jsonNotification, err := json.Marshal(notification) if err != nil { return fmt.Errorf("could not marshal: %s", err) } - // Send notification over HTTP. + // Send notification via HTTP POST. resp, err := h.client.Post(h.endpoint, "application/json", bytes.NewBuffer(jsonNotification)) if err != nil || resp == nil || (resp.StatusCode != 200 && resp.StatusCode != 201) { if resp != nil { @@ -112,11 +112,11 @@ func (h *HTTP) Send(notification *notifier.Notification) error { return nil } -// loadTLSClientConfig initializes a *tls.Config using the given HTTPConfiguration. +// loadTLSClientConfig initializes a *tls.Config using the given WebhookNotifierConfiguration. // // If no certificates are given, (nil, nil) is returned. // The CA certificate is optional and falls back to the system default. -func loadTLSClientConfig(cfg *HTTPConfiguration) (*tls.Config, error) { +func loadTLSClientConfig(cfg *WebhookNotifierConfiguration) (*tls.Config, error) { if cfg.CertFile == "" || cfg.KeyFile == "" { return nil, nil }