mirror of
https://github.com/containrrr/watchtower.git
synced 2026-02-09 17:04:20 +01:00
removes all code related to log levels and title, since that is not used anyway this also gets rid of slackrus dependency
357 lines
10 KiB
Go
357 lines
10 KiB
Go
package notifications
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/containrrr/shoutrrr/pkg/types"
|
|
"github.com/containrrr/watchtower/internal/actions/mocks"
|
|
"github.com/containrrr/watchtower/internal/flags"
|
|
s "github.com/containrrr/watchtower/pkg/session"
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var allButTrace = logrus.DebugLevel
|
|
|
|
var legacyMockData = Data{
|
|
Entries: []*logrus.Entry{
|
|
{
|
|
Level: logrus.InfoLevel,
|
|
Message: "foo Bar",
|
|
},
|
|
},
|
|
}
|
|
|
|
var mockDataMultipleEntries = Data{
|
|
Entries: []*logrus.Entry{
|
|
{
|
|
Level: logrus.InfoLevel,
|
|
Message: "The situation is under control",
|
|
},
|
|
{
|
|
Level: logrus.WarnLevel,
|
|
Message: "All the smoke might be covering up some problems",
|
|
},
|
|
{
|
|
Level: logrus.ErrorLevel,
|
|
Message: "Turns out everything is on fire",
|
|
},
|
|
},
|
|
}
|
|
|
|
var mockDataAllFresh = Data{
|
|
Entries: []*logrus.Entry{},
|
|
Report: mocks.CreateMockProgressReport(s.FreshState),
|
|
}
|
|
|
|
func mockDataFromStates(states ...s.State) Data {
|
|
hostname := "Mock"
|
|
prefix := ""
|
|
return Data{
|
|
Entries: legacyMockData.Entries,
|
|
Report: mocks.CreateMockProgressReport(states...),
|
|
StaticData: StaticData{
|
|
Title: GetTitle(hostname, prefix),
|
|
Host: hostname,
|
|
},
|
|
}
|
|
}
|
|
|
|
var _ = Describe("Shoutrrr", func() {
|
|
var logBuffer *gbytes.Buffer
|
|
|
|
BeforeEach(func() {
|
|
logBuffer = gbytes.NewBuffer()
|
|
logrus.SetOutput(logBuffer)
|
|
logrus.SetLevel(logrus.TraceLevel)
|
|
logrus.SetFormatter(&logrus.TextFormatter{
|
|
DisableColors: true,
|
|
DisableTimestamp: true,
|
|
})
|
|
})
|
|
|
|
When("passing a common template name", func() {
|
|
It("should format using that template", func() {
|
|
expected := `
|
|
updt1 (mock/updt1:latest): Updated
|
|
`[1:]
|
|
data := mockDataFromStates(s.UpdatedState)
|
|
Expect(getTemplatedResult(`porcelain.v1.summary-no-log`, false, data)).To(Equal(expected))
|
|
})
|
|
})
|
|
|
|
When("using legacy templates", func() {
|
|
|
|
When("no custom template is provided", func() {
|
|
It("should format the messages using the default template", func() {
|
|
cmd := new(cobra.Command)
|
|
flags.RegisterNotificationFlags(cmd)
|
|
|
|
shoutrrr := createNotifier([]string{}, logrus.TraceLevel, "", true, StaticData{}, false, time.Second)
|
|
|
|
entries := []*logrus.Entry{
|
|
{
|
|
Message: "foo bar",
|
|
},
|
|
}
|
|
|
|
s, err := shoutrrr.buildMessage(Data{Entries: entries})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(s).To(Equal("foo bar\n"))
|
|
})
|
|
})
|
|
When("given a valid custom template", func() {
|
|
It("should format the messages using the custom template", func() {
|
|
|
|
tplString := `{{range .}}{{.Level}}: {{.Message}}{{println}}{{end}}`
|
|
tpl, err := getShoutrrrTemplate(tplString, true)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
shoutrrr := &shoutrrrTypeNotifier{
|
|
template: tpl,
|
|
legacyTemplate: true,
|
|
}
|
|
|
|
entries := []*logrus.Entry{
|
|
{
|
|
Level: logrus.InfoLevel,
|
|
Message: "foo bar",
|
|
},
|
|
}
|
|
|
|
s, err := shoutrrr.buildMessage(Data{Entries: entries})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(s).To(Equal("info: foo bar\n"))
|
|
})
|
|
})
|
|
|
|
Describe("the default template", func() {
|
|
When("all containers are fresh", func() {
|
|
It("should return an empty string", func() {
|
|
Expect(getTemplatedResult(``, true, mockDataAllFresh)).To(Equal(""))
|
|
})
|
|
})
|
|
})
|
|
|
|
When("given an invalid custom template", func() {
|
|
It("should format the messages using the default template", func() {
|
|
invNotif, err := createNotifierWithTemplate(`{{ intentionalSyntaxError`, true)
|
|
Expect(err).To(HaveOccurred())
|
|
invMsg, err := invNotif.buildMessage(legacyMockData)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
defNotif, err := createNotifierWithTemplate(``, true)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defMsg, err := defNotif.buildMessage(legacyMockData)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(invMsg).To(Equal(defMsg))
|
|
})
|
|
})
|
|
|
|
When("given a template that is using ToUpper function", func() {
|
|
It("should return the text in UPPER CASE", func() {
|
|
tplString := `{{range .}}{{ .Message | ToUpper }}{{end}}`
|
|
Expect(getTemplatedResult(tplString, true, legacyMockData)).To(Equal("FOO BAR"))
|
|
})
|
|
})
|
|
|
|
When("given a template that is using ToLower function", func() {
|
|
It("should return the text in lower case", func() {
|
|
tplString := `{{range .}}{{ .Message | ToLower }}{{end}}`
|
|
Expect(getTemplatedResult(tplString, true, legacyMockData)).To(Equal("foo bar"))
|
|
})
|
|
})
|
|
|
|
When("given a template that is using Title function", func() {
|
|
It("should return the text in Title Case", func() {
|
|
tplString := `{{range .}}{{ .Message | Title }}{{end}}`
|
|
Expect(getTemplatedResult(tplString, true, legacyMockData)).To(Equal("Foo Bar"))
|
|
})
|
|
})
|
|
|
|
})
|
|
|
|
When("using report templates", func() {
|
|
When("no custom template is provided", func() {
|
|
It("should format the messages using the default template", func() {
|
|
expected := `4 Scanned, 2 Updated, 1 Failed
|
|
- updt1 (mock/updt1:latest): 01d110000000 updated to d0a110000000
|
|
- updt2 (mock/updt2:latest): 01d120000000 updated to d0a120000000
|
|
- frsh1 (mock/frsh1:latest): Fresh
|
|
- skip1 (mock/skip1:latest): Skipped: unpossible
|
|
- fail1 (mock/fail1:latest): Failed: accidentally the whole container`
|
|
data := mockDataFromStates(s.UpdatedState, s.FreshState, s.FailedState, s.SkippedState, s.UpdatedState)
|
|
Expect(getTemplatedResult(``, false, data)).To(Equal(expected))
|
|
})
|
|
|
|
})
|
|
|
|
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() {
|
|
When("all containers are fresh", func() {
|
|
It("should return an empty string", func() {
|
|
Expect(getTemplatedResult(``, false, mockDataAllFresh)).To(Equal(""))
|
|
})
|
|
})
|
|
When("at least one container was updated", func() {
|
|
It("should send a report", func() {
|
|
expected := `1 Scanned, 1 Updated, 0 Failed
|
|
- updt1 (mock/updt1:latest): 01d110000000 updated to d0a110000000`
|
|
data := mockDataFromStates(s.UpdatedState)
|
|
Expect(getTemplatedResult(``, false, data)).To(Equal(expected))
|
|
})
|
|
})
|
|
When("at least one container failed to update", func() {
|
|
It("should send a report", func() {
|
|
expected := `1 Scanned, 0 Updated, 1 Failed
|
|
- fail1 (mock/fail1:latest): Failed: accidentally the whole container`
|
|
data := mockDataFromStates(s.FailedState)
|
|
Expect(getTemplatedResult(``, false, data)).To(Equal(expected))
|
|
})
|
|
})
|
|
When("the report is nil", func() {
|
|
It("should return the logged entries", func() {
|
|
expected := `The situation is under control
|
|
All the smoke might be covering up some problems
|
|
Turns out everything is on fire
|
|
`
|
|
Expect(getTemplatedResult(``, false, mockDataMultipleEntries)).To(Equal(expected))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
When("batching notifications", func() {
|
|
When("no messages are queued", func() {
|
|
It("should not send any notification", func() {
|
|
shoutrrr := createNotifier([]string{"logger://"}, allButTrace, "", true, StaticData{}, false, time.Duration(0))
|
|
shoutrrr.StartNotification()
|
|
shoutrrr.SendNotification(nil)
|
|
Consistently(logBuffer).ShouldNot(gbytes.Say(`Shoutrrr:`))
|
|
})
|
|
})
|
|
When("at least one message is queued", func() {
|
|
It("should send a notification", func() {
|
|
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)
|
|
Eventually(logBuffer).Should(gbytes.Say(`Shoutrrr: This log message is sponsored by ContainrrrVPN`))
|
|
})
|
|
})
|
|
})
|
|
|
|
When("the title data field is empty", func() {
|
|
It("should not have set the title param", func() {
|
|
shoutrrr := createNotifier([]string{"logger://"}, allButTrace, "", true, StaticData{
|
|
Host: "test.host",
|
|
Title: "",
|
|
}, false, time.Second)
|
|
_, found := shoutrrr.params.Title()
|
|
Expect(found).ToNot(BeTrue())
|
|
})
|
|
})
|
|
|
|
When("sending notifications", func() {
|
|
|
|
It("SlowNotificationNotSent", func() {
|
|
_, blockingRouter := sendNotificationsWithBlockingRouter(true)
|
|
|
|
Eventually(blockingRouter.sent).Should(Not(Receive()))
|
|
|
|
})
|
|
|
|
It("SlowNotificationSent", func() {
|
|
shoutrrr, blockingRouter := sendNotificationsWithBlockingRouter(true)
|
|
|
|
blockingRouter.unlock <- true
|
|
shoutrrr.Close()
|
|
|
|
Eventually(blockingRouter.sent).Should(Receive(BeTrue()))
|
|
})
|
|
})
|
|
})
|
|
|
|
type blockingRouter struct {
|
|
unlock chan bool
|
|
sent chan bool
|
|
}
|
|
|
|
func (b blockingRouter) Send(_ string, _ *types.Params) []error {
|
|
<-b.unlock
|
|
b.sent <- true
|
|
return nil
|
|
}
|
|
|
|
func sendNotificationsWithBlockingRouter(legacy bool) (*shoutrrrTypeNotifier, *blockingRouter) {
|
|
|
|
router := &blockingRouter{
|
|
unlock: make(chan bool, 1),
|
|
sent: make(chan bool, 1),
|
|
}
|
|
|
|
tpl, err := getShoutrrrTemplate("", legacy)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
shoutrrr := &shoutrrrTypeNotifier{
|
|
template: tpl,
|
|
messages: make(chan string, 1),
|
|
done: make(chan bool),
|
|
Router: router,
|
|
legacyTemplate: legacy,
|
|
params: &types.Params{},
|
|
delay: time.Duration(0),
|
|
}
|
|
|
|
entry := &logrus.Entry{
|
|
Message: "foo bar",
|
|
}
|
|
|
|
go sendNotifications(shoutrrr)
|
|
|
|
shoutrrr.StartNotification()
|
|
_ = shoutrrr.Fire(entry)
|
|
|
|
shoutrrr.SendNotification(nil)
|
|
|
|
return shoutrrr, router
|
|
}
|
|
|
|
func createNotifierWithTemplate(tplString string, legacy bool) (*shoutrrrTypeNotifier, error) {
|
|
tpl, err := getShoutrrrTemplate(tplString, legacy)
|
|
|
|
return &shoutrrrTypeNotifier{
|
|
template: tpl,
|
|
legacyTemplate: legacy,
|
|
}, err
|
|
}
|
|
|
|
func getTemplatedResult(tplString string, legacy bool, data Data) (msg string) {
|
|
notifier, err := createNotifierWithTemplate(tplString, legacy)
|
|
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
|
msg, err = notifier.buildMessage(data)
|
|
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
|
return msg
|
|
}
|