mirror of
https://github.com/containrrr/watchtower.git
synced 2025-01-29 18:53:51 +02:00
feat: add template support for shoutrrr notifications (#515)
This commit is contained in:
parent
7052346570
commit
10fd81a2c1
@ -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
|
||||||
|
}
|
||||||
|
80
pkg/notifications/shoutrrr_test.go
Normal file
80
pkg/notifications/shoutrrr_test.go
Normal file
@ -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…
x
Reference in New Issue
Block a user