mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-21 21:30:48 +02:00
feat(notifications): add title field to template data (#1125)
This commit is contained in:
parent
1d59fb83dd
commit
aa02d8d31b
9 changed files with 94 additions and 35 deletions
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHostname returns the hostname as set by args or resolved from OS
|
||||||
|
func GetHostname(c *cobra.Command) string {
|
||||||
|
|
||||||
f := c.PersistentFlags()
|
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…
Add table
Add a link
Reference in a new issue