mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-21 21:30:48 +02:00
parent
3bbe1bd109
commit
35490c853d
9 changed files with 375 additions and 295 deletions
23
cmd/root.go
23
cmd/root.go
|
@ -34,15 +34,20 @@ var (
|
|||
scope string
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "watchtower",
|
||||
Short: "Automatically updates running Docker containers",
|
||||
Long: `
|
||||
Watchtower automatically updates running Docker containers whenever a new image is released.
|
||||
More information available at https://github.com/containrrr/watchtower/.
|
||||
`,
|
||||
Run: Run,
|
||||
PreRun: PreRun,
|
||||
var rootCmd = NewRootCommand()
|
||||
|
||||
// NewRootCommand creates the root command for watchtower
|
||||
func NewRootCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "watchtower",
|
||||
Short: "Automatically updates running Docker containers",
|
||||
Long: `
|
||||
Watchtower automatically updates running Docker containers whenever a new image is released.
|
||||
More information available at https://github.com/containrrr/watchtower/.
|
||||
`,
|
||||
Run: Run,
|
||||
PreRun: PreRun,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
shoutrrrSmtp "github.com/containrrr/shoutrrr/pkg/services/smtp"
|
||||
t "github.com/containrrr/watchtower/pkg/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
emailType = "email"
|
||||
)
|
||||
|
||||
// Implements Notifier, logrus.Hook
|
||||
// The default logrus email integration would have several issues:
|
||||
// - It would send one email per log output
|
||||
// - It would only send errors
|
||||
// We work around that by holding on to log entries until the update cycle is done.
|
||||
type emailTypeNotifier struct {
|
||||
url string
|
||||
From, To string
|
||||
Server, User, Password, SubjectTag string
|
||||
Port int
|
||||
|
@ -33,7 +26,12 @@ type emailTypeNotifier struct {
|
|||
delay time.Duration
|
||||
}
|
||||
|
||||
func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
|
||||
// NewEmailNotifier is a factory method creating a new email notifier instance
|
||||
func NewEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.ConvertableNotifier {
|
||||
return newEmailNotifier(c, acceptedLogLevels)
|
||||
}
|
||||
|
||||
func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.ConvertableNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
|
||||
from, _ := flags.GetString("notification-email-from")
|
||||
|
@ -47,6 +45,7 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
|
|||
subjecttag, _ := flags.GetString("notification-email-subjecttag")
|
||||
|
||||
n := &emailTypeNotifier{
|
||||
entries: []*log.Entry{},
|
||||
From: from,
|
||||
To: to,
|
||||
Server: server,
|
||||
|
@ -59,12 +58,33 @@ func newEmailNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
|
|||
SubjectTag: subjecttag,
|
||||
}
|
||||
|
||||
log.AddHook(n)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
|
||||
func (e *emailTypeNotifier) GetURL() string {
|
||||
conf := &shoutrrrSmtp.Config{
|
||||
FromAddress: e.From,
|
||||
FromName: "Watchtower",
|
||||
ToAddresses: []string{e.To},
|
||||
Port: uint16(e.Port),
|
||||
Host: e.Server,
|
||||
Subject: e.getSubject(),
|
||||
Username: e.User,
|
||||
Password: e.Password,
|
||||
UseStartTLS: true,
|
||||
UseHTML: false,
|
||||
}
|
||||
|
||||
if len(e.User) > 0 {
|
||||
conf.Set("auth", "Plain")
|
||||
} else {
|
||||
conf.Set("auth", "None")
|
||||
}
|
||||
|
||||
return conf.GetURL().String()
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) getSubject() string {
|
||||
var emailSubject string
|
||||
|
||||
if e.SubjectTag == "" {
|
||||
|
@ -75,83 +95,13 @@ func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
|
|||
if hostname, err := os.Hostname(); err == nil {
|
||||
emailSubject += " on " + hostname
|
||||
}
|
||||
body := ""
|
||||
for _, entry := range 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.
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
|
||||
header := make(map[string]string)
|
||||
header["From"] = e.From
|
||||
header["To"] = e.To
|
||||
header["Subject"] = emailSubject
|
||||
header["Date"] = t.Format(time.RFC1123Z)
|
||||
header["MIME-Version"] = "1.0"
|
||||
header["Content-Type"] = "text/plain; charset=\"utf-8\""
|
||||
header["Content-Transfer-Encoding"] = "base64"
|
||||
|
||||
message := ""
|
||||
for k, v := range header {
|
||||
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
||||
}
|
||||
|
||||
encodedBody := base64.StdEncoding.EncodeToString([]byte(body))
|
||||
//RFC 2045 base64 encoding demands line no longer than 76 characters.
|
||||
for _, line := range SplitSubN(encodedBody, 76) {
|
||||
message += "\r\n" + line
|
||||
}
|
||||
|
||||
return []byte(message)
|
||||
return emailSubject
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) sendEntries(entries []*log.Entry) {
|
||||
// Do the sending in a separate goroutine so we don't block the main process.
|
||||
msg := e.buildMessage(entries)
|
||||
go func() {
|
||||
if e.delay > 0 {
|
||||
time.Sleep(e.delay)
|
||||
}
|
||||
|
||||
var auth smtp.Auth
|
||||
if e.User != "" {
|
||||
auth = smtp.PlainAuth("", e.User, e.Password, e.Server)
|
||||
}
|
||||
err := SendMail(e.Server+":"+strconv.Itoa(e.Port), e.tlsSkipVerify, auth, e.From, strings.Split(e.To, ","), msg)
|
||||
if err != nil {
|
||||
// Use fmt so it doesn't trigger another email.
|
||||
fmt.Println("Failed to send notification email: ", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) StartNotification() {
|
||||
if e.entries == nil {
|
||||
e.entries = make([]*log.Entry, 0, 10)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) SendNotification() {
|
||||
if e.entries == nil || len(e.entries) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
e.sendEntries(e.entries)
|
||||
e.entries = nil
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) Levels() []log.Level {
|
||||
return e.logLevels
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) Fire(entry *log.Entry) error {
|
||||
if e.entries != nil {
|
||||
e.entries = append(e.entries, entry)
|
||||
} else {
|
||||
e.sendEntries([]*log.Entry{entry})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// TODO: Delete these once all notifiers have been converted to shoutrrr
|
||||
func (e *emailTypeNotifier) StartNotification() {}
|
||||
func (e *emailTypeNotifier) SendNotification() {}
|
||||
func (e *emailTypeNotifier) Levels() []log.Level { return nil }
|
||||
func (e *emailTypeNotifier) Fire(entry *log.Entry) error { return nil }
|
||||
|
||||
func (e *emailTypeNotifier) Close() {}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
shoutrrrGotify "github.com/containrrr/shoutrrr/pkg/services/gotify"
|
||||
t "github.com/containrrr/watchtower/pkg/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -24,10 +21,40 @@ type gotifyTypeNotifier struct {
|
|||
logLevels []log.Level
|
||||
}
|
||||
|
||||
func newGotifyNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
|
||||
// NewGotifyNotifier is a factory method creating a new gotify notifier instance
|
||||
func NewGotifyNotifier(c *cobra.Command, levels []log.Level) t.ConvertableNotifier {
|
||||
return newGotifyNotifier(c, levels)
|
||||
}
|
||||
|
||||
func newGotifyNotifier(c *cobra.Command, levels []log.Level) t.ConvertableNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
|
||||
url := getGotifyURL(flags)
|
||||
token := getGotifyToken(flags)
|
||||
|
||||
skipVerify, _ := flags.GetBool("notification-gotify-tls-skip-verify")
|
||||
|
||||
n := &gotifyTypeNotifier{
|
||||
gotifyURL: url,
|
||||
gotifyAppToken: token,
|
||||
gotifyInsecureSkipVerify: skipVerify,
|
||||
logLevels: levels,
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func getGotifyToken(flags *pflag.FlagSet) string {
|
||||
gotifyToken, _ := flags.GetString("notification-gotify-token")
|
||||
if len(gotifyToken) < 1 {
|
||||
log.Fatal("Required argument --notification-gotify-token(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN(env) is empty.")
|
||||
}
|
||||
return gotifyToken
|
||||
}
|
||||
|
||||
func getGotifyURL(flags *pflag.FlagSet) string {
|
||||
gotifyURL, _ := flags.GetString("notification-gotify-url")
|
||||
|
||||
if len(gotifyURL) < 1 {
|
||||
log.Fatal("Required argument --notification-gotify-url(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_URL(env) is empty.")
|
||||
} else if !(strings.HasPrefix(gotifyURL, "http://") || strings.HasPrefix(gotifyURL, "https://")) {
|
||||
|
@ -36,82 +63,29 @@ func newGotifyNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifi
|
|||
log.Warn("Using an HTTP url for Gotify is insecure")
|
||||
}
|
||||
|
||||
gotifyToken, _ := flags.GetString("notification-gotify-token")
|
||||
if len(gotifyToken) < 1 {
|
||||
log.Fatal("Required argument --notification-gotify-token(cli) or WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN(env) is empty.")
|
||||
}
|
||||
|
||||
gotifyInsecureSkipVerify, _ := flags.GetBool("notification-gotify-tls-skip-verify")
|
||||
|
||||
n := &gotifyTypeNotifier{
|
||||
gotifyURL: gotifyURL,
|
||||
gotifyAppToken: gotifyToken,
|
||||
gotifyInsecureSkipVerify: gotifyInsecureSkipVerify,
|
||||
logLevels: acceptedLogLevels,
|
||||
}
|
||||
|
||||
log.AddHook(n)
|
||||
|
||||
return n
|
||||
return gotifyURL
|
||||
}
|
||||
|
||||
func (n *gotifyTypeNotifier) StartNotification() {}
|
||||
|
||||
func (n *gotifyTypeNotifier) SendNotification() {}
|
||||
|
||||
func (n *gotifyTypeNotifier) Close() {}
|
||||
|
||||
func (n *gotifyTypeNotifier) Levels() []log.Level {
|
||||
return n.logLevels
|
||||
}
|
||||
|
||||
func (n *gotifyTypeNotifier) getURL() string {
|
||||
func (n *gotifyTypeNotifier) GetURL() string {
|
||||
url := n.gotifyURL
|
||||
if !strings.HasSuffix(url, "/") {
|
||||
url += "/"
|
||||
|
||||
if strings.HasPrefix(url, "https://") {
|
||||
url = strings.TrimPrefix(url, "https://")
|
||||
} else {
|
||||
url = strings.TrimPrefix(url, "http://")
|
||||
}
|
||||
return url + "message?token=" + n.gotifyAppToken
|
||||
|
||||
url = strings.TrimSuffix(url, "/")
|
||||
|
||||
config := &shoutrrrGotify.Config{
|
||||
Host: url,
|
||||
Token: n.gotifyAppToken,
|
||||
}
|
||||
|
||||
return config.GetURL().String()
|
||||
}
|
||||
|
||||
func (n *gotifyTypeNotifier) Fire(entry *log.Entry) error {
|
||||
|
||||
go func() {
|
||||
jsonBody, err := json.Marshal(gotifyMessage{
|
||||
Message: "(" + entry.Level.String() + "): " + entry.Message,
|
||||
Title: "Watchtower",
|
||||
Priority: 0,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("Failed to create JSON body for Gotify notification: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Explicitly define the client so we can set InsecureSkipVerify to the desired value.
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: n.gotifyInsecureSkipVerify,
|
||||
},
|
||||
},
|
||||
}
|
||||
jsonBodyBuffer := bytes.NewBuffer([]byte(jsonBody))
|
||||
resp, err := client.Post(n.getURL(), "application/json", jsonBodyBuffer)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to send Gotify notification: ", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
fmt.Printf("Gotify notification returned %d HTTP status code", resp.StatusCode)
|
||||
}
|
||||
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
type gotifyMessage struct {
|
||||
Message string `json:"message"`
|
||||
Title string `json:"title"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
func (n *gotifyTypeNotifier) StartNotification() {}
|
||||
func (n *gotifyTypeNotifier) SendNotification() {}
|
||||
func (n *gotifyTypeNotifier) Close() {}
|
||||
func (n *gotifyTypeNotifier) Levels() []log.Level { return nil }
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
shoutrrrTeams "github.com/containrrr/shoutrrr/pkg/services/teams"
|
||||
t "github.com/containrrr/watchtower/pkg/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -22,7 +19,12 @@ type msTeamsTypeNotifier struct {
|
|||
data bool
|
||||
}
|
||||
|
||||
func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
|
||||
// NewMsTeamsNotifier is a factory method creating a new teams notifier instance
|
||||
func NewMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.ConvertableNotifier {
|
||||
return newMsTeamsNotifier(cmd, acceptedLogLevels)
|
||||
}
|
||||
|
||||
func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.ConvertableNotifier {
|
||||
|
||||
flags := cmd.PersistentFlags()
|
||||
|
||||
|
@ -38,103 +40,29 @@ func newMsTeamsNotifier(cmd *cobra.Command, acceptedLogLevels []log.Level) t.Not
|
|||
data: withData,
|
||||
}
|
||||
|
||||
log.AddHook(n)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *msTeamsTypeNotifier) StartNotification() {}
|
||||
func (n *msTeamsTypeNotifier) GetURL() string {
|
||||
|
||||
func (n *msTeamsTypeNotifier) SendNotification() {}
|
||||
baseURL := "https://outlook.office.com/webhook/"
|
||||
|
||||
func (n *msTeamsTypeNotifier) Close() {}
|
||||
path := strings.Replace(n.webHookURL, baseURL, "", 1)
|
||||
rawToken := strings.Replace(path, "/IncomingWebhook", "", 1)
|
||||
token := strings.Split(rawToken, "/")
|
||||
config := &shoutrrrTeams.Config{
|
||||
Token: shoutrrrTeams.Token{
|
||||
A: token[0],
|
||||
B: token[1],
|
||||
C: token[2],
|
||||
},
|
||||
}
|
||||
|
||||
func (n *msTeamsTypeNotifier) Levels() []log.Level {
|
||||
return n.levels
|
||||
return config.GetURL().String()
|
||||
}
|
||||
|
||||
func (n *msTeamsTypeNotifier) Fire(entry *log.Entry) error {
|
||||
|
||||
message := "(" + entry.Level.String() + "): " + entry.Message
|
||||
|
||||
go func() {
|
||||
webHookBody := messageCard{
|
||||
CardType: "MessageCard",
|
||||
Context: "http://schema.org/extensions",
|
||||
Markdown: true,
|
||||
Text: message,
|
||||
}
|
||||
|
||||
if n.data && entry.Data != nil && len(entry.Data) > 0 {
|
||||
section := messageCardSection{
|
||||
Facts: make([]messageCardSectionFact, len(entry.Data)),
|
||||
Text: "",
|
||||
}
|
||||
|
||||
index := 0
|
||||
for k, v := range entry.Data {
|
||||
section.Facts[index] = messageCardSectionFact{
|
||||
Name: k,
|
||||
Value: fmt.Sprint(v),
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
webHookBody.Sections = []messageCardSection{section}
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(webHookBody)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to build JSON body for MSTeams notificattion: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := http.Post(n.webHookURL, "application/json", bytes.NewBuffer([]byte(jsonBody)))
|
||||
if err != nil {
|
||||
fmt.Println("Failed to send MSTeams notificattion: ", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
fmt.Println("Failed to send MSTeams notificattion. HTTP RESPONSE STATUS: ", resp.StatusCode)
|
||||
if resp.Body != nil {
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
bodyString := string(bodyBytes)
|
||||
fmt.Println(bodyString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type messageCard struct {
|
||||
CardType string `json:"@type"`
|
||||
Context string `json:"@context"`
|
||||
CorrelationID string `json:"correlationId,omitempty"`
|
||||
ThemeColor string `json:"themeColor,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Markdown bool `json:"markdown,bool"`
|
||||
Sections []messageCardSection `json:"sections,omitempty"`
|
||||
}
|
||||
|
||||
type messageCardSection struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
ActivityTitle string `json:"activityTitle,omitempty"`
|
||||
ActivitySubtitle string `json:"activitySubtitle,omitempty"`
|
||||
ActivityImage string `json:"activityImage,omitempty"`
|
||||
ActivityText string `json:"activityText,omitempty"`
|
||||
HeroImage string `json:"heroImage,omitempty"`
|
||||
Facts []messageCardSectionFact `json:"facts,omitempty"`
|
||||
}
|
||||
|
||||
type messageCardSectionFact struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
func (n *msTeamsTypeNotifier) StartNotification() {}
|
||||
func (n *msTeamsTypeNotifier) SendNotification() {}
|
||||
func (n *msTeamsTypeNotifier) Close() {}
|
||||
func (n *msTeamsTypeNotifier) Levels() []log.Level { return nil }
|
||||
func (n *msTeamsTypeNotifier) Fire(entry *log.Entry) error { return nil }
|
||||
|
|
|
@ -31,26 +31,48 @@ func NewNotifier(c *cobra.Command) *Notifier {
|
|||
if err != nil {
|
||||
log.WithField("could not read notifications argument", log.Fields{"Error": err}).Fatal()
|
||||
}
|
||||
|
||||
n.types = n.GetNotificationTypes(c, acceptedLogLevels, types)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// GetNotificationTypes produces an array of notifiers from a list of types
|
||||
func (n *Notifier) GetNotificationTypes(cmd *cobra.Command, levels []log.Level, types []string) []ty.Notifier {
|
||||
output := make([]ty.Notifier, 0)
|
||||
|
||||
for _, t := range types {
|
||||
var tn ty.Notifier
|
||||
|
||||
if t == shoutrrrType {
|
||||
output = append(output, newShoutrrrNotifier(cmd, levels))
|
||||
continue
|
||||
}
|
||||
|
||||
var legacyNotifier ty.ConvertableNotifier
|
||||
|
||||
switch t {
|
||||
case emailType:
|
||||
tn = newEmailNotifier(c, acceptedLogLevels)
|
||||
legacyNotifier = newEmailNotifier(cmd, []log.Level{})
|
||||
case slackType:
|
||||
tn = newSlackNotifier(c, acceptedLogLevels)
|
||||
legacyNotifier = newSlackNotifier(cmd, []log.Level{})
|
||||
case msTeamsType:
|
||||
tn = newMsTeamsNotifier(c, acceptedLogLevels)
|
||||
legacyNotifier = newMsTeamsNotifier(cmd, levels)
|
||||
case gotifyType:
|
||||
tn = newGotifyNotifier(c, acceptedLogLevels)
|
||||
case shoutrrrType:
|
||||
tn = newShoutrrrNotifier(c, acceptedLogLevels)
|
||||
legacyNotifier = newGotifyNotifier(cmd, []log.Level{})
|
||||
default:
|
||||
log.Fatalf("Unknown notification type %q", t)
|
||||
}
|
||||
n.types = append(n.types, tn)
|
||||
|
||||
notifier := newShoutrrrNotifierFromURL(
|
||||
cmd,
|
||||
legacyNotifier.GetURL(),
|
||||
levels,
|
||||
)
|
||||
|
||||
output = append(output, notifier)
|
||||
}
|
||||
|
||||
return n
|
||||
return output
|
||||
}
|
||||
|
||||
// StartNotification starts a log batch. Notifications will be accumulated after this point and only sent when SendNotification() is called.
|
||||
|
|
163
pkg/notifications/notifier_test.go
Normal file
163
pkg/notifications/notifier_test.go
Normal file
|
@ -0,0 +1,163 @@
|
|||
package notifications_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/containrrr/watchtower/cmd"
|
||||
"github.com/containrrr/watchtower/internal/flags"
|
||||
"github.com/containrrr/watchtower/pkg/notifications"
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestActions(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Notifier Suite")
|
||||
}
|
||||
|
||||
var _ = Describe("notifications", func() {
|
||||
// TODO: Either, we delete this test or we need to pass it valid URLs in the cobra command.
|
||||
// ---
|
||||
// When("getting notifiers from a types array", func() {
|
||||
// It("should return the same amount of notifiers a string entries", func() {
|
||||
|
||||
// notifier := ¬ifications.Notifier{}
|
||||
// notifiers := notifier.GetNotificationTypes(&cobra.Command{}, []log.Level{}, []string{"slack", "email"})
|
||||
// Expect(len(notifiers)).To(Equal(2))
|
||||
// })
|
||||
// })
|
||||
Describe("the slack notifier", func() {
|
||||
When("converting a slack service config into a shoutrrr url", func() {
|
||||
builderFn := notifications.NewSlackNotifier
|
||||
|
||||
It("should return the expected URL", func() {
|
||||
|
||||
username := "containrrrbot"
|
||||
tokenA := "aaa"
|
||||
tokenB := "bbb"
|
||||
tokenC := "ccc"
|
||||
|
||||
password := fmt.Sprintf("%s-%s-%s", tokenA, tokenB, tokenC)
|
||||
hookURL := fmt.Sprintf("https://hooks.slack.com/services/%s/%s/%s", tokenA, tokenB, tokenC)
|
||||
expectedOutput := fmt.Sprintf("slack://%s:%s@%s/%s/%s", username, password, tokenA, tokenB, tokenC)
|
||||
|
||||
args := []string{
|
||||
"--notification-slack-hook-url",
|
||||
hookURL,
|
||||
"--notification-slack-identifier",
|
||||
username,
|
||||
}
|
||||
|
||||
testURL(builderFn, args, expectedOutput)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("the gotify notifier", func() {
|
||||
When("converting a gotify service config into a shoutrrr url", func() {
|
||||
builderFn := notifications.NewGotifyNotifier
|
||||
|
||||
It("should return the expected URL", func() {
|
||||
token := "aaa"
|
||||
host := "shoutrrr.local"
|
||||
|
||||
expectedOutput := fmt.Sprintf("gotify://%s/%s", host, token)
|
||||
|
||||
args := []string{
|
||||
"--notification-gotify-url",
|
||||
fmt.Sprintf("https://%s", host),
|
||||
"--notification-gotify-token",
|
||||
token,
|
||||
}
|
||||
|
||||
testURL(builderFn, args, expectedOutput)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("the teams notifier", func() {
|
||||
When("converting a teams service config into a shoutrrr url", func() {
|
||||
builderFn := notifications.NewMsTeamsNotifier
|
||||
|
||||
It("should return the expected URL", func() {
|
||||
|
||||
tokenA := "aaa"
|
||||
tokenB := "bbb"
|
||||
tokenC := "ccc"
|
||||
|
||||
hookURL := fmt.Sprintf("https://outlook.office.com/webhook/%s/IncomingWebhook/%s/%s", tokenA, tokenB, tokenC)
|
||||
expectedOutput := fmt.Sprintf("teams://%s:%s@%s", tokenA, tokenB, tokenC)
|
||||
|
||||
args := []string{
|
||||
"--notification-msteams-hook",
|
||||
hookURL,
|
||||
}
|
||||
|
||||
testURL(builderFn, args, expectedOutput)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("the email notifier", func() {
|
||||
|
||||
builderFn := notifications.NewEmailNotifier
|
||||
|
||||
When("converting an email service config into a shoutrrr url", func() {
|
||||
It("should set the from address in the URL", func() {
|
||||
fromAddress := "lala@example.com"
|
||||
expectedOutput := buildExpectedURL("", "", "", 25, fromAddress, "", "None")
|
||||
args := []string{
|
||||
"--notification-email-from",
|
||||
fromAddress,
|
||||
}
|
||||
testURL(builderFn, args, expectedOutput)
|
||||
})
|
||||
|
||||
It("should return the expected URL", func() {
|
||||
|
||||
fromAddress := "sender@example.com"
|
||||
toAddress := "receiver@example.com"
|
||||
expectedOutput := buildExpectedURL("", "", "", 25, fromAddress, toAddress, "None")
|
||||
|
||||
args := []string{
|
||||
"--notification-email-from",
|
||||
fromAddress,
|
||||
"--notification-email-to",
|
||||
toAddress,
|
||||
}
|
||||
|
||||
testURL(builderFn, args, expectedOutput)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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/?fromAddress=%s&fromName=Watchtower&toAddresses=%s&auth=%s&subject=%s&startTls=Yes&useHTML=No"
|
||||
return fmt.Sprintf(template, username, password, host, port, from, to, auth, subject)
|
||||
}
|
||||
|
||||
type builderFn = func(c *cobra.Command, acceptedLogLevels []log.Level) types.ConvertableNotifier
|
||||
|
||||
func testURL(builder builderFn, args []string, expectedURL string) {
|
||||
|
||||
command := cmd.NewRootCommand()
|
||||
flags.RegisterNotificationFlags(command)
|
||||
command.ParseFlags(args)
|
||||
|
||||
notifier := builder(command, []log.Level{})
|
||||
actualURL := notifier.GetURL()
|
||||
|
||||
Expect(actualURL).To(Equal(expectedURL))
|
||||
}
|
|
@ -35,8 +35,17 @@ type shoutrrrTypeNotifier struct {
|
|||
|
||||
func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
|
||||
flags := c.PersistentFlags()
|
||||
|
||||
urls, _ := flags.GetStringArray("notification-url")
|
||||
template := getShoutrrrTemplate(c)
|
||||
return createSender(urls, acceptedLogLevels, template)
|
||||
}
|
||||
|
||||
func newShoutrrrNotifierFromURL(c *cobra.Command, url string, levels []log.Level) t.Notifier {
|
||||
template := getShoutrrrTemplate(c)
|
||||
return createSender([]string{url}, levels, template)
|
||||
}
|
||||
|
||||
func createSender(urls []string, levels []log.Level, template *template.Template) t.Notifier {
|
||||
r, err := shoutrrr.CreateSender(urls...)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize Shoutrrr notifications: %s\n", err.Error())
|
||||
|
@ -45,10 +54,10 @@ func newShoutrrrNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Noti
|
|||
n := &shoutrrrTypeNotifier{
|
||||
Urls: urls,
|
||||
Router: r,
|
||||
logLevels: acceptedLogLevels,
|
||||
template: getShoutrrrTemplate(c),
|
||||
messages: make(chan string, 1),
|
||||
done: make(chan bool),
|
||||
logLevels: levels,
|
||||
template: template,
|
||||
}
|
||||
|
||||
log.AddHook(n)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
shoutrrrSlack "github.com/containrrr/shoutrrr/pkg/services/slack"
|
||||
t "github.com/containrrr/watchtower/pkg/types"
|
||||
"github.com/johntdyer/slackrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -15,7 +18,12 @@ type slackTypeNotifier struct {
|
|||
slackrus.SlackrusHook
|
||||
}
|
||||
|
||||
func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifier {
|
||||
// NewSlackNotifier is a factory function used to generate new instance of the slack notifier type
|
||||
func NewSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.ConvertableNotifier {
|
||||
return newSlackNotifier(c, acceptedLogLevels)
|
||||
}
|
||||
|
||||
func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.ConvertableNotifier {
|
||||
flags := c.PersistentFlags()
|
||||
|
||||
hookURL, _ := flags.GetString("notification-slack-hook-url")
|
||||
|
@ -23,7 +31,6 @@ func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
|
|||
channel, _ := flags.GetString("notification-slack-channel")
|
||||
emoji, _ := flags.GetString("notification-slack-icon-emoji")
|
||||
iconURL, _ := flags.GetString("notification-slack-icon-url")
|
||||
|
||||
n := &slackTypeNotifier{
|
||||
SlackrusHook: slackrus.SlackrusHook{
|
||||
HookURL: hookURL,
|
||||
|
@ -34,12 +41,27 @@ func newSlackNotifier(c *cobra.Command, acceptedLogLevels []log.Level) t.Notifie
|
|||
AcceptedLevels: acceptedLogLevels,
|
||||
},
|
||||
}
|
||||
|
||||
log.AddHook(n)
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *slackTypeNotifier) StartNotification() {}
|
||||
func (s *slackTypeNotifier) GetURL() string {
|
||||
rawTokens := strings.Replace(s.HookURL, "https://hooks.slack.com/services/", "", 1)
|
||||
tokens := strings.Split(rawTokens, "/")
|
||||
|
||||
conf := &shoutrrrSlack.Config{
|
||||
BotName: s.Username,
|
||||
Token: shoutrrrSlack.Token{
|
||||
A: tokens[0],
|
||||
B: tokens[1],
|
||||
C: tokens[2],
|
||||
},
|
||||
}
|
||||
|
||||
return conf.GetURL().String()
|
||||
}
|
||||
|
||||
func (s *slackTypeNotifier) StartNotification() {
|
||||
}
|
||||
|
||||
func (s *slackTypeNotifier) SendNotification() {}
|
||||
|
||||
|
|
7
pkg/types/convertable_notifier.go
Normal file
7
pkg/types/convertable_notifier.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package types
|
||||
|
||||
// ConvertableNotifier is a notifier capable of creating a shoutrrr URL
|
||||
type ConvertableNotifier interface {
|
||||
Notifier
|
||||
GetURL() string
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue