diff --git a/main.go b/main.go index c1a96f0..209ea1c 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( configflag "github.com/ncarlier/webhookd/pkg/config/flag" "github.com/ncarlier/webhookd/pkg/logger" "github.com/ncarlier/webhookd/pkg/notification" + _ "github.com/ncarlier/webhookd/pkg/notification/all" "github.com/ncarlier/webhookd/pkg/server" "github.com/ncarlier/webhookd/pkg/version" "github.com/ncarlier/webhookd/pkg/worker" diff --git a/pkg/api/helper.go b/pkg/api/helper.go index 5323034..77e62c9 100644 --- a/pkg/api/helper.go +++ b/pkg/api/helper.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/ncarlier/webhookd/pkg/strcase" + "github.com/ncarlier/webhookd/pkg/helper" ) // HTTPParamsToShellVars convert URL values to shell vars. @@ -20,7 +20,7 @@ func HTTPParamsToShellVars[T url.Values | http.Header](params T) []string { if err != nil { continue } - buf.WriteString(strcase.ToSnake(k)) + buf.WriteString(helper.ToSnake(k)) buf.WriteString("=") buf.WriteString(value) result = append(result, buf.String()) diff --git a/pkg/config/flag/bind.go b/pkg/config/flag/bind.go index 1086af6..5179a33 100644 --- a/pkg/config/flag/bind.go +++ b/pkg/config/flag/bind.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/ncarlier/webhookd/pkg/strcase" + "github.com/ncarlier/webhookd/pkg/helper" ) // Bind conf struct tags with flags @@ -103,7 +103,7 @@ func Bind(conf interface{}, prefix string) error { } func getEnvKey(prefix, key string) string { - return strcase.ToScreamingSnake(prefix + "_" + key) + return helper.ToScreamingSnake(prefix + "_" + key) } func getEnvValue(prefix, key, fallback string) string { diff --git a/pkg/strcase/snake.go b/pkg/helper/snake.go similarity index 97% rename from pkg/strcase/snake.go rename to pkg/helper/snake.go index 2ee6351..acaa47a 100644 --- a/pkg/strcase/snake.go +++ b/pkg/helper/snake.go @@ -23,8 +23,7 @@ * SOFTWARE. */ -// Package strcase converts strings to snake_case or CamelCase -package strcase +package helper import ( "strings" diff --git a/pkg/strcase/test/snake_test.go b/pkg/helper/test/snake_test.go similarity index 84% rename from pkg/strcase/test/snake_test.go rename to pkg/helper/test/snake_test.go index a99a3fd..96ec81e 100644 --- a/pkg/strcase/test/snake_test.go +++ b/pkg/helper/test/snake_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/ncarlier/webhookd/pkg/assert" - "github.com/ncarlier/webhookd/pkg/strcase" + "github.com/ncarlier/webhookd/pkg/helper" ) func TestToSnakeCase(t *testing.T) { @@ -19,7 +19,7 @@ func TestToSnakeCase(t *testing.T) { {"Hello/world", "hello_world"}, } for _, tc := range testCases { - value := strcase.ToSnake(tc.value) + value := helper.ToSnake(tc.value) assert.Equal(t, tc.expected, value, "") } } diff --git a/pkg/helper/values.go b/pkg/helper/values.go new file mode 100644 index 0000000..be67db8 --- /dev/null +++ b/pkg/helper/values.go @@ -0,0 +1,14 @@ +package helper + +import ( + "net/url" + "strings" +) + +// GetValueOrAlt get value or alt +func GetValueOrAlt(values url.Values, key, alt string) string { + if val, ok := values[key]; ok { + return strings.Join(val[:], ",") + } + return alt +} diff --git a/pkg/hook/job.go b/pkg/hook/job.go index bc1a70e..6e22ad9 100644 --- a/pkg/hook/job.go +++ b/pkg/hook/job.go @@ -15,8 +15,8 @@ import ( "syscall" "time" + "github.com/ncarlier/webhookd/pkg/helper" "github.com/ncarlier/webhookd/pkg/logger" - "github.com/ncarlier/webhookd/pkg/strcase" ) var hookID uint64 @@ -54,7 +54,7 @@ func NewHookJob(request *Request) (*Job, error) { MessageChan: make(chan []byte), status: Idle, } - job.logFilename = path.Join(request.OutputDir, fmt.Sprintf("%s_%d_%s.txt", strcase.ToSnake(job.name), job.id, time.Now().Format("20060102_1504"))) + job.logFilename = path.Join(request.OutputDir, fmt.Sprintf("%s_%d_%s.txt", helper.ToSnake(job.name), job.id, time.Now().Format("20060102_1504"))) return job, nil } diff --git a/pkg/hook/logs.go b/pkg/hook/logs.go index 0246976..d05f40f 100644 --- a/pkg/hook/logs.go +++ b/pkg/hook/logs.go @@ -6,12 +6,12 @@ import ( "path" "path/filepath" - "github.com/ncarlier/webhookd/pkg/strcase" + "github.com/ncarlier/webhookd/pkg/helper" ) // Logs get hook log with its name and id func Logs(id, name, base string) (*os.File, error) { - logPattern := path.Join(base, fmt.Sprintf("%s_%s_*.txt", strcase.ToSnake(name), id)) + logPattern := path.Join(base, fmt.Sprintf("%s_%s_*.txt", helper.ToSnake(name), id)) files, err := filepath.Glob(logPattern) if err != nil { return nil, err diff --git a/pkg/notification/all/all.go b/pkg/notification/all/all.go new file mode 100644 index 0000000..4ab0613 --- /dev/null +++ b/pkg/notification/all/all.go @@ -0,0 +1,8 @@ +package all + +import ( + // activate HTTP notifier + _ "github.com/ncarlier/webhookd/pkg/notification/http" + // activate SMTP notifier + _ "github.com/ncarlier/webhookd/pkg/notification/smtp" +) diff --git a/pkg/notification/http_notifier.go b/pkg/notification/http/http_notifier.go similarity index 68% rename from pkg/notification/http_notifier.go rename to pkg/notification/http/http_notifier.go index c2ef679..6b91b5f 100644 --- a/pkg/notification/http_notifier.go +++ b/pkg/notification/http/http_notifier.go @@ -1,4 +1,4 @@ -package notification +package http import ( "bytes" @@ -8,7 +8,9 @@ import ( "strconv" "strings" + "github.com/ncarlier/webhookd/pkg/helper" "github.com/ncarlier/webhookd/pkg/logger" + "github.com/ncarlier/webhookd/pkg/notification" ) type notifPayload struct { @@ -18,22 +20,22 @@ type notifPayload struct { Error error `json:"error,omitempty"` } -// HTTPNotifier is able to send a notification to a HTTP endpoint. -type HTTPNotifier struct { +// httpNotifier is able to send a notification to a HTTP endpoint. +type httpNotifier struct { URL *url.URL PrefixFilter string } -func newHTTPNotifier(uri *url.URL) *HTTPNotifier { +func newHTTPNotifier(uri *url.URL) (notification.Notifier, error) { logger.Info.Println("using HTTP notification system: ", uri.String()) - return &HTTPNotifier{ + return &httpNotifier{ URL: uri, - PrefixFilter: getValueOrAlt(uri.Query(), "prefix", "notify:"), - } + PrefixFilter: helper.GetValueOrAlt(uri.Query(), "prefix", "notify:"), + }, nil } // Notify send a notification to a HTTP endpoint. -func (n *HTTPNotifier) Notify(result HookResult) error { +func (n *httpNotifier) Notify(result notification.HookResult) error { payload := result.Logs(n.PrefixFilter) if strings.TrimSpace(payload) == "" { // Nothing to notify, abort @@ -66,3 +68,8 @@ func (n *HTTPNotifier) Notify(result HookResult) error { logger.Info.Printf("job %s#%d notification sent to %s\n", result.Name(), result.ID(), n.URL.String()) return nil } + +func init() { + notification.Register("http", newHTTPNotifier) + notification.Register("https", newHTTPNotifier) +} diff --git a/pkg/notification/notifier.go b/pkg/notification/notifier.go index 3236307..65b4459 100644 --- a/pkg/notification/notifier.go +++ b/pkg/notification/notifier.go @@ -1,10 +1,6 @@ package notification import ( - "fmt" - "net/url" - "strings" - "github.com/ncarlier/webhookd/pkg/logger" ) @@ -25,30 +21,8 @@ func Notify(result HookResult) { } } -// Init creates a notifier regarding the URI. -func Init(uri string) error { - if uri == "" { - return nil - } - u, err := url.Parse(uri) - if err != nil { - return fmt.Errorf("invalid notification URL: %s", uri) - } - switch u.Scheme { - case "mailto": - notifier = newSMTPNotifier(u) - case "http", "https": - notifier = newHTTPNotifier(u) - default: - return fmt.Errorf("unable to create notifier: %v", err) - } - - return nil -} - -func getValueOrAlt(values url.Values, key, alt string) string { - if val, ok := values[key]; ok { - return strings.Join(val[:], ",") - } - return alt +// Init creates the notifier singleton regarding the URI. +func Init(uri string) (err error) { + notifier, err = NewNotifier(uri) + return err } diff --git a/pkg/notification/registry.go b/pkg/notification/registry.go new file mode 100644 index 0000000..9f8bc3a --- /dev/null +++ b/pkg/notification/registry.go @@ -0,0 +1,33 @@ +package notification + +import ( + "fmt" + "net/url" +) + +// NotifierCreator function for create a notifier +type NotifierCreator func(uri *url.URL) (Notifier, error) + +// Registry of all Notifiers +var registry = map[string]NotifierCreator{} + +// Register a Notifier to the registry +func Register(scheme string, creator NotifierCreator) { + registry[scheme] = creator +} + +// NewNotifier create new Notifier +func NewNotifier(uri string) (Notifier, error) { + if uri == "" { + return nil, nil + } + u, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("invalid notification URL: %s", uri) + } + creator, ok := registry[u.Scheme] + if !ok { + return nil, fmt.Errorf("unsupported notification scheme: %s", u.Scheme) + } + return creator(u) +} diff --git a/pkg/notification/smtp_notifier.go b/pkg/notification/smtp/smtp_notifier.go similarity index 70% rename from pkg/notification/smtp_notifier.go rename to pkg/notification/smtp/smtp_notifier.go index b39fed6..a6ccfe1 100644 --- a/pkg/notification/smtp_notifier.go +++ b/pkg/notification/smtp/smtp_notifier.go @@ -1,4 +1,4 @@ -package notification +package smtp import ( "crypto/tls" @@ -10,11 +10,13 @@ import ( "strings" "time" + "github.com/ncarlier/webhookd/pkg/helper" "github.com/ncarlier/webhookd/pkg/logger" + "github.com/ncarlier/webhookd/pkg/notification" ) -// SMTPNotifier is able to send notification to a email destination. -type SMTPNotifier struct { +// smtpNotifier is able to send notification to a email destination. +type smtpNotifier struct { Host string Username string Password string @@ -25,22 +27,22 @@ type SMTPNotifier struct { PrefixFilter string } -func newSMTPNotifier(uri *url.URL) *SMTPNotifier { +func newSMTPNotifier(uri *url.URL) (notification.Notifier, error) { logger.Info.Println("using SMTP notification system:", uri.Opaque) q := uri.Query() - return &SMTPNotifier{ - Host: getValueOrAlt(q, "smtp", "localhost:25"), - Username: getValueOrAlt(q, "username", ""), - Password: getValueOrAlt(q, "password", ""), - Conn: getValueOrAlt(q, "conn", "plain"), - From: getValueOrAlt(q, "from", "noreply@nunux.org"), + return &smtpNotifier{ + Host: helper.GetValueOrAlt(q, "smtp", "localhost:25"), + Username: helper.GetValueOrAlt(q, "username", ""), + Password: helper.GetValueOrAlt(q, "password", ""), + Conn: helper.GetValueOrAlt(q, "conn", "plain"), + From: helper.GetValueOrAlt(q, "from", "noreply@nunux.org"), To: uri.Opaque, - Subject: getValueOrAlt(uri.Query(), "subject", "[whd-notification] {name}#{id} {status}"), - PrefixFilter: getValueOrAlt(uri.Query(), "prefix", "notify:"), - } + Subject: helper.GetValueOrAlt(uri.Query(), "subject", "[whd-notification] {name}#{id} {status}"), + PrefixFilter: helper.GetValueOrAlt(uri.Query(), "prefix", "notify:"), + }, nil } -func (n *SMTPNotifier) buildEmailPayload(result HookResult) string { +func (n *smtpNotifier) buildEmailPayload(result notification.HookResult) string { // Get email body body := result.Logs(n.PrefixFilter) if strings.TrimSpace(body) == "" { @@ -66,7 +68,7 @@ func (n *SMTPNotifier) buildEmailPayload(result HookResult) string { } // Notify send a notification to a email destination. -func (n *SMTPNotifier) Notify(result HookResult) error { +func (n *smtpNotifier) Notify(result notification.HookResult) error { hostname, _, _ := net.SplitHostPort(n.Host) payload := n.buildEmailPayload(result) if payload == "" { @@ -132,9 +134,13 @@ func (n *SMTPNotifier) Notify(result HookResult) error { return client.Quit() } -func buildSubject(template string, result HookResult) string { +func buildSubject(template string, result notification.HookResult) string { subject := strings.ReplaceAll(template, "{name}", result.Name()) subject = strings.ReplaceAll(subject, "{id}", strconv.FormatUint(uint64(result.ID()), 10)) subject = strings.ReplaceAll(subject, "{status}", result.StatusLabel()) return subject } + +func init() { + notification.Register("mailto", newSMTPNotifier) +}