feat(notifications): add title field to template data (#1125)

pull/1183/head
nils måsén 3 years ago committed by GitHub
parent 1d59fb83dd
commit aa02d8d31b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -55,14 +55,14 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
return n return n
} }
func (e *emailTypeNotifier) GetURL(c *cobra.Command) (string, error) { func (e *emailTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
conf := &shoutrrrSmtp.Config{ conf := &shoutrrrSmtp.Config{
FromAddress: e.From, FromAddress: e.From,
FromName: "Watchtower", FromName: "Watchtower",
ToAddresses: []string{e.To}, ToAddresses: []string{e.To},
Port: uint16(e.Port), Port: uint16(e.Port),
Host: e.Server, Host: e.Server,
Subject: e.getSubject(c), Subject: e.getSubject(c, title),
Username: e.User, Username: e.User,
Password: e.Password, Password: e.Password,
UseStartTLS: !e.tlsSkipVerify, UseStartTLS: !e.tlsSkipVerify,
@ -86,12 +86,10 @@ func (e *emailTypeNotifier) GetDelay() time.Duration {
return e.delay return e.delay
} }
func (e *emailTypeNotifier) getSubject(c *cobra.Command) string { func (e *emailTypeNotifier) getSubject(_ *cobra.Command, title string) string {
subject := GetTitle(c)
if e.SubjectTag != "" { if e.SubjectTag != "" {
subject = e.SubjectTag + " " + subject return e.SubjectTag + " " + title
} }
return subject return title
} }

@ -62,7 +62,7 @@ func getGotifyURL(flags *pflag.FlagSet) string {
return gotifyURL return gotifyURL
} }
func (n *gotifyTypeNotifier) GetURL(c *cobra.Command) (string, error) { func (n *gotifyTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
apiURL, err := url.Parse(n.gotifyURL) apiURL, err := url.Parse(n.gotifyURL)
if err != nil { if err != nil {
return "", err return "", err
@ -72,7 +72,7 @@ func (n *gotifyTypeNotifier) GetURL(c *cobra.Command) (string, error) {
Host: apiURL.Host, Host: apiURL.Host,
Path: apiURL.Path, Path: apiURL.Path,
DisableTLS: apiURL.Scheme == "http", DisableTLS: apiURL.Scheme == "http",
Title: GetTitle(c), Title: title,
Token: n.gotifyAppToken, Token: n.gotifyAppToken,
} }

@ -1,11 +1,12 @@
package notifications package notifications
import ( import (
"net/url"
shoutrrrTeams "github.com/containrrr/shoutrrr/pkg/services/teams" shoutrrrTeams "github.com/containrrr/shoutrrr/pkg/services/teams"
t "github.com/containrrr/watchtower/pkg/types" t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"net/url"
) )
const ( const (
@ -37,7 +38,7 @@ func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Con
return n return n
} }
func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command) (string, error) { func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
webhookURL, err := url.Parse(n.webHookURL) webhookURL, err := url.Parse(n.webHookURL)
if err != nil { if err != nil {
return "", err return "", err
@ -49,7 +50,7 @@ func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command) (string, error) {
} }
config.Color = ColorHex config.Color = ColorHex
config.Title = GetTitle(c) config.Title = title
return config.GetURL().String(), nil return config.GetURL().String(), nil
} }

@ -30,14 +30,14 @@ func NewNotifier(c *cobra.Command) ty.Notifier {
tplString, _ := f.GetString("notification-template") tplString, _ := f.GetString("notification-template")
urls, _ := f.GetStringArray("notification-url") urls, _ := f.GetStringArray("notification-url")
urls, delay := AppendLegacyUrls(urls, c) hostname := GetHostname(c)
urls, delay := AppendLegacyUrls(urls, c, GetTitle(hostname))
title := GetTitle(c) return newShoutrrrNotifier(tplString, acceptedLogLevels, !reportTemplate, hostname, delay, urls...)
return newShoutrrrNotifier(tplString, acceptedLogLevels, !reportTemplate, title, delay, urls...)
} }
// AppendLegacyUrls creates shoutrrr equivalent URLs from legacy notification flags // AppendLegacyUrls creates shoutrrr equivalent URLs from legacy notification flags
func AppendLegacyUrls(urls []string, cmd *cobra.Command) ([]string, time.Duration) { func AppendLegacyUrls(urls []string, cmd *cobra.Command, title string) ([]string, time.Duration) {
// Parse types and create notifiers. // Parse types and create notifiers.
types, err := cmd.Flags().GetStringSlice("notifications") types, err := cmd.Flags().GetStringSlice("notifications")
@ -69,7 +69,7 @@ func AppendLegacyUrls(urls []string, cmd *cobra.Command) ([]string, time.Duratio
continue continue
} }
shoutrrrURL, err := legacyNotifier.GetURL(cmd) shoutrrrURL, err := legacyNotifier.GetURL(cmd, title)
if err != nil { if err != nil {
log.Fatal("failed to create notification config: ", err) log.Fatal("failed to create notification config: ", err)
} }
@ -85,20 +85,27 @@ func AppendLegacyUrls(urls []string, cmd *cobra.Command) ([]string, time.Duratio
} }
// GetTitle returns a common notification title with hostname appended // GetTitle returns a common notification title with hostname appended
func GetTitle(c *cobra.Command) (title string) { func GetTitle(hostname string) string {
title = "Watchtower updates" title := "Watchtower updates"
if hostname != "" {
title += " on " + hostname
}
return title
}
f := c.PersistentFlags() // GetHostname returns the hostname as set by args or resolved from OS
func GetHostname(c *cobra.Command) string {
f := c.PersistentFlags()
hostname, _ := f.GetString("notifications-hostname") hostname, _ := f.GetString("notifications-hostname")
if hostname != "" { if hostname != "" {
title += " on " + hostname return hostname
} else if hostname, err := os.Hostname(); err == nil { } else if hostname, err := os.Hostname(); err == nil {
title += " on " + hostname return hostname
} }
return return ""
} }
// ColorHex is the default notification color used for services that support it (formatted as a CSS hex string) // ColorHex is the default notification color used for services that support it (formatted as a CSS hex string)

@ -28,6 +28,27 @@ var _ = Describe("notifications", func() {
Expect(notif.GetNames()).To(BeEmpty()) Expect(notif.GetNames()).To(BeEmpty())
}) })
When("title is overriden in flag", func() {
It("should use the specified hostname in the title", func() {
command := cmd.NewRootCommand()
flags.RegisterNotificationFlags(command)
err := command.ParseFlags([]string{
"--notifications-hostname",
"test.host",
})
Expect(err).NotTo(HaveOccurred())
hostname := notifications.GetHostname(command)
title := notifications.GetTitle(hostname)
Expect(title).To(Equal("Watchtower updates on test.host"))
})
})
When("no hostname can be resolved", func() {
It("should use the default simple title", func() {
title := notifications.GetTitle("")
Expect(title).To(Equal("Watchtower updates"))
})
})
}) })
Describe("the slack notifier", func() { Describe("the slack notifier", func() {
// builderFn := notifications.NewSlackNotifier // builderFn := notifications.NewSlackNotifier
@ -39,7 +60,8 @@ var _ = Describe("notifications", func() {
channel := "123456789" channel := "123456789"
token := "abvsihdbau" token := "abvsihdbau"
color := notifications.ColorInt color := notifications.ColorInt
title := url.QueryEscape(notifications.GetTitle(command)) hostname := notifications.GetHostname(command)
title := url.QueryEscape(notifications.GetTitle(hostname))
expected := fmt.Sprintf("discord://%s@%s?color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&title=%s&username=watchtower", token, channel, color, title) expected := fmt.Sprintf("discord://%s@%s?color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&title=%s&username=watchtower", token, channel, color, title)
buildArgs := func(url string) []string { buildArgs := func(url string) []string {
return []string{ return []string{
@ -67,7 +89,8 @@ var _ = Describe("notifications", func() {
tokenB := "BBBBBBBBB" tokenB := "BBBBBBBBB"
tokenC := "123456789123456789123456" tokenC := "123456789123456789123456"
color := url.QueryEscape(notifications.ColorHex) color := url.QueryEscape(notifications.ColorHex)
title := url.QueryEscape(notifications.GetTitle(command)) hostname := notifications.GetHostname(command)
title := url.QueryEscape(notifications.GetTitle(hostname))
iconURL := "https://containrrr.dev/watchtower-sq180.png" iconURL := "https://containrrr.dev/watchtower-sq180.png"
iconEmoji := "whale" iconEmoji := "whale"
@ -122,7 +145,8 @@ var _ = Describe("notifications", func() {
token := "aaa" token := "aaa"
host := "shoutrrr.local" host := "shoutrrr.local"
title := url.QueryEscape(notifications.GetTitle(command)) hostname := notifications.GetHostname(command)
title := url.QueryEscape(notifications.GetTitle(hostname))
expectedOutput := fmt.Sprintf("gotify://%s/%s?title=%s", host, token, title) expectedOutput := fmt.Sprintf("gotify://%s/%s?title=%s", host, token, title)
@ -150,7 +174,8 @@ var _ = Describe("notifications", func() {
tokenB := "33333333012222222222333333333344" tokenB := "33333333012222222222333333333344"
tokenC := "44444444-4444-4444-8444-cccccccccccc" tokenC := "44444444-4444-4444-8444-cccccccccccc"
color := url.QueryEscape(notifications.ColorHex) color := url.QueryEscape(notifications.ColorHex)
title := url.QueryEscape(notifications.GetTitle(command)) hostname := notifications.GetHostname(command)
title := url.QueryEscape(notifications.GetTitle(hostname))
hookURL := fmt.Sprintf("https://outlook.office.com/webhook/%s/IncomingWebhook/%s/%s", tokenA, tokenB, tokenC) hookURL := fmt.Sprintf("https://outlook.office.com/webhook/%s/IncomingWebhook/%s/%s", tokenA, tokenB, tokenC)
expectedOutput := fmt.Sprintf("teams://%s/%s/%s?color=%s&title=%s", tokenA, tokenB, tokenC, color, title) expectedOutput := fmt.Sprintf("teams://%s/%s/%s?color=%s&title=%s", tokenA, tokenB, tokenC, color, title)
@ -241,7 +266,9 @@ func testURL(args []string, expectedURL string) {
err := command.ParseFlags(args) err := command.ParseFlags(args)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
urls, _ := notifications.AppendLegacyUrls([]string{}, command) hostname := notifications.GetHostname(command)
title := notifications.GetTitle(hostname)
urls, _ := notifications.AppendLegacyUrls([]string{}, command, title)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())

@ -58,6 +58,7 @@ type shoutrrrTypeNotifier struct {
done chan bool done chan bool
legacyTemplate bool legacyTemplate bool
params *types.Params params *types.Params
hostname string
} }
// GetScheme returns the scheme part of a Shoutrrr URL // GetScheme returns the scheme part of a Shoutrrr URL
@ -78,10 +79,11 @@ func (n *shoutrrrTypeNotifier) GetNames() []string {
return names return names
} }
func newShoutrrrNotifier(tplString string, acceptedLogLevels []log.Level, legacy bool, title string, delay time.Duration, urls ...string) t.Notifier { func newShoutrrrNotifier(tplString string, acceptedLogLevels []log.Level, legacy bool, hostname string, delay time.Duration, urls ...string) t.Notifier {
notifier := createNotifier(urls, acceptedLogLevels, tplString, legacy) notifier := createNotifier(urls, acceptedLogLevels, tplString, legacy)
notifier.params = &types.Params{"title": title} notifier.hostname = hostname
notifier.params = &types.Params{"title": GetTitle(hostname)}
log.AddHook(notifier) log.AddHook(notifier)
// Do the sending in a separate goroutine so we don't block the main process. // Do the sending in a separate goroutine so we don't block the main process.
@ -147,7 +149,9 @@ func (n *shoutrrrTypeNotifier) buildMessage(data Data) (string, error) {
} }
func (n *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry, report t.Report) { func (n *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry, report t.Report) {
msg, err := n.buildMessage(Data{entries, report}) title, _ := n.params.Title()
host := n.hostname
msg, err := n.buildMessage(Data{entries, report, title, host})
if msg == "" { if msg == "" {
// Log in go func in case we entered from Fire to avoid stalling // Log in go func in case we entered from Fire to avoid stalling
@ -240,4 +244,6 @@ func getShoutrrrTemplate(tplString string, legacy bool) (tpl *template.Template,
type Data struct { type Data struct {
Entries []*log.Entry Entries []*log.Entry
Report t.Report Report t.Report
Title string
Host string
} }

@ -48,9 +48,12 @@ var mockDataAllFresh = Data{
} }
func mockDataFromStates(states ...s.State) Data { func mockDataFromStates(states ...s.State) Data {
hostname := "Mock"
return Data{ return Data{
Entries: legacyMockData.Entries, Entries: legacyMockData.Entries,
Report: mocks.CreateMockProgressReport(states...), Report: mocks.CreateMockProgressReport(states...),
Title: GetTitle(hostname),
Host: hostname,
} }
} }
@ -177,6 +180,22 @@ var _ = Describe("Shoutrrr", func() {
}) })
When("using a template referencing Title", func() {
It("should contain the title in the output", func() {
expected := `Watchtower updates on Mock`
data := mockDataFromStates(s.UpdatedState)
Expect(getTemplatedResult(`{{ .Title }}`, false, data)).To(Equal(expected))
})
})
When("using a template referencing Host", func() {
It("should contain the hostname in the output", func() {
expected := `Mock`
data := mockDataFromStates(s.UpdatedState)
Expect(getTemplatedResult(`{{ .Host }}`, false, data)).To(Equal(expected))
})
})
Describe("the default template", func() { Describe("the default template", func() {
When("all containers are fresh", func() { When("all containers are fresh", func() {
It("should return an empty string", func() { It("should return an empty string", func() {
@ -278,6 +297,7 @@ func sendNotificationsWithBlockingRouter(legacy bool) (*shoutrrrTypeNotifier, *b
done: make(chan bool), done: make(chan bool),
Router: router, Router: router,
legacyTemplate: legacy, legacyTemplate: legacy,
params: &types.Params{},
} }
entry := &logrus.Entry{ entry := &logrus.Entry{

@ -41,7 +41,7 @@ func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
return n return n
} }
func (s *slackTypeNotifier) GetURL(c *cobra.Command) (string, error) { func (s *slackTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
trimmedURL := strings.TrimRight(s.HookURL, "/") trimmedURL := strings.TrimRight(s.HookURL, "/")
trimmedURL = strings.TrimLeft(trimmedURL, "https://") trimmedURL = strings.TrimLeft(trimmedURL, "https://")
parts := strings.Split(trimmedURL, "/") parts := strings.Split(trimmedURL, "/")
@ -52,7 +52,7 @@ func (s *slackTypeNotifier) GetURL(c *cobra.Command) (string, error) {
WebhookID: parts[len(parts)-3], WebhookID: parts[len(parts)-3],
Token: parts[len(parts)-2], Token: parts[len(parts)-2],
Color: ColorInt, Color: ColorInt,
Title: GetTitle(c), Title: title,
SplitLines: true, SplitLines: true,
Username: s.Username, Username: s.Username,
} }
@ -65,7 +65,7 @@ func (s *slackTypeNotifier) GetURL(c *cobra.Command) (string, error) {
BotName: s.Username, BotName: s.Username,
Color: ColorHex, Color: ColorHex,
Channel: "webhook", Channel: "webhook",
Title: GetTitle(c), Title: title,
} }
if s.IconURL != "" { if s.IconURL != "" {

@ -7,7 +7,7 @@ import (
// ConvertibleNotifier is a notifier capable of creating a shoutrrr URL // ConvertibleNotifier is a notifier capable of creating a shoutrrr URL
type ConvertibleNotifier interface { type ConvertibleNotifier interface {
GetURL(c *cobra.Command) (string, error) GetURL(c *cobra.Command, title string) (string, error)
} }
// DelayNotifier is a notifier that might need to be delayed before sending notifications // DelayNotifier is a notifier that might need to be delayed before sending notifications

Loading…
Cancel
Save