From 9d5e30752bfa16557b4ca1a8c4178ac6972d93a5 Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Mon, 15 Mar 2021 02:50:12 -0300 Subject: [PATCH 01/10] feat(service): add mqtt service Co-authored-by: Mexazonic --- go.mod | 1 + go.sum | 5 ++ pkg/router/servicemap.go | 2 + pkg/services/mqtt/mqtt.go | 105 ++++++++++++++++++++++++++++ pkg/services/mqtt/mqtt_config.go | 82 ++++++++++++++++++++++ pkg/services/mqtt/mqtt_json.go | 21 ++++++ pkg/services/mqtt/mqtt_parsemode.go | 34 +++++++++ 7 files changed, 250 insertions(+) create mode 100644 pkg/services/mqtt/mqtt.go create mode 100644 pkg/services/mqtt/mqtt_config.go create mode 100644 pkg/services/mqtt/mqtt_json.go create mode 100644 pkg/services/mqtt/mqtt_parsemode.go diff --git a/go.mod b/go.mod index a85a4782..883f45ab 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/containrrr/shoutrrr go 1.12 require ( + github.com/eclipse/paho.mqtt.golang v1.3.2 // indirect github.com/fatih/color v1.10.0 github.com/google/uuid v1.1.5 // indirect github.com/jarcoal/httpmock v1.0.4 diff --git a/go.sum b/go.sum index e1969ea2..cc836c33 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/eclipse/paho.mqtt.golang v1.3.2 h1:ICzfxSyrR8bOsh9l8JBBOwO1tc2C26oEyody0ml0L6E= +github.com/eclipse/paho.mqtt.golang v1.3.2/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -102,6 +104,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -277,6 +281,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/pkg/router/servicemap.go b/pkg/router/servicemap.go index 89aa1efd..f9c3ca8f 100644 --- a/pkg/router/servicemap.go +++ b/pkg/router/servicemap.go @@ -8,6 +8,7 @@ import ( "github.com/containrrr/shoutrrr/pkg/services/join" "github.com/containrrr/shoutrrr/pkg/services/logger" "github.com/containrrr/shoutrrr/pkg/services/mattermost" + "github.com/containrrr/shoutrrr/pkg/services/mqtt" "github.com/containrrr/shoutrrr/pkg/services/opsgenie" "github.com/containrrr/shoutrrr/pkg/services/pushbullet" "github.com/containrrr/shoutrrr/pkg/services/pushover" @@ -34,6 +35,7 @@ var serviceMap = map[string]func() t.Service{ "xmpp": func() t.Service { return &xmpp.Service{} }, "pushbullet": func() t.Service { return &pushbullet.Service{} }, "mattermost": func() t.Service { return &mattermost.Service{} }, + "mqtt": func() t.Service { return &mqtt.Service{} }, "hangouts": func() t.Service { return &hangouts.Service{} }, "zulip": func() t.Service { return &zulip.Service{} }, "join": func() t.Service { return &join.Service{} }, diff --git a/pkg/services/mqtt/mqtt.go b/pkg/services/mqtt/mqtt.go new file mode 100644 index 00000000..ac6b7380 --- /dev/null +++ b/pkg/services/mqtt/mqtt.go @@ -0,0 +1,105 @@ +package mqtt + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net/url" + + "github.com/containrrr/shoutrrr/pkg/format" + mqtt "github.com/eclipse/paho.mqtt.golang" + + "github.com/containrrr/shoutrrr/pkg/services/standard" + "github.com/containrrr/shoutrrr/pkg/types" +) + +const ( + maxlength = 4096 +) + +// Service sends notifications to mqtt topic +type Service struct { + standard.Standard + config *Config + pkr format.PropKeyResolver +} + +// Send notification to mqtt +func (service *Service) Send(message string, params *types.Params) error { + if len(message) > maxlength { + return errors.New("message exceeds the max length") + } + + config := *service.config + if err := service.pkr.UpdateConfigFromParams(&config, params); err != nil { + return err + } + + return publishMessageToTopic(message, &config) +} + +// Initialize loads ServiceConfig from configURL and sets logger for this Service +func (service *Service) Initialize(configURL *url.URL, logger *log.Logger) error { + service.Logger.SetLogger(logger) + service.config = &Config{ + DisableTLS: true, + } + service.pkr = format.NewPropKeyResolver(service.config) + if err := service.config.setURL(&service.pkr, configURL); err != nil { + return err + } + + return nil +} + +// GetConfig returns the Config for the service +func (service *Service) GetConfig() *Config { + return service.config +} + +// Handle Connection Lost +var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) { + fmt.Printf("Connect lost: %v", err) +} + +// Publish to topic +func publish(client mqtt.Client, topic string, data []byte) { + token := client.Publish(topic, 0, false, data) + token.Wait() +} + +// Publish payload +func publishMessageToTopic(message string, config *Config) error { + postURL := fmt.Sprintf("tcp://%s:%d", config.Host, config.Port) + payload := createSendMessagePayload(message, config.Topic, config) + + jsonData, err := json.Marshal(payload) + if err != nil { + return err + } + + // Config + opts := mqtt.NewClientOptions() + + opts.AddBroker(postURL) + + opts.OnConnectionLost = connectLostHandler + + // Start client + client := mqtt.NewClient(opts) + + token := client.Connect(); + + if token.Error() != nil { + return token.Error() + } + + token.Wait() + + publish(client, config.Topic, jsonData) + + client.Disconnect(1) + + return nil +} diff --git a/pkg/services/mqtt/mqtt_config.go b/pkg/services/mqtt/mqtt_config.go new file mode 100644 index 00000000..1a85d96f --- /dev/null +++ b/pkg/services/mqtt/mqtt_config.go @@ -0,0 +1,82 @@ +package mqtt + +import ( + "fmt" + "github.com/containrrr/shoutrrr/pkg/format" + "github.com/containrrr/shoutrrr/pkg/types" + "net/url" + "strings" + "strconv" +) + +// Config for use within the mqtt +type Config struct { + Host string `key:"broker" default:"" desc:"MQTT broker server hostname or IP address"` + Port int64 `key:"port" default:"1883" desc:"TCP Port"` + Topic string `key:"topic" default:"" desc:"Topic where the message is sent"` + DisableTLS bool `key:"disabletls" default:"Yes"` + ParseMode parseMode `key:"parsemode" default:"None" desc:"How the text message should be parsed"` +} + +// Enums returns the fields that should use a corresponding EnumFormatter to Print/Parse their values +func (config *Config) Enums() map[string]types.EnumFormatter { + return map[string]types.EnumFormatter{ + "ParseMode": parseModes.Enum, + } +} + +// GetURL returns a URL representation of it's current field values +func (config *Config) GetURL() *url.URL { + resolver := format.NewPropKeyResolver(config) + return config.getURL(&resolver) +} + +// SetURL updates a ServiceConfig from a URL representation of it's field values +func (config *Config) SetURL(url *url.URL) error { + resolver := format.NewPropKeyResolver(config) + return config.setURL(&resolver, url) +} + +func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL { + + return &url.URL{ + Host: fmt.Sprintf("%s:%d", config.Host, config.Port), + Scheme: Scheme, + ForceQuery: true, + RawQuery: format.BuildQuery(resolver), + } + +} + +func Split(r rune) bool { + return r == '/' || r == '?' || r == ':' +} + +func getTopic(r rune) bool { + return r == '=' +} + +func (config *Config) setURL(resolver types.ConfigQueryResolver, url *url.URL) error { + + u := strings.FieldsFunc(url.String(), Split) + topic := strings.FieldsFunc(url.String(), getTopic) + + port, err := strconv.ParseInt(u[2], 10, 64) + + if err != nil { + return err + } + + if len(u) > 4 { + config.Host = u[1] + config.Port = port + config.Topic = topic[1] + } + + return nil +} + +// Scheme is the identifying part of this service's configuration URL +const ( + Scheme = "tcp" +) diff --git a/pkg/services/mqtt/mqtt_json.go b/pkg/services/mqtt/mqtt_json.go new file mode 100644 index 00000000..4c1fa7a9 --- /dev/null +++ b/pkg/services/mqtt/mqtt_json.go @@ -0,0 +1,21 @@ +package mqtt + +// JSON to be used as a notification payload for the telegram notification service +type SendMessagePayload struct { + Text string `json:"text"` + Topic string `json:"topic"` + ParseMode string `json:"parse_mode,omitempty"` +} + +func createSendMessagePayload(message string, topic string, config *Config) SendMessagePayload { + payload := SendMessagePayload{ + Text: message, + Topic: topic, + } + + if config.ParseMode != parseModes.None { + payload.ParseMode = config.ParseMode.String() + } + + return payload +} diff --git a/pkg/services/mqtt/mqtt_parsemode.go b/pkg/services/mqtt/mqtt_parsemode.go new file mode 100644 index 00000000..43fefaf0 --- /dev/null +++ b/pkg/services/mqtt/mqtt_parsemode.go @@ -0,0 +1,34 @@ +package mqtt + +import ( + "github.com/containrrr/shoutrrr/pkg/format" + "github.com/containrrr/shoutrrr/pkg/types" +) + +type parseMode int + +type parseModeVals struct { + None parseMode + Markdown parseMode + HTML parseMode + MarkdownV2 parseMode + Enum types.EnumFormatter +} + +var parseModes = &parseModeVals{ + None: 0, + Markdown: 1, + HTML: 2, + MarkdownV2: 3, + Enum: format.CreateEnumFormatter( + []string{ + "None", + "Markdown", + "HTML", + "MarkdownV2", + }), +} + +func (pm parseMode) String() string { + return parseModes.Enum.Print(int(pm)) +} From 7b4e3a959c55044f69ef77462d2e2682b0e45fc3 Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Mon, 15 Mar 2021 03:38:07 -0300 Subject: [PATCH 02/10] fix(mqtt): fix static issuesin mqtt --- pkg/services/mqtt/mqtt.go | 5 ++++- pkg/services/mqtt/mqtt_config.go | 9 ++++++--- pkg/services/mqtt/mqtt_json.go | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/services/mqtt/mqtt.go b/pkg/services/mqtt/mqtt.go index ac6b7380..67bbd3df 100644 --- a/pkg/services/mqtt/mqtt.go +++ b/pkg/services/mqtt/mqtt.go @@ -46,7 +46,10 @@ func (service *Service) Initialize(configURL *url.URL, logger *log.Logger) error DisableTLS: true, } service.pkr = format.NewPropKeyResolver(service.config) - if err := service.config.setURL(&service.pkr, configURL); err != nil { + + err := service.config.setURL(&service.pkr, configURL) + + if err == nil { return err } diff --git a/pkg/services/mqtt/mqtt_config.go b/pkg/services/mqtt/mqtt_config.go index 1a85d96f..948aa67a 100644 --- a/pkg/services/mqtt/mqtt_config.go +++ b/pkg/services/mqtt/mqtt_config.go @@ -2,11 +2,12 @@ package mqtt import ( "fmt" - "github.com/containrrr/shoutrrr/pkg/format" - "github.com/containrrr/shoutrrr/pkg/types" "net/url" - "strings" "strconv" + "strings" + + "github.com/containrrr/shoutrrr/pkg/format" + "github.com/containrrr/shoutrrr/pkg/types" ) // Config for use within the mqtt @@ -48,10 +49,12 @@ func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL { } +// Split is used to get the fields of the url func Split(r rune) bool { return r == '/' || r == '?' || r == ':' } +// getTopic is used to return the topic func getTopic(r rune) bool { return r == '=' } diff --git a/pkg/services/mqtt/mqtt_json.go b/pkg/services/mqtt/mqtt_json.go index 4c1fa7a9..d8d038b3 100644 --- a/pkg/services/mqtt/mqtt_json.go +++ b/pkg/services/mqtt/mqtt_json.go @@ -1,12 +1,13 @@ package mqtt -// JSON to be used as a notification payload for the telegram notification service +// SendMessagePayload sets the JSON to be used as a notification payload for the telegram notification service type SendMessagePayload struct { Text string `json:"text"` Topic string `json:"topic"` ParseMode string `json:"parse_mode,omitempty"` } +// createSendMessagePayload is used to define the payload func createSendMessagePayload(message string, topic string, config *Config) SendMessagePayload { payload := SendMessagePayload{ Text: message, From cba79ac237aa79852cc081c2daa299521f7e399e Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Tue, 6 Apr 2021 22:01:32 -0300 Subject: [PATCH 03/10] feat(mqtt): add tls support to mqtt Co-authored-by: Mexazonic --- pkg/services/mqtt/mqtt.go | 71 ++++++++---------- pkg/services/mqtt/mqtt_config.go | 111 ++++++++++++++++++++-------- pkg/services/mqtt/mqtt_json.go | 22 ------ pkg/services/mqtt/mqtt_parsemode.go | 34 --------- 4 files changed, 113 insertions(+), 125 deletions(-) delete mode 100644 pkg/services/mqtt/mqtt_json.go delete mode 100644 pkg/services/mqtt/mqtt_parsemode.go diff --git a/pkg/services/mqtt/mqtt.go b/pkg/services/mqtt/mqtt.go index 67bbd3df..06766e3a 100644 --- a/pkg/services/mqtt/mqtt.go +++ b/pkg/services/mqtt/mqtt.go @@ -1,13 +1,12 @@ package mqtt import ( - "encoding/json" - "errors" "fmt" "log" "net/url" "github.com/containrrr/shoutrrr/pkg/format" + "github.com/containrrr/shoutrrr/pkg/util" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/containrrr/shoutrrr/pkg/services/standard" @@ -15,7 +14,7 @@ import ( ) const ( - maxlength = 4096 + maxLength = 268435455 ) // Service sends notifications to mqtt topic @@ -27,8 +26,11 @@ type Service struct { // Send notification to mqtt func (service *Service) Send(message string, params *types.Params) error { - if len(message) > maxlength { - return errors.New("message exceeds the max length") + + message, omitted := MessageLimit(message) + + if omitted > 0 { + service.Logf("omitted %v character(s) from the message", omitted) } config := *service.config @@ -36,63 +38,52 @@ func (service *Service) Send(message string, params *types.Params) error { return err } - return publishMessageToTopic(message, &config) + if err := service.PublishMessageToTopic(message, &config); err != nil { + return fmt.Errorf("an error occurred while sending notification to generic webhook: %s", err.Error()) + } + + return nil } // Initialize loads ServiceConfig from configURL and sets logger for this Service func (service *Service) Initialize(configURL *url.URL, logger *log.Logger) error { service.Logger.SetLogger(logger) service.config = &Config{ - DisableTLS: true, + DisableTLS: false, + Port: 8883, } service.pkr = format.NewPropKeyResolver(service.config) - - err := service.config.setURL(&service.pkr, configURL) - - if err == nil { + if err := service.config.setURL(&service.pkr, configURL); err != nil { return err } return nil } +func MessageLimit(message string) (string, int) { + size := util.Min(maxLength, len(message)) + omitted := len(message) - size + + return message[:size], omitted +} + // GetConfig returns the Config for the service func (service *Service) GetConfig() *Config { return service.config } -// Handle Connection Lost -var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) { - fmt.Printf("Connect lost: %v", err) -} - // Publish to topic -func publish(client mqtt.Client, topic string, data []byte) { - token := client.Publish(topic, 0, false, data) +func (service *Service) Publish(client mqtt.Client, topic string, message string) { + token := client.Publish(topic, 0, false, message) token.Wait() } -// Publish payload -func publishMessageToTopic(message string, config *Config) error { - postURL := fmt.Sprintf("tcp://%s:%d", config.Host, config.Port) - payload := createSendMessagePayload(message, config.Topic, config) - - jsonData, err := json.Marshal(payload) - if err != nil { - return err - } - - // Config - opts := mqtt.NewClientOptions() - - opts.AddBroker(postURL) - - opts.OnConnectionLost = connectLostHandler - - // Start client +// PublishMessageToTopic +func (service *Service) PublishMessageToTopic(message string, config *Config) error { + postURL := config.MqttURL() + opts := config.GetClientConfig(postURL) client := mqtt.NewClient(opts) - - token := client.Connect(); + token := client.Connect() if token.Error() != nil { return token.Error() @@ -100,9 +91,9 @@ func publishMessageToTopic(message string, config *Config) error { token.Wait() - publish(client, config.Topic, jsonData) + service.Publish(client, config.Topic, message) - client.Disconnect(1) + client.Disconnect(250) return nil } diff --git a/pkg/services/mqtt/mqtt_config.go b/pkg/services/mqtt/mqtt_config.go index 948aa67a..6f3cd3ac 100644 --- a/pkg/services/mqtt/mqtt_config.go +++ b/pkg/services/mqtt/mqtt_config.go @@ -1,29 +1,33 @@ package mqtt import ( + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" + "log" "net/url" "strconv" - "strings" "github.com/containrrr/shoutrrr/pkg/format" "github.com/containrrr/shoutrrr/pkg/types" + mqtt "github.com/eclipse/paho.mqtt.golang" ) // Config for use within the mqtt type Config struct { - Host string `key:"broker" default:"" desc:"MQTT broker server hostname or IP address"` - Port int64 `key:"port" default:"1883" desc:"TCP Port"` - Topic string `key:"topic" default:"" desc:"Topic where the message is sent"` - DisableTLS bool `key:"disabletls" default:"Yes"` - ParseMode parseMode `key:"parsemode" default:"None" desc:"How the text message should be parsed"` + Host string `key:"host" default:"" desc:"MQTT broker server hostname or IP address"` + Port uint16 `key:"port" default:"8883" desc:"SMTP server port, common ones are 8883, 1883"` + Topic string `key:"topic" default:"" desc:"Topic where the message is sent"` + ClientId string `key:"clientid" default:"" desc:"client's id from the message is sent"` + Username string `key:"username" default:"" desc:"username for auth"` + Password string `key:"password" default:"" desc:"password for auth"` + DisableTLS bool `key:"disabletls" default:"No"` } // Enums returns the fields that should use a corresponding EnumFormatter to Print/Parse their values func (config *Config) Enums() map[string]types.EnumFormatter { - return map[string]types.EnumFormatter{ - "ParseMode": parseModes.Enum, - } + return map[string]types.EnumFormatter{} } // GetURL returns a URL representation of it's current field values @@ -39,7 +43,7 @@ func (config *Config) SetURL(url *url.URL) error { } func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL { - + return &url.URL{ Host: fmt.Sprintf("%s:%d", config.Host, config.Port), Scheme: Scheme, @@ -49,37 +53,86 @@ func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL { } -// Split is used to get the fields of the url -func Split(r rune) bool { - return r == '/' || r == '?' || r == ':' +func (config *Config) setURL(resolver types.ConfigQueryResolver, url *url.URL) error { + + config.Host = url.Hostname() + + if port, err := strconv.ParseUint(url.Port(), 10, 16); err == nil { + config.Port = uint16(port) + } + + for key, vals := range url.Query() { + if err := resolver.Set(key, vals[0]); err != nil { + return err + } + } + + return nil } -// getTopic is used to return the topic -func getTopic(r rune) bool { - return r == '=' +// MqttURL returns a string that is synchronized with the config props +func (config *Config) MqttURL() string { + MqttHost := config.Host + MqttPort := config.Port + scheme := DefaultWebhookScheme + if config.DisableTLS { + scheme = Scheme[:4] + } + return fmt.Sprintf("%s://%s:%d", scheme, MqttHost, MqttPort) } -func (config *Config) setURL(resolver types.ConfigQueryResolver, url *url.URL) error { +// MqttURL return the client options +func (config *Config) GetClientConfig(postURL string) *mqtt.ClientOptions { + opts := mqtt.NewClientOptions() + + opts.AddBroker(postURL) - u := strings.FieldsFunc(url.String(), Split) - topic := strings.FieldsFunc(url.String(), getTopic) + if len(config.ClientId) > 0 { + opts.SetClientID(config.ClientId) + } - port, err := strconv.ParseInt(u[2], 10, 64) + if len(config.Username) > 0 { + opts.SetUsername(config.Username) + } - if err != nil { - return err + if len(config.Password) > 0 { + opts.SetPassword(config.Password) } - if len(u) > 4 { - config.Host = u[1] - config.Port = port - config.Topic = topic[1] + if !config.DisableTLS { + tlsConfig := config.GetTlsConfig() + opts.SetTLSConfig(tlsConfig) } - return nil + return opts +} + +// GetTlsConfig returns the configuration with the certificates for TLS +func (config *Config) GetTlsConfig() *tls.Config { + certpool := x509.NewCertPool() + ca, err := ioutil.ReadFile("ca.crt") + + if err != nil { + log.Fatalln(err.Error()) + } + certpool.AppendCertsFromPEM(ca) + + clientKeyPair, err := tls.LoadX509KeyPair("client.crt", "client.key") + if err != nil { + panic(err) + } + return &tls.Config{ + RootCAs: certpool, + ClientAuth: tls.NoClientCert, + ClientCAs: nil, + InsecureSkipVerify: true, + Certificates: []tls.Certificate{clientKeyPair}, + } } -// Scheme is the identifying part of this service's configuration URL const ( - Scheme = "tcp" + // Scheme is the identifying part of this service's configuration URL + Scheme = "mqtt" + // DefaultWebhookScheme is the scheme used for webhook URLs unless overridden + DefaultWebhookScheme = "mqtts" ) diff --git a/pkg/services/mqtt/mqtt_json.go b/pkg/services/mqtt/mqtt_json.go deleted file mode 100644 index d8d038b3..00000000 --- a/pkg/services/mqtt/mqtt_json.go +++ /dev/null @@ -1,22 +0,0 @@ -package mqtt - -// SendMessagePayload sets the JSON to be used as a notification payload for the telegram notification service -type SendMessagePayload struct { - Text string `json:"text"` - Topic string `json:"topic"` - ParseMode string `json:"parse_mode,omitempty"` -} - -// createSendMessagePayload is used to define the payload -func createSendMessagePayload(message string, topic string, config *Config) SendMessagePayload { - payload := SendMessagePayload{ - Text: message, - Topic: topic, - } - - if config.ParseMode != parseModes.None { - payload.ParseMode = config.ParseMode.String() - } - - return payload -} diff --git a/pkg/services/mqtt/mqtt_parsemode.go b/pkg/services/mqtt/mqtt_parsemode.go deleted file mode 100644 index 43fefaf0..00000000 --- a/pkg/services/mqtt/mqtt_parsemode.go +++ /dev/null @@ -1,34 +0,0 @@ -package mqtt - -import ( - "github.com/containrrr/shoutrrr/pkg/format" - "github.com/containrrr/shoutrrr/pkg/types" -) - -type parseMode int - -type parseModeVals struct { - None parseMode - Markdown parseMode - HTML parseMode - MarkdownV2 parseMode - Enum types.EnumFormatter -} - -var parseModes = &parseModeVals{ - None: 0, - Markdown: 1, - HTML: 2, - MarkdownV2: 3, - Enum: format.CreateEnumFormatter( - []string{ - "None", - "Markdown", - "HTML", - "MarkdownV2", - }), -} - -func (pm parseMode) String() string { - return parseModes.Enum.Print(int(pm)) -} From 333ad76c5bc9e501a22c2bd82225b46dbb6cb5e0 Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Tue, 6 Apr 2021 22:01:59 -0300 Subject: [PATCH 04/10] docs(mqtt): add docs to mqtt service Co-authored-by: Mexazonic --- docs/services/mqtt.md | 50 +++++++++++++++++++++++++++++++++++++++ docs/services/overview.md | 29 ++++++++++++----------- 2 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 docs/services/mqtt.md diff --git a/docs/services/mqtt.md b/docs/services/mqtt.md new file mode 100644 index 00000000..2d544d2e --- /dev/null +++ b/docs/services/mqtt.md @@ -0,0 +1,50 @@ +# MQTT + +## URL Format + +_mqtt://**`host`**:**`port`**?topic=**`topic`**_ + +## Optional parameters + +You can optionally specify the **`disableTLS`**, **`clientId`**, **`username`** and **`password`** parameters in the URL: +_mqtt://**`host`**:**`port`**?topic=**`topic`**&disableTLS=true&clientId=**`clientId`**&username=**`username`**&password:**`password`**_ + +## Parameters Description + +- **Host** - MQTT broker server hostname or IP address (**Required**) + Default: _empty_ + Aliases: `host` + +- **Port** - MQTT server port, common ones are 8883 for TCP/TLS and 1883 for TCP (**Required**) + Default: `8883` + +- **Topic** - Topic where the message is sent (**Required**) + Default: _empty_ + Aliases: `Topic` + +- **DisableTLS** - disable TLS/SSL Configurations + Default: `false` + +- **ClientID** - The client identifier (ClientId) identifies each MQTT client that connects to an MQTT + Default: _empty_ + Aliases: `clientId` + +- **Username** - name of the sender to auth + Default: _empty_ + Aliases: `clientId` + +- **Password** - authentication password or hash + Default: _empty_ + Aliases: `password` + +## Certificates to use TCP/TLS + +To use TCP/TLS connection, it is necessary the files: + +- Cerficate Authority: ca.crt +- Client Certificate: client.crt +- Client Key: client.key + +## Configure TLS in mosquitto + +Generate the certificates [mosquitto-tls](https://mosquitto.org/man/mosquitto-tls-7.html). diff --git a/docs/services/overview.md b/docs/services/overview.md index c416a451..ab9fbe28 100644 --- a/docs/services/overview.md +++ b/docs/services/overview.md @@ -4,17 +4,18 @@ Click on the service for a more thorough explanation. | Service | URL format | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| [Discord](./discord.md) | *discord://__`token`__@__`id`__* | -| [Telegram](./telegram.md) | *telegram://__`token`__@telegram?channels=__`channel-1`__[,__`channel-2`__,...]* | -| [Pushover](./pushover.md) | *pushover://shoutrrr:__`apiToken`__@__`userKey`__/?devices=__`device1`__[,__`device2`__, ...]* | -| [Slack](./not-documented.md) | *slack://[__`botname`__@]__`token-a`__/__`token-b`__/__`token-c`__* | -| [Email](./not-documented.md) | *smtp://__`username`__:__`password`__@__`host`__:__`port`__/?fromAddress=__`fromAddress`__&toAddresses=__`recipient1`__[,__`recipient2`__,...]* | -| [Microsoft Teams](./teams.md) | *teams://__`token-a`__/__`token-b`__/__`token-c`__* | -| [Gotify](./not-documented.md) | *gotify://__`gotify-host`__/__`token`__* | -| [Pushbullet](./not-documented.md) | *pushbullet://__`api-token`__[/__`device`__/#__`channel`__/__`email`__]* | -| [IFTTT](./not-documented.md) | *ifttt://__`key`__/?events=__`event1`__[,__`event2`__,...]&value1=__`value1`__&value2=__`value2`__&value3=__`value3`__* | -| [Mattermost](./not-documented.md) | *mattermost://[__`username`__@]__`mattermost-host`__/__`token`__[/__`channel`__]* | -| [Hangouts Chat](./hangouts.md) | *hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz* | -| [Zulip Chat](./zulip.md) | *zulip://__`bot-mail`__:__`bot-key`__@__`zulip-domain`__/?stream=__`name-or-id`__&topic=__`name`__* | -| [Join](./not-documented.md) | *join://shoutrrr:__`api-key`__@join/?devices=__`device1`__[,__`device2`__, ...][&icon=__`icon`__][&title=__`title`__]* | -| [Rocketchat](./rocketchat.md) | *rocketchat://[__`username`__@]__`rocketchat-host`__/__`token`__[/__`channel`|`@recipient`__]* | +| [Discord](./discord.md) | _discord://**`token`**@**`id`**_ | +| [Telegram](./telegram.md) | _telegram://**`token`**@telegram?channels=**`channel-1`**[,__`channel-2`__,...]_ | +| [Pushover](./pushover.md) | _pushover://shoutrrr:**`apiToken`**@**`userKey`**/?devices=**`device1`**[,__`device2`__, ...]_ | +| [Slack](./not-documented.md) | _slack://[__`botname`__@]**`token-a`**/**`token-b`**/**`token-c`**_ | +| [Email](./not-documented.md) | _smtp://**`username`**:**`password`**@**`host`**:**`port`**/?fromAddress=**`fromAddress`**&toAddresses=**`recipient1`**[,__`recipient2`__,...]_ | +| [Microsoft Teams](./teams.md) | _teams://**`token-a`**/**`token-b`**/**`token-c`**_ | +| [Gotify](./not-documented.md) | _gotify://**`gotify-host`**/**`token`**_ | +| [Pushbullet](./not-documented.md) | _pushbullet://**`api-token`**[/__`device`__/#__`channel`__/__`email`__]_ | +| [IFTTT](./not-documented.md) | _ifttt://**`key`**/?events=**`event1`**[,__`event2`__,...]&value1=**`value1`**&value2=**`value2`**&value3=**`value3`**_ | +| [Mattermost](./not-documented.md) | _mattermost://[__`username`__@]**`mattermost-host`**/**`token`**[/__`channel`__]_ | +| [MQTT](./mqtt.md) | _mqtt://**`host`**:**`port`**?topic=**`topic`**_ | +| [Hangouts Chat](./hangouts.md) | _hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz_ | +| [Zulip Chat](./zulip.md) | _zulip://**`bot-mail`**:**`bot-key`**@**`zulip-domain`**/?stream=**`name-or-id`**&topic=**`name`**_ | +| [Join](./not-documented.md) | _join://shoutrrr:**`api-key`**@join/?devices=**`device1`**[,**`device2`**, ...][&icon=__`icon`__][&title=__`title`__]_ | +| [Rocketchat](./rocketchat.md) | _rocketchat://[__`username`__@]**`rocketchat-host`**/**`token`**[/__`channel`|`@recipient`__]_ | From 2243be02f2ef8840c4a1453297e8098f4c7a9a22 Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Tue, 6 Apr 2021 22:49:16 -0300 Subject: [PATCH 05/10] feat(mqtt): improve codacy issues Co-authored-by: Mexazonic --- docs/services/mqtt.md | 10 +++++----- pkg/services/mqtt/mqtt.go | 9 ++++----- pkg/services/mqtt/mqtt_config.go | 12 ++++++------ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/services/mqtt.md b/docs/services/mqtt.md index 2d544d2e..c3eafd59 100644 --- a/docs/services/mqtt.md +++ b/docs/services/mqtt.md @@ -6,8 +6,8 @@ _mqtt://**`host`**:**`port`**?topic=**`topic`**_ ## Optional parameters -You can optionally specify the **`disableTLS`**, **`clientId`**, **`username`** and **`password`** parameters in the URL: -_mqtt://**`host`**:**`port`**?topic=**`topic`**&disableTLS=true&clientId=**`clientId`**&username=**`username`**&password:**`password`**_ +You can optionally specify the **`disableTLS`**, **`clientID`**, **`username`** and **`password`** parameters in the URL: +_mqtt://**`host`**:**`port`**?topic=**`topic`**&disableTLS=true&clientID=**`clientID`**&username=**`username`**&password:**`password`**_ ## Parameters Description @@ -25,13 +25,13 @@ _mqtt://**`host`**:**`port`**?topic=**`topic`**&disableTLS=true&clientId=**`clie - **DisableTLS** - disable TLS/SSL Configurations Default: `false` -- **ClientID** - The client identifier (ClientId) identifies each MQTT client that connects to an MQTT +- **ClientID** - The client identifier (ClientID) identifies each MQTT client that connects to an MQTT Default: _empty_ - Aliases: `clientId` + Aliases: `clientID` - **Username** - name of the sender to auth Default: _empty_ - Aliases: `clientId` + Aliases: `clientID` - **Password** - authentication password or hash Default: _empty_ diff --git a/pkg/services/mqtt/mqtt.go b/pkg/services/mqtt/mqtt.go index 06766e3a..5b38065a 100644 --- a/pkg/services/mqtt/mqtt.go +++ b/pkg/services/mqtt/mqtt.go @@ -53,13 +53,12 @@ func (service *Service) Initialize(configURL *url.URL, logger *log.Logger) error Port: 8883, } service.pkr = format.NewPropKeyResolver(service.config) - if err := service.config.setURL(&service.pkr, configURL); err != nil { - return err - } + err := service.config.setURL(&service.pkr, configURL) - return nil + return err } +// MessageLimit returns a string with the maximum size and the amount of omitted characters func MessageLimit(message string) (string, int) { size := util.Min(maxLength, len(message)) omitted := len(message) - size @@ -78,7 +77,7 @@ func (service *Service) Publish(client mqtt.Client, topic string, message string token.Wait() } -// PublishMessageToTopic +// PublishMessageToTopic initializes the client and publishes the message func (service *Service) PublishMessageToTopic(message string, config *Config) error { postURL := config.MqttURL() opts := config.GetClientConfig(postURL) diff --git a/pkg/services/mqtt/mqtt_config.go b/pkg/services/mqtt/mqtt_config.go index 6f3cd3ac..12b45013 100644 --- a/pkg/services/mqtt/mqtt_config.go +++ b/pkg/services/mqtt/mqtt_config.go @@ -19,7 +19,7 @@ type Config struct { Host string `key:"host" default:"" desc:"MQTT broker server hostname or IP address"` Port uint16 `key:"port" default:"8883" desc:"SMTP server port, common ones are 8883, 1883"` Topic string `key:"topic" default:"" desc:"Topic where the message is sent"` - ClientId string `key:"clientid" default:"" desc:"client's id from the message is sent"` + ClientID string `key:"clientid" default:"" desc:"client's id from the message is sent"` Username string `key:"username" default:"" desc:"username for auth"` Password string `key:"password" default:"" desc:"password for auth"` DisableTLS bool `key:"disabletls" default:"No"` @@ -81,14 +81,14 @@ func (config *Config) MqttURL() string { return fmt.Sprintf("%s://%s:%d", scheme, MqttHost, MqttPort) } -// MqttURL return the client options +// GetClientConfig returns the client options func (config *Config) GetClientConfig(postURL string) *mqtt.ClientOptions { opts := mqtt.NewClientOptions() opts.AddBroker(postURL) - if len(config.ClientId) > 0 { - opts.SetClientID(config.ClientId) + if len(config.ClientID) > 0 { + opts.SetClientID(config.ClientID) } if len(config.Username) > 0 { @@ -100,7 +100,7 @@ func (config *Config) GetClientConfig(postURL string) *mqtt.ClientOptions { } if !config.DisableTLS { - tlsConfig := config.GetTlsConfig() + tlsConfig := config.GetTLSConfig() opts.SetTLSConfig(tlsConfig) } @@ -108,7 +108,7 @@ func (config *Config) GetClientConfig(postURL string) *mqtt.ClientOptions { } // GetTlsConfig returns the configuration with the certificates for TLS -func (config *Config) GetTlsConfig() *tls.Config { +func (config *Config) GetTLSConfig() *tls.Config { certpool := x509.NewCertPool() ca, err := ioutil.ReadFile("ca.crt") From 365f8c32cd72cab548f551a432722a24ad9d9395 Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Tue, 6 Apr 2021 23:08:41 -0300 Subject: [PATCH 06/10] docs(mqtt): fix overview page of docs --- docs/services/overview.md | 32 +++++++++++++++++--------------- pkg/services/mqtt/mqtt_config.go | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/services/overview.md b/docs/services/overview.md index ab9fbe28..a42fdfb1 100644 --- a/docs/services/overview.md +++ b/docs/services/overview.md @@ -4,18 +4,20 @@ Click on the service for a more thorough explanation. | Service | URL format | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| [Discord](./discord.md) | _discord://**`token`**@**`id`**_ | -| [Telegram](./telegram.md) | _telegram://**`token`**@telegram?channels=**`channel-1`**[,__`channel-2`__,...]_ | -| [Pushover](./pushover.md) | _pushover://shoutrrr:**`apiToken`**@**`userKey`**/?devices=**`device1`**[,__`device2`__, ...]_ | -| [Slack](./not-documented.md) | _slack://[__`botname`__@]**`token-a`**/**`token-b`**/**`token-c`**_ | -| [Email](./not-documented.md) | _smtp://**`username`**:**`password`**@**`host`**:**`port`**/?fromAddress=**`fromAddress`**&toAddresses=**`recipient1`**[,__`recipient2`__,...]_ | -| [Microsoft Teams](./teams.md) | _teams://**`token-a`**/**`token-b`**/**`token-c`**_ | -| [Gotify](./not-documented.md) | _gotify://**`gotify-host`**/**`token`**_ | -| [Pushbullet](./not-documented.md) | _pushbullet://**`api-token`**[/__`device`__/#__`channel`__/__`email`__]_ | -| [IFTTT](./not-documented.md) | _ifttt://**`key`**/?events=**`event1`**[,__`event2`__,...]&value1=**`value1`**&value2=**`value2`**&value3=**`value3`**_ | -| [Mattermost](./not-documented.md) | _mattermost://[__`username`__@]**`mattermost-host`**/**`token`**[/__`channel`__]_ | -| [MQTT](./mqtt.md) | _mqtt://**`host`**:**`port`**?topic=**`topic`**_ | -| [Hangouts Chat](./hangouts.md) | _hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz_ | -| [Zulip Chat](./zulip.md) | _zulip://**`bot-mail`**:**`bot-key`**@**`zulip-domain`**/?stream=**`name-or-id`**&topic=**`name`**_ | -| [Join](./not-documented.md) | _join://shoutrrr:**`api-key`**@join/?devices=**`device1`**[,**`device2`**, ...][&icon=__`icon`__][&title=__`title`__]_ | -| [Rocketchat](./rocketchat.md) | _rocketchat://[__`username`__@]**`rocketchat-host`**/**`token`**[/__`channel`|`@recipient`__]_ | +| [Discord](./discord.md) | *discord://__`token`__@__`id`__* | +| [Email](./email.md) | *smtp://__`username`__:__`password`__@__`host`__:__`port`__/?fromAddress=__`fromAddress`__&toAddresses=__`recipient1`__[,__`recipient2`__,...]* | +| [Gotify](./gotify.md) | *gotify://__`gotify-host`__/__`token`__* | +| [Hangouts Chat](./hangouts.md) | *hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz* | +| [IFTTT](./ifttt.md) | *ifttt://__`key`__/?events=__`event1`__[,__`event2`__,...]&value1=__`value1`__&value2=__`value2`__&value3=__`value3`__* | +| [Join](./join.md) | *join://shoutrrr:__`api-key`__@join/?devices=__`device1`__[,__`device2`__, ...][&icon=__`icon`__][&title=__`title`__]* | +| [Mattermost](./mattermost.md) | *mattermost://[__`username`__@]__`mattermost-host`__/__`token`__[/__`channel`__]* | +| [MQTT](./mqtt.md) | *mqtt://__`host`__:__`port`__?topic=__`topic`__* +| [OpsGenie](./opsgenie.md) | *opsgenie://__`host`__/token?responders=__`responder1`__[,__`responder2`__]* | +| [Pushbullet](./pushbullet.md) | *pushbullet://__`api-token`__[/__`device`__/#__`channel`__/__`email`__]* | +| [Pushover](./pushover.md) | *pushover://shoutrrr:__`apiToken`__@__`userKey`__/?devices=__`device1`__[,__`device2`__, ...]* | +| [Rocketchat](./rocketchat.md) | *rocketchat://[__`username`__@]__`rocketchat-host`__/__`token`__[/__`channel`|`@recipient`__]* | +| [Slack](./slack.md) | *slack://[__`botname`__@]__`token-a`__/__`token-b`__/__`token-c`__* | +| [Teams](./teams.md) | *teams://__`token-a`__/__`token-b`__/__`token-c`__* | +| [Telegram](./telegram.md) | *telegram://__`token`__@telegram?channels=__`channel-1`__[,__`channel-2`__,...]* | +| [Zulip Chat](./zulip.md) | *zulip://__`bot-mail`__:__`bot-key`__@__`zulip-domain`__/?stream=__`name-or-id`__&topic=__`name`__* | + diff --git a/pkg/services/mqtt/mqtt_config.go b/pkg/services/mqtt/mqtt_config.go index 12b45013..4c0c9b4d 100644 --- a/pkg/services/mqtt/mqtt_config.go +++ b/pkg/services/mqtt/mqtt_config.go @@ -107,7 +107,7 @@ func (config *Config) GetClientConfig(postURL string) *mqtt.ClientOptions { return opts } -// GetTlsConfig returns the configuration with the certificates for TLS +// GetTLSConfig returns the configuration with the certificates for TLS func (config *Config) GetTLSConfig() *tls.Config { certpool := x509.NewCertPool() ca, err := ioutil.ReadFile("ca.crt") From 87b65b133e6219bfb43dccd7d38151c824bf14e3 Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Tue, 6 Apr 2021 23:32:45 -0300 Subject: [PATCH 07/10] feat(mqtt): fix config description --- pkg/services/mqtt/mqtt_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/mqtt/mqtt_config.go b/pkg/services/mqtt/mqtt_config.go index 4c0c9b4d..df0fa95e 100644 --- a/pkg/services/mqtt/mqtt_config.go +++ b/pkg/services/mqtt/mqtt_config.go @@ -17,7 +17,7 @@ import ( // Config for use within the mqtt type Config struct { Host string `key:"host" default:"" desc:"MQTT broker server hostname or IP address"` - Port uint16 `key:"port" default:"8883" desc:"SMTP server port, common ones are 8883, 1883"` + Port uint16 `key:"port" default:"8883" desc:"MQTT server port, common ones are 8883, 1883"` Topic string `key:"topic" default:"" desc:"Topic where the message is sent"` ClientID string `key:"clientid" default:"" desc:"client's id from the message is sent"` Username string `key:"username" default:"" desc:"username for auth"` From 8c17b9681990a74a1e79bfeca30569f935461c11 Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Wed, 7 Apr 2021 10:16:24 -0300 Subject: [PATCH 08/10] feat(mqtt): improve mqtt description Co-authored-by: Mexazonic --- pkg/services/mqtt/mqtt.go | 2 +- pkg/services/mqtt/mqtt_config.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/services/mqtt/mqtt.go b/pkg/services/mqtt/mqtt.go index 5b38065a..94a9258b 100644 --- a/pkg/services/mqtt/mqtt.go +++ b/pkg/services/mqtt/mqtt.go @@ -39,7 +39,7 @@ func (service *Service) Send(message string, params *types.Params) error { } if err := service.PublishMessageToTopic(message, &config); err != nil { - return fmt.Errorf("an error occurred while sending notification to generic webhook: %s", err.Error()) + return fmt.Errorf("an error occurred while sending notification to the MQTT topic: %s", err.Error()) } return nil diff --git a/pkg/services/mqtt/mqtt_config.go b/pkg/services/mqtt/mqtt_config.go index df0fa95e..4d355efe 100644 --- a/pkg/services/mqtt/mqtt_config.go +++ b/pkg/services/mqtt/mqtt_config.go @@ -74,7 +74,7 @@ func (config *Config) setURL(resolver types.ConfigQueryResolver, url *url.URL) e func (config *Config) MqttURL() string { MqttHost := config.Host MqttPort := config.Port - scheme := DefaultWebhookScheme + scheme := DefaultMQTTScheme if config.DisableTLS { scheme = Scheme[:4] } @@ -133,6 +133,6 @@ func (config *Config) GetTLSConfig() *tls.Config { const ( // Scheme is the identifying part of this service's configuration URL Scheme = "mqtt" - // DefaultWebhookScheme is the scheme used for webhook URLs unless overridden - DefaultWebhookScheme = "mqtts" + // DefaultMQTTScheme is the scheme used for MQTT URLs unless overridden + DefaultMQTTScheme = "mqtts" ) From 5e1b0bb732d838308a60451256198131aa0317b7 Mon Sep 17 00:00:00 2001 From: Mexazonic Date: Thu, 6 May 2021 18:24:11 -0300 Subject: [PATCH 09/10] test(mqtt): add mqtt tests When TLS is enabled and disabled When generating a config object with/without optional arguments --- pkg/services/mqtt/mqtt_config.go | 8 ++ pkg/services/mqtt/mqtt_test.go | 160 +++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 pkg/services/mqtt/mqtt_test.go diff --git a/pkg/services/mqtt/mqtt_config.go b/pkg/services/mqtt/mqtt_config.go index 4d355efe..36bd17f5 100644 --- a/pkg/services/mqtt/mqtt_config.go +++ b/pkg/services/mqtt/mqtt_config.go @@ -25,6 +25,14 @@ type Config struct { DisableTLS bool `key:"disabletls" default:"No"` } +// DefaultConfig creates a PropKeyResolver and uses it to populate the default values of a new Config, returning both +func DefaultConfig() (*Config, format.PropKeyResolver) { + config := &Config{} + pkr := format.NewPropKeyResolver(config) + _ = pkr.SetDefaultProps(config) + return config, pkr +} + // Enums returns the fields that should use a corresponding EnumFormatter to Print/Parse their values func (config *Config) Enums() map[string]types.EnumFormatter { return map[string]types.EnumFormatter{} diff --git a/pkg/services/mqtt/mqtt_test.go b/pkg/services/mqtt/mqtt_test.go new file mode 100644 index 00000000..d9d2653b --- /dev/null +++ b/pkg/services/mqtt/mqtt_test.go @@ -0,0 +1,160 @@ +package mqtt + +import ( + "fmt" + "github.com/containrrr/shoutrrr/pkg/format" + "log" + "net/url" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var ( + logger = log.New(GinkgoWriter, "Test", log.LstdFlags) + service *Service + config *Config +) + +func TestMqtt(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "MQTT Suite") +} + +var _ = Describe("the MQTT service", func() { + BeforeEach(func() { + service = &Service{} + service.SetLogger(logger) + }) + + When("TLS is enabled", func() { + It("should use mqtts schema", func() { + broker := "localhost" + port := 8883 + disableTLS := "false" + config, _ := getTestConfig(fmt.Sprintf("mqtt://%s:%d?disableTLS=%s", broker, port, disableTLS)) + postURL := config.MqttURL() + + Expect(postURL).To(Equal(fmt.Sprintf("mqtts://%s:%d", broker, port))) + }) + }) + + When("TLS is disabled", func() { + It("should use mqtt schema", func() { + broker := "localhost" + port := 1883 + disableTLS := "true" + config, _ := getTestConfig(fmt.Sprintf("mqtt://%s:%d?disableTLS=%s", broker, port, disableTLS)) + postURL := config.MqttURL() + + Expect(postURL).To(Equal(fmt.Sprintf("mqtt://%s:%d", broker, port))) + }) + }) + + When("a MQTT URL is provided", func() { + It("should disable TLS", func() { + broker := "localhost" + port := 1883 + disableTLS := "true" + config, _ := getTestConfig(fmt.Sprintf("mqtt://%s:%d?disableTLS=%s", broker, port, disableTLS)) + config.MqttURL() + Expect(config.DisableTLS).To(BeTrue()) + }) + }) + + When("a MQTT URL is provided", func() { + It("should enable TLS", func() { + broker := "localhost" + port := 8883 + disableTLS := "false" + config, _ := getTestConfig(fmt.Sprintf("mqtt://%s:%d?disableTLS=%s", broker, port, disableTLS)) + config.MqttURL() + Expect(config.DisableTLS).To(BeFalse()) + }) + }) + + Describe("creating a config", func() { + When("creating a default config", func() { + It("should not return an error", func() { + config := &Config{} + pkr := format.NewPropKeyResolver(config) + err := pkr.SetDefaultProps(config) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + When("generating a config object with optional arguments", func() { + mqttURL, _ := url.Parse("mqtt://localhost:1883?topic=topic/test&disableTls=true&clientId=1&username=testUser&password=password") + config := &Config{} + err := config.SetURL(mqttURL) + It("should not have caused an error", func() { + Expect(err).NotTo(HaveOccurred()) + }) + It("should set host", func() { + Expect(config.Host).To(Equal("localhost")) + }) + It("should set Port", func() { + Expect(config.Port).To(Equal(uint16(1883))) + }) + + It("should set topic", func() { + Expect(config.Topic).To(Equal("topic/test")) + }) + + It("should set client", func() { + Expect(config.ClientID).To(Equal("1")) + }) + + It("should set username", func() { + Expect(config.Username).To(Equal("testUser")) + }) + + It("should set password", func() { + Expect(config.Password).To(Equal("password")) + }) + + It("should not set DisableTLS", func() { + Expect(config.DisableTLS).Should(BeTrue()) + }) + }) + + When("generating a config object without optional arguments", func() { + mqttURL, _ := url.Parse("mqtt://localhost:1883?topic=topic/test") + config := &Config{} + err := config.SetURL(mqttURL) + It("should not have caused an error", func() { + Expect(err).NotTo(HaveOccurred()) + }) + + It("should not set client", func() { + Expect(config.ClientID).To(Equal("")) + }) + + It("should set username", func() { + Expect(config.Username).To(Equal("")) + }) + + It("should set password", func() { + Expect(config.Password).To(Equal("")) + }) + + It("should set DisableTLS", func() { + Expect(config.DisableTLS).Should(BeFalse()) + }) + }) + + }) +}) + +// GetTestConfig return the object config of the service +func getTestConfig(testURL string) (*Config, *url.URL) { + + serviceURL, err := url.Parse(testURL) + Expect(err).NotTo(HaveOccurred()) + config, pkr := DefaultConfig() + err = config.setURL(&pkr, serviceURL) + Expect(err).NotTo(HaveOccurred()) + + return config, config.getURL(&pkr) +} From fca43305000f258a344e5addc044e6b65dcc6a59 Mon Sep 17 00:00:00 2001 From: Eugenio Sales Date: Mon, 17 May 2021 17:28:45 -0300 Subject: [PATCH 10/10] conflict(mqtt): Remove conflict in overview doc and servicemap --- docs/services/overview.md | 20 ++++++++++++++++++-- pkg/router/servicemap.go | 26 +++++++++++++++----------- pkg/services/mqtt/mqtt_test.go | 4 ++-- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/docs/services/overview.md b/docs/services/overview.md index 4734ed90..25165d7e 100644 --- a/docs/services/overview.md +++ b/docs/services/overview.md @@ -1,6 +1,6 @@ # Services overview -Click on the service for a more thorough explanation. +Click on the service for a more thorough explanation. | Service | URL format | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | @@ -19,4 +19,20 @@ Click on the service for a more thorough explanation. | [Slack](./slack.md) | *slack://[__`botname`__@]__`token-a`__/__`token-b`__/__`token-c`__* | | [Teams](./teams.md) | *teams://__`token-a`__/__`token-b`__/__`token-c`__* | | [Telegram](./telegram.md) | *telegram://__`token`__@telegram?channels=__`channel-1`__[,__`channel-2`__,...]* | -| [Zulip Chat](./zulip.md) | *zulip://__`bot-mail`__:__`bot-key`__@__`zulip-domain`__/?stream=__`name-or-id`__&topic=__`name`__* | \ No newline at end of file +| [Zulip Chat](./zulip.md) | *zulip://__`bot-mail`__:__`bot-key`__@__`zulip-domain`__/?stream=__`name-or-id`__&topic=__`name`__* | + + +## Specialized services + +| Service | Description | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| [Logger](./logger.md) | Writes notification to a configured go `log.Logger` | + +## Upcoming services + +*Note that these are not available in the current release* + +| Service | Description | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| [Generic Webhook](./generic.md) | Sends notifications directly to a webhook | +| [Matrix](./matrix.md) | *matrix://__`username`__:__`password`__@__`host`__:__`port`__/[?rooms=__`!roomID1`__[,__`roomAlias2`__]]* | diff --git a/pkg/router/servicemap.go b/pkg/router/servicemap.go index f9c3ca8f..3833ccfd 100644 --- a/pkg/router/servicemap.go +++ b/pkg/router/servicemap.go @@ -2,11 +2,13 @@ package router import ( "github.com/containrrr/shoutrrr/pkg/services/discord" + "github.com/containrrr/shoutrrr/pkg/services/generic" "github.com/containrrr/shoutrrr/pkg/services/gotify" "github.com/containrrr/shoutrrr/pkg/services/hangouts" "github.com/containrrr/shoutrrr/pkg/services/ifttt" "github.com/containrrr/shoutrrr/pkg/services/join" "github.com/containrrr/shoutrrr/pkg/services/logger" + "github.com/containrrr/shoutrrr/pkg/services/matrix" "github.com/containrrr/shoutrrr/pkg/services/mattermost" "github.com/containrrr/shoutrrr/pkg/services/mqtt" "github.com/containrrr/shoutrrr/pkg/services/opsgenie" @@ -24,21 +26,23 @@ import ( var serviceMap = map[string]func() t.Service{ "discord": func() t.Service { return &discord.Service{} }, + "generic": func() t.Service { return &generic.Service{} }, + "gotify": func() t.Service { return &gotify.Service{} }, + "hangouts": func() t.Service { return &hangouts.Service{} }, + "ifttt": func() t.Service { return &ifttt.Service{} }, + "join": func() t.Service { return &join.Service{} }, + "logger": func() t.Service { return &logger.Service{} }, + "matrix": func() t.Service { return &matrix.Service{} }, + "mattermost": func() t.Service { return &mattermost.Service{} }, + "mqtt": func() t.Service { return &mqtt.Service{} }, + "opsgenie": func() t.Service { return &opsgenie.Service{} }, + "pushbullet": func() t.Service { return &pushbullet.Service{} }, "pushover": func() t.Service { return &pushover.Service{} }, + "rocketchat": func() t.Service { return &rocketchat.Service{} }, "slack": func() t.Service { return &slack.Service{} }, + "smtp": func() t.Service { return &smtp.Service{} }, "teams": func() t.Service { return &teams.Service{} }, "telegram": func() t.Service { return &telegram.Service{} }, - "smtp": func() t.Service { return &smtp.Service{} }, - "ifttt": func() t.Service { return &ifttt.Service{} }, - "gotify": func() t.Service { return &gotify.Service{} }, - "logger": func() t.Service { return &logger.Service{} }, "xmpp": func() t.Service { return &xmpp.Service{} }, - "pushbullet": func() t.Service { return &pushbullet.Service{} }, - "mattermost": func() t.Service { return &mattermost.Service{} }, - "mqtt": func() t.Service { return &mqtt.Service{} }, - "hangouts": func() t.Service { return &hangouts.Service{} }, "zulip": func() t.Service { return &zulip.Service{} }, - "join": func() t.Service { return &join.Service{} }, - "rocketchat": func() t.Service { return &rocketchat.Service{} }, - "opsgenie": func() t.Service { return &opsgenie.Service{} }, } diff --git a/pkg/services/mqtt/mqtt_test.go b/pkg/services/mqtt/mqtt_test.go index d9d2653b..73ccce43 100644 --- a/pkg/services/mqtt/mqtt_test.go +++ b/pkg/services/mqtt/mqtt_test.go @@ -2,11 +2,12 @@ package mqtt import ( "fmt" - "github.com/containrrr/shoutrrr/pkg/format" "log" "net/url" "testing" + "github.com/containrrr/shoutrrr/pkg/format" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -14,7 +15,6 @@ import ( var ( logger = log.New(GinkgoWriter, "Test", log.LstdFlags) service *Service - config *Config ) func TestMqtt(t *testing.T) {