mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-21 21:30:48 +02:00
preparations for soft deprecation of legacy notification args (#1377)
Co-authored-by: Simon Aronsson <simme@arcticbit.se>
This commit is contained in:
parent
2102a056de
commit
cb555f539d
18 changed files with 505 additions and 167 deletions
29
pkg/container/cgroup_id.go
Normal file
29
pkg/container/cgroup_id.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
)
|
||||
|
||||
var dockerContainerPattern = regexp.MustCompile(`[0-9]+:.*:/docker/([a-f|0-9]{64})`)
|
||||
|
||||
// GetRunningContainerID tries to resolve the current container ID from the current process cgroup information
|
||||
func GetRunningContainerID() (cid types.ContainerID, err error) {
|
||||
file, err := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", os.Getpid()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return getRunningContainerIDFromString(string(file)), nil
|
||||
}
|
||||
|
||||
func getRunningContainerIDFromString(s string) types.ContainerID {
|
||||
matches := dockerContainerPattern.FindStringSubmatch(s)
|
||||
if len(matches) < 2 {
|
||||
return ""
|
||||
}
|
||||
return types.ContainerID(matches[1])
|
||||
}
|
40
pkg/container/cgroup_id_test.go
Normal file
40
pkg/container/cgroup_id_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("GetRunningContainerID", func() {
|
||||
When("a matching container ID is found", func() {
|
||||
It("should return that container ID", func() {
|
||||
cid := getRunningContainerIDFromString(`
|
||||
15:name=systemd:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
14:misc:/
|
||||
13:rdma:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
12:pids:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
11:hugetlb:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
10:net_prio:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
9:perf_event:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
8:net_cls:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
7:freezer:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
6:devices:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
5:blkio:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
4:cpuacct:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
3:cpu:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
2:cpuset:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
1:memory:/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
0::/docker/991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377
|
||||
`)
|
||||
Expect(cid).To(BeEquivalentTo(`991b6b42691449d3ce90192ff9f006863dcdafc6195e227aeefa298235004377`))
|
||||
})
|
||||
})
|
||||
When("no matching container ID could be found", func() {
|
||||
It("should return that container ID", func() {
|
||||
cid := getRunningContainerIDFromString(`14:misc:/`)
|
||||
Expect(cid).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
//
|
|
@ -1,3 +1,4 @@
|
|||
// Package container contains code related to dealing with docker containers
|
||||
package container
|
||||
|
||||
import (
|
||||
|
|
|
@ -15,17 +15,16 @@ const (
|
|||
)
|
||||
|
||||
type emailTypeNotifier struct {
|
||||
From, To string
|
||||
Server, User, Password, SubjectTag string
|
||||
Port int
|
||||
tlsSkipVerify bool
|
||||
entries []*log.Entry
|
||||
logLevels []log.Level
|
||||
delay time.Duration
|
||||
From, To string
|
||||
Server, User, Password string
|
||||
Port int
|
||||
tlsSkipVerify bool
|
||||
entries []*log.Entry
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.ConvertibleNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
func newEmailNotifier(c *cobra.Command) t.ConvertibleNotifier {
|
||||
flags := c.Flags()
|
||||
|
||||
from, _ := flags.GetString("notification-email-from")
|
||||
to, _ := flags.GetString("notification-email-to")
|
||||
|
@ -35,7 +34,6 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
|
|||
port, _ := flags.GetInt("notification-email-server-port")
|
||||
tlsSkipVerify, _ := flags.GetBool("notification-email-server-tls-skip-verify")
|
||||
delay, _ := flags.GetInt("notification-email-delay")
|
||||
subjecttag, _ := flags.GetString("notification-email-subjecttag")
|
||||
|
||||
n := &emailTypeNotifier{
|
||||
entries: []*log.Entry{},
|
||||
|
@ -46,22 +44,19 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
|
|||
Password: password,
|
||||
Port: port,
|
||||
tlsSkipVerify: tlsSkipVerify,
|
||||
logLevels: acceptedLogLevels,
|
||||
delay: time.Duration(delay) * time.Second,
|
||||
SubjectTag: subjecttag,
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
|
||||
func (e *emailTypeNotifier) GetURL(c *cobra.Command) (string, error) {
|
||||
conf := &shoutrrrSmtp.Config{
|
||||
FromAddress: e.From,
|
||||
FromName: "Watchtower",
|
||||
ToAddresses: []string{e.To},
|
||||
Port: uint16(e.Port),
|
||||
Host: e.Server,
|
||||
Subject: e.getSubject(c, title),
|
||||
Username: e.User,
|
||||
Password: e.Password,
|
||||
UseStartTLS: !e.tlsSkipVerify,
|
||||
|
@ -84,11 +79,3 @@ func (e *emailTypeNotifier) GetURL(c *cobra.Command, title string) (string, erro
|
|||
func (e *emailTypeNotifier) GetDelay() time.Duration {
|
||||
return e.delay
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) getSubject(_ *cobra.Command, title string) string {
|
||||
if e.SubjectTag != "" {
|
||||
return e.SubjectTag + " " + title
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
|
|
@ -19,11 +19,10 @@ type gotifyTypeNotifier struct {
|
|||
gotifyURL string
|
||||
gotifyAppToken string
|
||||
gotifyInsecureSkipVerify bool
|
||||
logLevels []log.Level
|
||||
}
|
||||
|
||||
func newGotifyNotifier(c *cobra.Command, levels []log.Level) t.ConvertibleNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
func newGotifyNotifier(c *cobra.Command) t.ConvertibleNotifier {
|
||||
flags := c.Flags()
|
||||
|
||||
apiURL := getGotifyURL(flags)
|
||||
token := getGotifyToken(flags)
|
||||
|
@ -34,7 +33,6 @@ func newGotifyNotifier(c *cobra.Command, levels []log.Level) t.ConvertibleNotifi
|
|||
gotifyURL: apiURL,
|
||||
gotifyAppToken: token,
|
||||
gotifyInsecureSkipVerify: skipVerify,
|
||||
logLevels: levels,
|
||||
}
|
||||
|
||||
return n
|
||||
|
@ -62,7 +60,7 @@ func getGotifyURL(flags *pflag.FlagSet) string {
|
|||
return gotifyURL
|
||||
}
|
||||
|
||||
func (n *gotifyTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
|
||||
func (n *gotifyTypeNotifier) GetURL(c *cobra.Command) (string, error) {
|
||||
apiURL, err := url.Parse(n.gotifyURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -72,7 +70,6 @@ func (n *gotifyTypeNotifier) GetURL(c *cobra.Command, title string) (string, err
|
|||
Host: apiURL.Host,
|
||||
Path: apiURL.Path,
|
||||
DisableTLS: apiURL.Scheme == "http",
|
||||
Title: title,
|
||||
Token: n.gotifyAppToken,
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,12 @@ const (
|
|||
|
||||
type msTeamsTypeNotifier struct {
|
||||
webHookURL string
|
||||
levels []log.Level
|
||||
data bool
|
||||
}
|
||||
|
||||
func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.ConvertibleNotifier {
|
||||
func newMsTeamsNotifier(cmd *cobra.Command) t.ConvertibleNotifier {
|
||||
|
||||
flags := cmd.PersistentFlags()
|
||||
flags := cmd.Flags()
|
||||
|
||||
webHookURL, _ := flags.GetString("notification-msteams-hook")
|
||||
if len(webHookURL) <= 0 {
|
||||
|
@ -30,7 +29,6 @@ func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Con
|
|||
|
||||
withData, _ := flags.GetBool("notification-msteams-data")
|
||||
n := &msTeamsTypeNotifier{
|
||||
levels: acceptedLogLevels,
|
||||
webHookURL: webHookURL,
|
||||
data: withData,
|
||||
}
|
||||
|
@ -38,7 +36,7 @@ func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Con
|
|||
return n
|
||||
}
|
||||
|
||||
func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
|
||||
func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command) (string, error) {
|
||||
webhookURL, err := url.Parse(n.webHookURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -50,7 +48,6 @@ func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command, title string) (string, er
|
|||
}
|
||||
|
||||
config.Color = ColorHex
|
||||
config.Title = title
|
||||
|
||||
return config.GetURL().String(), nil
|
||||
}
|
||||
|
|
|
@ -6,14 +6,13 @@ import (
|
|||
"time"
|
||||
|
||||
ty "github.com/containrrr/watchtower/pkg/types"
|
||||
"github.com/johntdyer/slackrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewNotifier creates and returns a new Notifier, using global configuration.
|
||||
func NewNotifier(c *cobra.Command) ty.Notifier {
|
||||
f := c.PersistentFlags()
|
||||
f := c.Flags()
|
||||
|
||||
level, _ := f.GetString("notifications-level")
|
||||
logLevel, err := log.ParseLevel(level)
|
||||
|
@ -21,25 +20,19 @@ func NewNotifier(c *cobra.Command) ty.Notifier {
|
|||
log.Fatalf("Notifications invalid log level: %s", err.Error())
|
||||
}
|
||||
|
||||
levels := slackrus.LevelThreshold(logLevel)
|
||||
// slackrus does not allow log level TRACE, even though it's an accepted log level for logrus
|
||||
if len(levels) == 0 {
|
||||
log.Fatalf("Unsupported notification log level provided: %s", level)
|
||||
}
|
||||
|
||||
reportTemplate, _ := f.GetBool("notification-report")
|
||||
stdout, _ := f.GetBool("notification-log-stdout")
|
||||
tplString, _ := f.GetString("notification-template")
|
||||
urls, _ := f.GetStringArray("notification-url")
|
||||
|
||||
data := GetTemplateData(c)
|
||||
urls, delay := AppendLegacyUrls(urls, c, data.Title)
|
||||
urls, delay := AppendLegacyUrls(urls, c)
|
||||
|
||||
return newShoutrrrNotifier(tplString, levels, !reportTemplate, data, delay, stdout, urls...)
|
||||
return createNotifier(urls, logLevel, tplString, !reportTemplate, data, stdout, delay)
|
||||
}
|
||||
|
||||
// AppendLegacyUrls creates shoutrrr equivalent URLs from legacy notification flags
|
||||
func AppendLegacyUrls(urls []string, cmd *cobra.Command, title string) ([]string, time.Duration) {
|
||||
func AppendLegacyUrls(urls []string, cmd *cobra.Command) ([]string, time.Duration) {
|
||||
|
||||
// Parse types and create notifiers.
|
||||
types, err := cmd.Flags().GetStringSlice("notifications")
|
||||
|
@ -56,13 +49,13 @@ func AppendLegacyUrls(urls []string, cmd *cobra.Command, title string) ([]string
|
|||
|
||||
switch t {
|
||||
case emailType:
|
||||
legacyNotifier = newEmailNotifier(cmd, []log.Level{})
|
||||
legacyNotifier = newEmailNotifier(cmd)
|
||||
case slackType:
|
||||
legacyNotifier = newSlackNotifier(cmd, []log.Level{})
|
||||
legacyNotifier = newSlackNotifier(cmd)
|
||||
case msTeamsType:
|
||||
legacyNotifier = newMsTeamsNotifier(cmd, []log.Level{})
|
||||
legacyNotifier = newMsTeamsNotifier(cmd)
|
||||
case gotifyType:
|
||||
legacyNotifier = newGotifyNotifier(cmd, []log.Level{})
|
||||
legacyNotifier = newGotifyNotifier(cmd)
|
||||
case shoutrrrType:
|
||||
continue
|
||||
default:
|
||||
|
@ -71,7 +64,7 @@ func AppendLegacyUrls(urls []string, cmd *cobra.Command, title string) ([]string
|
|||
continue
|
||||
}
|
||||
|
||||
shoutrrrURL, err := legacyNotifier.GetURL(cmd, title)
|
||||
shoutrrrURL, err := legacyNotifier.GetURL(cmd)
|
||||
if err != nil {
|
||||
log.Fatal("failed to create notification config: ", err)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package notifications_test
|
|||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containrrr/watchtower/cmd"
|
||||
|
@ -147,11 +146,9 @@ var _ = Describe("notifications", func() {
|
|||
channel := "123456789"
|
||||
token := "abvsihdbau"
|
||||
color := notifications.ColorInt
|
||||
data := notifications.GetTemplateData(command)
|
||||
title := url.QueryEscape(data.Title)
|
||||
username := "containrrrbot"
|
||||
iconURL := "https://containrrr.dev/watchtower-sq180.png"
|
||||
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&username=watchtower", token, channel, color)
|
||||
buildArgs := func(url string) []string {
|
||||
return []string{
|
||||
"--notifications",
|
||||
|
@ -172,7 +169,7 @@ var _ = Describe("notifications", func() {
|
|||
When("icon URL and username are specified", func() {
|
||||
It("should return the expected URL", func() {
|
||||
hookURL := fmt.Sprintf("https://%s/api/webhooks/%s/%s/slack", "discord.com", channel, token)
|
||||
expectedOutput := fmt.Sprintf("discord://%s@%s?avatar=%s&color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&title=%s&username=%s", token, channel, url.QueryEscape(iconURL), color, title, username)
|
||||
expectedOutput := fmt.Sprintf("discord://%s@%s?avatar=%s&color=0x%x&colordebug=0x0&colorerror=0x0&colorinfo=0x0&colorwarn=0x0&username=%s", token, channel, url.QueryEscape(iconURL), color, username)
|
||||
expectedDelay := time.Duration(7) * time.Second
|
||||
args := []string{
|
||||
"--notifications",
|
||||
|
@ -199,8 +196,6 @@ var _ = Describe("notifications", func() {
|
|||
tokenB := "BBBBBBBBB"
|
||||
tokenC := "123456789123456789123456"
|
||||
color := url.QueryEscape(notifications.ColorHex)
|
||||
data := notifications.GetTemplateData(command)
|
||||
title := url.QueryEscape(data.Title)
|
||||
iconURL := "https://containrrr.dev/watchtower-sq180.png"
|
||||
iconEmoji := "whale"
|
||||
|
||||
|
@ -208,7 +203,7 @@ var _ = Describe("notifications", func() {
|
|||
It("should return the expected URL", func() {
|
||||
|
||||
hookURL := fmt.Sprintf("https://hooks.slack.com/services/%s/%s/%s", tokenA, tokenB, tokenC)
|
||||
expectedOutput := fmt.Sprintf("slack://hook:%s-%s-%s@webhook?botname=%s&color=%s&icon=%s&title=%s", tokenA, tokenB, tokenC, username, color, url.QueryEscape(iconURL), title)
|
||||
expectedOutput := fmt.Sprintf("slack://hook:%s-%s-%s@webhook?botname=%s&color=%s&icon=%s", tokenA, tokenB, tokenC, username, color, url.QueryEscape(iconURL))
|
||||
expectedDelay := time.Duration(7) * time.Second
|
||||
|
||||
args := []string{
|
||||
|
@ -231,7 +226,7 @@ var _ = Describe("notifications", func() {
|
|||
When("icon emoji is specified", func() {
|
||||
It("should return the expected URL", func() {
|
||||
hookURL := fmt.Sprintf("https://hooks.slack.com/services/%s/%s/%s", tokenA, tokenB, tokenC)
|
||||
expectedOutput := fmt.Sprintf("slack://hook:%s-%s-%s@webhook?botname=%s&color=%s&icon=%s&title=%s", tokenA, tokenB, tokenC, username, color, iconEmoji, title)
|
||||
expectedOutput := fmt.Sprintf("slack://hook:%s-%s-%s@webhook?botname=%s&color=%s&icon=%s", tokenA, tokenB, tokenC, username, color, iconEmoji)
|
||||
|
||||
args := []string{
|
||||
"--notifications",
|
||||
|
@ -258,10 +253,8 @@ var _ = Describe("notifications", func() {
|
|||
|
||||
token := "aaa"
|
||||
host := "shoutrrr.local"
|
||||
data := notifications.GetTemplateData(command)
|
||||
title := url.QueryEscape(data.Title)
|
||||
|
||||
expectedOutput := fmt.Sprintf("gotify://%s/%s?title=%s", host, token, title)
|
||||
expectedOutput := fmt.Sprintf("gotify://%s/%s?title=", host, token)
|
||||
|
||||
args := []string{
|
||||
"--notifications",
|
||||
|
@ -287,11 +280,9 @@ var _ = Describe("notifications", func() {
|
|||
tokenB := "33333333012222222222333333333344"
|
||||
tokenC := "44444444-4444-4444-8444-cccccccccccc"
|
||||
color := url.QueryEscape(notifications.ColorHex)
|
||||
data := notifications.GetTemplateData(command)
|
||||
title := url.QueryEscape(data.Title)
|
||||
|
||||
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", tokenA, tokenB, tokenC, color)
|
||||
|
||||
args := []string{
|
||||
"--notifications",
|
||||
|
@ -362,18 +353,12 @@ var _ = Describe("notifications", func() {
|
|||
})
|
||||
|
||||
func buildExpectedURL(username string, password string, host string, port int, from string, to string, auth string) string {
|
||||
hostname, err := os.Hostname()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
subject := fmt.Sprintf("Watchtower updates on %s", hostname)
|
||||
|
||||
var template = "smtp://%s:%s@%s:%d/?auth=%s&fromaddress=%s&fromname=Watchtower&subject=%s&toaddresses=%s"
|
||||
var template = "smtp://%s:%s@%s:%d/?auth=%s&fromaddress=%s&fromname=Watchtower&subject=&toaddresses=%s"
|
||||
return fmt.Sprintf(template,
|
||||
url.QueryEscape(username),
|
||||
url.QueryEscape(password),
|
||||
host, port, auth,
|
||||
url.QueryEscape(from),
|
||||
url.QueryEscape(subject),
|
||||
url.QueryEscape(to))
|
||||
}
|
||||
|
||||
|
@ -385,8 +370,7 @@ func testURL(args []string, expectedURL string, expectedDelay time.Duration) {
|
|||
|
||||
Expect(command.ParseFlags(args)).To(Succeed())
|
||||
|
||||
data := notifications.GetTemplateData(command)
|
||||
urls, delay := notifications.AppendLegacyUrls([]string{}, command, data.Title)
|
||||
urls, delay := notifications.AppendLegacyUrls([]string{}, command)
|
||||
|
||||
Expect(urls).To(ContainElement(expectedURL))
|
||||
Expect(delay).To(Equal(expectedDelay))
|
||||
|
|
|
@ -32,13 +32,15 @@ type shoutrrrTypeNotifier struct {
|
|||
Urls []string
|
||||
Router router
|
||||
entries []*log.Entry
|
||||
logLevels []log.Level
|
||||
logLevel log.Level
|
||||
template *template.Template
|
||||
messages chan string
|
||||
done chan bool
|
||||
legacyTemplate bool
|
||||
params *types.Params
|
||||
data StaticData
|
||||
receiving bool
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
// GetScheme returns the scheme part of a Shoutrrr URL
|
||||
|
@ -59,18 +61,24 @@ func (n *shoutrrrTypeNotifier) GetNames() []string {
|
|||
return names
|
||||
}
|
||||
|
||||
func newShoutrrrNotifier(tplString string, levels []log.Level, legacy bool, data StaticData, delay time.Duration, stdout bool, urls ...string) t.Notifier {
|
||||
|
||||
notifier := createNotifier(urls, levels, tplString, legacy, data, stdout)
|
||||
log.AddHook(notifier)
|
||||
|
||||
// Do the sending in a separate goroutine so we don't block the main process.
|
||||
go sendNotifications(notifier, delay)
|
||||
|
||||
return notifier
|
||||
// GetNames returns a list of URLs for notification services that has been added
|
||||
func (n *shoutrrrTypeNotifier) GetURLs() []string {
|
||||
return n.Urls
|
||||
}
|
||||
|
||||
func createNotifier(urls []string, levels []log.Level, tplString string, legacy bool, data StaticData, stdout bool) *shoutrrrTypeNotifier {
|
||||
// AddLogHook adds the notifier as a receiver of log messages and starts a go func for processing them
|
||||
func (n *shoutrrrTypeNotifier) AddLogHook() {
|
||||
if n.receiving {
|
||||
return
|
||||
}
|
||||
n.receiving = true
|
||||
log.AddHook(n)
|
||||
|
||||
// Do the sending in a separate goroutine so we don't block the main process.
|
||||
go sendNotifications(n)
|
||||
}
|
||||
|
||||
func createNotifier(urls []string, level log.Level, tplString string, legacy bool, data StaticData, stdout bool, delay time.Duration) *shoutrrrTypeNotifier {
|
||||
tpl, err := getShoutrrrTemplate(tplString, legacy)
|
||||
if err != nil {
|
||||
log.Errorf("Could not use configured notification template: %s. Using default template", err)
|
||||
|
@ -97,7 +105,7 @@ func createNotifier(urls []string, levels []log.Level, tplString string, legacy
|
|||
Router: r,
|
||||
messages: make(chan string, 1),
|
||||
done: make(chan bool),
|
||||
logLevels: levels,
|
||||
logLevel: level,
|
||||
template: tpl,
|
||||
legacyTemplate: legacy,
|
||||
data: data,
|
||||
|
@ -105,9 +113,9 @@ func createNotifier(urls []string, levels []log.Level, tplString string, legacy
|
|||
}
|
||||
}
|
||||
|
||||
func sendNotifications(n *shoutrrrTypeNotifier, delay time.Duration) {
|
||||
func sendNotifications(n *shoutrrrTypeNotifier) {
|
||||
for msg := range n.messages {
|
||||
time.Sleep(delay)
|
||||
time.Sleep(n.delay)
|
||||
errs := n.Router.Send(msg, n.params)
|
||||
|
||||
for i, err := range errs {
|
||||
|
@ -180,7 +188,7 @@ func (n *shoutrrrTypeNotifier) Close() {
|
|||
|
||||
// Levels return what log levels trigger notifications
|
||||
func (n *shoutrrrTypeNotifier) Levels() []log.Level {
|
||||
return n.logLevels
|
||||
return log.AllLevels[:n.logLevel+1]
|
||||
}
|
||||
|
||||
// Fire is the hook that logrus calls on a new log message
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var allButTrace = logrus.AllLevels[0:logrus.TraceLevel]
|
||||
var allButTrace = logrus.DebugLevel
|
||||
|
||||
var legacyMockData = Data{
|
||||
Entries: []*logrus.Entry{
|
||||
|
@ -83,6 +83,30 @@ updt1 (mock/updt1:latest): Updated
|
|||
})
|
||||
})
|
||||
|
||||
When("adding a log hook", func() {
|
||||
When("it has not been added before", func() {
|
||||
It("should be added to the logrus hooks", func() {
|
||||
level := logrus.TraceLevel
|
||||
hooksBefore := len(logrus.StandardLogger().Hooks[level])
|
||||
shoutrrr := createNotifier([]string{}, level, "", true, StaticData{}, false, time.Second)
|
||||
shoutrrr.AddLogHook()
|
||||
hooksAfter := len(logrus.StandardLogger().Hooks[level])
|
||||
Expect(hooksAfter).To(BeNumerically(">", hooksBefore))
|
||||
})
|
||||
})
|
||||
When("it is being added a second time", func() {
|
||||
It("should not be added to the logrus hooks", func() {
|
||||
level := logrus.TraceLevel
|
||||
shoutrrr := createNotifier([]string{}, level, "", true, StaticData{}, false, time.Second)
|
||||
shoutrrr.AddLogHook()
|
||||
hooksBefore := len(logrus.StandardLogger().Hooks[level])
|
||||
shoutrrr.AddLogHook()
|
||||
hooksAfter := len(logrus.StandardLogger().Hooks[level])
|
||||
Expect(hooksAfter).To(Equal(hooksBefore))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("using legacy templates", func() {
|
||||
|
||||
When("no custom template is provided", func() {
|
||||
|
@ -90,7 +114,7 @@ updt1 (mock/updt1:latest): Updated
|
|||
cmd := new(cobra.Command)
|
||||
flags.RegisterNotificationFlags(cmd)
|
||||
|
||||
shoutrrr := createNotifier([]string{}, logrus.AllLevels, "", true, StaticData{}, false)
|
||||
shoutrrr := createNotifier([]string{}, logrus.TraceLevel, "", true, StaticData{}, false, time.Second)
|
||||
|
||||
entries := []*logrus.Entry{
|
||||
{
|
||||
|
@ -245,7 +269,7 @@ Turns out everything is on fire
|
|||
When("batching notifications", func() {
|
||||
When("no messages are queued", func() {
|
||||
It("should not send any notification", func() {
|
||||
shoutrrr := newShoutrrrNotifier("", allButTrace, true, StaticData{}, time.Duration(0), false, "logger://")
|
||||
shoutrrr := createNotifier([]string{"logger://"}, allButTrace, "", true, StaticData{}, false, time.Duration(0))
|
||||
shoutrrr.StartNotification()
|
||||
shoutrrr.SendNotification(nil)
|
||||
Consistently(logBuffer).ShouldNot(gbytes.Say(`Shoutrrr:`))
|
||||
|
@ -253,7 +277,8 @@ Turns out everything is on fire
|
|||
})
|
||||
When("at least one message is queued", func() {
|
||||
It("should send a notification", func() {
|
||||
shoutrrr := newShoutrrrNotifier("", allButTrace, true, StaticData{}, time.Duration(0), false, "logger://")
|
||||
shoutrrr := createNotifier([]string{"logger://"}, allButTrace, "", true, StaticData{}, false, time.Duration(0))
|
||||
shoutrrr.AddLogHook()
|
||||
shoutrrr.StartNotification()
|
||||
logrus.Info("This log message is sponsored by ContainrrrVPN")
|
||||
shoutrrr.SendNotification(nil)
|
||||
|
@ -267,7 +292,7 @@ Turns out everything is on fire
|
|||
shoutrrr := createNotifier([]string{"logger://"}, allButTrace, "", true, StaticData{
|
||||
Host: "test.host",
|
||||
Title: "",
|
||||
}, false)
|
||||
}, false, time.Second)
|
||||
_, found := shoutrrr.params.Title()
|
||||
Expect(found).ToNot(BeTrue())
|
||||
})
|
||||
|
@ -321,13 +346,14 @@ func sendNotificationsWithBlockingRouter(legacy bool) (*shoutrrrTypeNotifier, *b
|
|||
Router: router,
|
||||
legacyTemplate: legacy,
|
||||
params: &types.Params{},
|
||||
delay: time.Duration(0),
|
||||
}
|
||||
|
||||
entry := &logrus.Entry{
|
||||
Message: "foo bar",
|
||||
}
|
||||
|
||||
go sendNotifications(shoutrrr, time.Duration(0))
|
||||
go sendNotifications(shoutrrr)
|
||||
|
||||
shoutrrr.StartNotification()
|
||||
_ = shoutrrr.Fire(entry)
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
shoutrrrDisco "github.com/containrrr/shoutrrr/pkg/services/discord"
|
||||
shoutrrrSlack "github.com/containrrr/shoutrrr/pkg/services/slack"
|
||||
t "github.com/containrrr/watchtower/pkg/types"
|
||||
"github.com/johntdyer/slackrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -16,11 +15,15 @@ const (
|
|||
)
|
||||
|
||||
type slackTypeNotifier struct {
|
||||
slackrus.SlackrusHook
|
||||
HookURL string
|
||||
Username string
|
||||
Channel string
|
||||
IconEmoji string
|
||||
IconURL string
|
||||
}
|
||||
|
||||
func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.ConvertibleNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
func newSlackNotifier(c *cobra.Command) t.ConvertibleNotifier {
|
||||
flags := c.Flags()
|
||||
|
||||
hookURL, _ := flags.GetString("notification-slack-hook-url")
|
||||
userName, _ := flags.GetString("notification-slack-identifier")
|
||||
|
@ -29,19 +32,16 @@ func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Convert
|
|||
iconURL, _ := flags.GetString("notification-slack-icon-url")
|
||||
|
||||
n := &slackTypeNotifier{
|
||||
SlackrusHook: slackrus.SlackrusHook{
|
||||
HookURL: hookURL,
|
||||
Username: userName,
|
||||
Channel: channel,
|
||||
IconEmoji: emoji,
|
||||
IconURL: iconURL,
|
||||
AcceptedLevels: acceptedLogLevels,
|
||||
},
|
||||
HookURL: hookURL,
|
||||
Username: userName,
|
||||
Channel: channel,
|
||||
IconEmoji: emoji,
|
||||
IconURL: iconURL,
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *slackTypeNotifier) GetURL(c *cobra.Command, title string) (string, error) {
|
||||
func (s *slackTypeNotifier) GetURL(c *cobra.Command) (string, error) {
|
||||
trimmedURL := strings.TrimRight(s.HookURL, "/")
|
||||
trimmedURL = strings.TrimPrefix(trimmedURL, "https://")
|
||||
parts := strings.Split(trimmedURL, "/")
|
||||
|
@ -52,7 +52,6 @@ func (s *slackTypeNotifier) GetURL(c *cobra.Command, title string) (string, erro
|
|||
WebhookID: parts[len(parts)-3],
|
||||
Token: parts[len(parts)-2],
|
||||
Color: ColorInt,
|
||||
Title: title,
|
||||
SplitLines: true,
|
||||
Username: s.Username,
|
||||
}
|
||||
|
@ -70,7 +69,6 @@ func (s *slackTypeNotifier) GetURL(c *cobra.Command, title string) (string, erro
|
|||
BotName: s.Username,
|
||||
Color: ColorHex,
|
||||
Channel: "webhook",
|
||||
Title: title,
|
||||
}
|
||||
|
||||
if s.IconURL != "" {
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ConvertibleNotifier is a notifier capable of creating a shoutrrr URL
|
||||
type ConvertibleNotifier interface {
|
||||
GetURL(c *cobra.Command, title string) (string, error)
|
||||
GetURL(c *cobra.Command) (string, error)
|
||||
}
|
||||
|
||||
// DelayNotifier is a notifier that might need to be delayed before sending notifications
|
||||
type DelayNotifier interface {
|
||||
GetDelay() time.Duration
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ package types
|
|||
type Notifier interface {
|
||||
StartNotification()
|
||||
SendNotification(Report)
|
||||
AddLogHook()
|
||||
GetNames() []string
|
||||
GetURLs() []string
|
||||
Close()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue