feat: add template support for shoutrrr notifications (#515)

pull/544/head
Arne Jørgensen 5 years ago committed by GitHub
parent 7052346570
commit 10fd81a2c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -179,6 +179,14 @@ To send notifications via shoutrrr, the following command-line options, or their
Go to [containrrr.github.io/shoutrrr/services/overview](https://containrrr.github.io/shoutrrr/services/overview) to learn more about the different service URLs you can use. Go to [containrrr.github.io/shoutrrr/services/overview](https://containrrr.github.io/shoutrrr/services/overview) to learn more about the different service URLs you can use.
You can define multiple services by space separating the URLs. (See example below) You can define multiple services by space separating the URLs. (See example below)
You can customize the message posted by setting a template.
- `--notification-template` (env. `WATCHTOWER_NOTIFICATION_TEMPLATE`): The template used for the message.
The template is a Go [template](https://golang.org/pkg/text/template/) and the you format a list of [log entries](https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry).
The default value if not set is `{{range .}}{{.Message}}{{println}}{{end}}`. The example below uses a template that also outputs timestamp and log level.
Example: Example:
```bash ```bash
@ -187,5 +195,6 @@ docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \ -v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=shoutrrr \ -e WATCHTOWER_NOTIFICATIONS=shoutrrr \
-e WATCHTOWER_NOTIFICATION_URL="discord://token@channel slack://watchtower@token-a/token-b/token-c" \ -e WATCHTOWER_NOTIFICATION_URL="discord://token@channel slack://watchtower@token-a/token-b/token-c" \
-e WATCHTOWER_NOTIFICATION_TEMPLATE="{{range .}}{{.Time.Format \"2006-01-02 15:04:05\"}} ({{.Level}}): {{.Message}}{{println}}{{end}}" \
containrrr/watchtower containrrr/watchtower
``` ```

@ -259,6 +259,12 @@ Should only be used for testing.
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"), viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
"The Gotify Application required to query the Gotify API") "The Gotify Application required to query the Gotify API")
flags.StringP(
"notification-template",
"",
viper.GetString("WATCHTOWER_NOTIFICATION_TEMPLATE"),
"The shoutrrr text/template for the messages")
flags.StringArrayP( flags.StringArrayP(
"notification-url", "notification-url",
"", "",

@ -1,7 +1,10 @@
package notifications package notifications
import ( import (
"bytes"
"fmt" "fmt"
"text/template"
"github.com/containrrr/shoutrrr" "github.com/containrrr/shoutrrr"
"github.com/containrrr/shoutrrr/pkg/router" "github.com/containrrr/shoutrrr/pkg/router"
t "github.com/containrrr/watchtower/pkg/types" t "github.com/containrrr/watchtower/pkg/types"
@ -10,7 +13,8 @@ import (
) )
const ( const (
shoutrrrType = "shoutrrr" shoutrrrDefaultTemplate = "{{range .}}{{.Message}}{{println}}{{end}}"
shoutrrrType = "shoutrrr"
) )
// Implements Notifier, logrus.Hook // Implements Notifier, logrus.Hook
@ -19,6 +23,7 @@ type shoutrrrTypeNotifier struct {
Router *router.ServiceRouter Router *router.ServiceRouter
entries []*log.Entry entries []*log.Entry
logLevels []log.Level logLevels []log.Level
template *template.Template
} }
func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier { func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
@ -31,6 +36,7 @@ func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Noti
Urls: urls, Urls: urls,
Router: r, Router: r,
logLevels: acceptedLogLevels, logLevels: acceptedLogLevels,
template: getShoutrrrTemplate(c),
} }
log.AddHook(n) log.AddHook(n)
@ -39,13 +45,10 @@ func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Noti
} }
func (e *shoutrrrTypeNotifier) buildMessage(entries []*log.Entry) string { func (e *shoutrrrTypeNotifier) buildMessage(entries []*log.Entry) string {
body := "" var body bytes.Buffer
for _, entry := range entries { e.template.Execute(&body, entries)
body += entry.Time.Format("2006-01-02 15:04:05") + " (" + entry.Level.String() + "): " + entry.Message + "\r\n"
// We don't use fields in watchtower, so don't bother sending them.
}
return body return body.String()
} }
func (e *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry) { func (e *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry) {
@ -91,3 +94,35 @@ func (e *shoutrrrTypeNotifier) Fire(entry *log.Entry) error {
} }
return nil return nil
} }
func getShoutrrrTemplate(c *cobra.Command) *template.Template {
var tpl *template.Template
flags := c.PersistentFlags()
tplString, err := flags.GetString("notification-template")
// If we succeed in getting a non-empty template configuration
// try to parse the template string.
if tplString != "" && err == nil {
tpl, err = template.New("").Parse(tplString)
}
// In case of errors (either from parsing the template string
// or from getting the template configuration) log an error
// message about this and the fact that we'll use the default
// template instead.
if err != nil {
log.Errorf("Could not use configured notification template: %s. Using default template", err)
}
// If we had an error (either from parsing the template string
// or from getting the template configuration) or we a
// template wasn't configured (the empty template string)
// fallback to using the default template.
if err != nil || tplString == "" {
tpl = template.Must(template.New("").Parse(shoutrrrDefaultTemplate))
}
return tpl
}

@ -0,0 +1,80 @@
package notifications
import (
"testing"
"text/template"
"github.com/containrrr/watchtower/internal/flags"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
)
func TestShoutrrrDefaultTemplate(t *testing.T) {
cmd := new(cobra.Command)
shoutrrr := &shoutrrrTypeNotifier{
template: getShoutrrrTemplate(cmd),
}
entries := []*log.Entry{
{
Message: "foo bar",
},
}
s := shoutrrr.buildMessage(entries)
require.Equal(t, "foo bar\n", s)
}
func TestShoutrrrTemplate(t *testing.T) {
cmd := new(cobra.Command)
flags.RegisterNotificationFlags(cmd)
err := cmd.ParseFlags([]string{"--notification-template={{range .}}{{.Level}}: {{.Message}}{{println}}{{end}}"})
require.NoError(t, err)
shoutrrr := &shoutrrrTypeNotifier{
template: getShoutrrrTemplate(cmd),
}
entries := []*log.Entry{
{
Level: log.InfoLevel,
Message: "foo bar",
},
}
s := shoutrrr.buildMessage(entries)
require.Equal(t, "info: foo bar\n", s)
}
func TestShoutrrrInvalidTemplateUsesTemplate(t *testing.T) {
cmd := new(cobra.Command)
flags.RegisterNotificationFlags(cmd)
err := cmd.ParseFlags([]string{"--notification-template={{"})
require.NoError(t, err)
shoutrrr := &shoutrrrTypeNotifier{
template: getShoutrrrTemplate(cmd),
}
shoutrrrDefault := &shoutrrrTypeNotifier{
template: template.Must(template.New("").Parse(shoutrrrDefaultTemplate)),
}
entries := []*log.Entry{
{
Message: "foo bar",
},
}
s := shoutrrr.buildMessage(entries)
sd := shoutrrrDefault.buildMessage(entries)
require.Equal(t, sd, s)
}
Loading…
Cancel
Save