mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +02:00
Merge pull request #106 from rdamazio/master
Adding basic (but flexible) notification system which hooks into logrus.
This commit is contained in:
commit
f365014b8f
4 changed files with 236 additions and 2 deletions
33
README.md
33
README.md
|
@ -98,6 +98,8 @@ docker run --rm v2tec/watchtower --help
|
|||
* `--debug` Enable debug mode. When this option is specified you'll see more verbose logging in the watchtower log file.
|
||||
* `--help` Show documentation about the supported flags.
|
||||
|
||||
See below for options used to configure notifications.
|
||||
|
||||
## Linked Containers
|
||||
|
||||
Watchtower will detect if there are links between any of the running containers and ensure that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
|
||||
|
@ -161,3 +163,34 @@ docker run -d \
|
|||
## Updating Watchtower
|
||||
|
||||
If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you volume-mounted */var/run/docker.sock* into the watchtower container) then it has the ability to update itself. If a new version of the *v2tec/watchtower* image is pushed to the Docker Hub, your watchtower will pull down the new image and restart itself automatically.
|
||||
|
||||
## Notifications
|
||||
|
||||
Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging system, [logrus](http://github.com/sirupsen/logrus).
|
||||
The types of notifications to send are passed via the comma-separated option `--notifications` (or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
|
||||
|
||||
* `email` to send notifications via e-mail
|
||||
|
||||
To receive notifications by email, the following command-line options, or their corresponding environment variables, can be set:
|
||||
|
||||
* `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
|
||||
* `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
|
||||
* `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
|
||||
* `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
|
||||
* `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name watchtower \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-e WATCHTOWER_NOTIFICATIONS=email \
|
||||
-e WATCHTOWER_NOTIFICATION_EMAIL_FROM=fromaddress@gmail.com \
|
||||
-e WATCHTOWER_NOTIFICATION_EMAIL_TO=toaddress@gmail.com \
|
||||
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com \
|
||||
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=fromaddress@gmail.com \
|
||||
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app_password \
|
||||
v2tec/watchtower
|
||||
```
|
||||
|
||||
|
|
40
main.go
40
main.go
|
@ -1,7 +1,6 @@
|
|||
package main // import "github.com/v2tec/watchtower"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
@ -14,6 +13,7 @@ import (
|
|||
"github.com/urfave/cli"
|
||||
"github.com/v2tec/watchtower/actions"
|
||||
"github.com/v2tec/watchtower/container"
|
||||
"github.com/v2tec/watchtower/notifications"
|
||||
)
|
||||
|
||||
// DockerAPIMinVersion is the version of the docker API, which is minimally required by
|
||||
|
@ -27,6 +27,7 @@ var (
|
|||
scheduleSpec string
|
||||
cleanup bool
|
||||
noRestart bool
|
||||
notifier *notifications.Notifier
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -82,6 +83,37 @@ func main() {
|
|||
Name: "debug",
|
||||
Usage: "enable debug mode with verbose logging",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "notifications",
|
||||
Value: &cli.StringSlice{},
|
||||
Usage: "notification types to send (valid: email)",
|
||||
EnvVar: "WATCHTOWER_NOTIFICATIONS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "notification-email-from",
|
||||
Usage: "Address to send notification e-mails from",
|
||||
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_FROM",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "notification-email-to",
|
||||
Usage: "Address to send notification e-mails to",
|
||||
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_TO",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "notification-email-server",
|
||||
Usage: "SMTP server to send notification e-mails through",
|
||||
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "notification-email-server-user",
|
||||
Usage: "SMTP server user for sending notifications",
|
||||
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "notification-email-server-password",
|
||||
Usage: "SMTP server password for sending notifications",
|
||||
EnvVar: "WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD",
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
|
@ -115,6 +147,8 @@ func before(c *cli.Context) error {
|
|||
}
|
||||
|
||||
client = container.NewClient(!c.GlobalBool("no-pull"))
|
||||
notifier = notifications.NewNotifier(c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -135,9 +169,11 @@ func start(c *cli.Context) error {
|
|||
select {
|
||||
case v := <-tryLockSem:
|
||||
defer func() { tryLockSem <- v }()
|
||||
notifier.StartNotification()
|
||||
if err := actions.Update(client, names, cleanup, noRestart); err != nil {
|
||||
fmt.Println(err)
|
||||
log.Println(err)
|
||||
}
|
||||
notifier.SendNotification()
|
||||
default:
|
||||
log.Debug("Skipped another update already running.")
|
||||
}
|
||||
|
|
115
notifications/email.go
Normal file
115
notifications/email.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
emailType = "email"
|
||||
)
|
||||
|
||||
// Implements typeNotifier, 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 {
|
||||
From, To string
|
||||
Server, User, Password string
|
||||
entries []*log.Entry
|
||||
}
|
||||
|
||||
func newEmailNotifier(c *cli.Context) typeNotifier {
|
||||
n := &emailTypeNotifier{
|
||||
From: c.GlobalString("notification-email-from"),
|
||||
To: c.GlobalString("notification-email-to"),
|
||||
Server: c.GlobalString("notification-email-server"),
|
||||
User: c.GlobalString("notification-email-server-user"),
|
||||
Password: c.GlobalString("notification-email-server-password"),
|
||||
}
|
||||
|
||||
log.AddHook(n)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) buildMessage(entries []*log.Entry) []byte {
|
||||
emailSubject := "Watchtower updates"
|
||||
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.
|
||||
}
|
||||
|
||||
header := make(map[string]string)
|
||||
header["From"] = e.From
|
||||
header["To"] = e.To
|
||||
header["Subject"] = emailSubject
|
||||
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)
|
||||
}
|
||||
message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
|
||||
|
||||
return []byte(message)
|
||||
}
|
||||
|
||||
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() {
|
||||
auth := smtp.PlainAuth("", e.User, e.Password, e.Server)
|
||||
err := smtp.SendMail(e.Server+":25", auth, e.From, []string{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 {
|
||||
e.sendEntries(e.entries)
|
||||
e.entries = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) Levels() []log.Level {
|
||||
// TODO: Make this configurable.
|
||||
return []log.Level{
|
||||
log.PanicLevel,
|
||||
log.FatalLevel,
|
||||
log.ErrorLevel,
|
||||
log.WarnLevel,
|
||||
log.InfoLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *emailTypeNotifier) Fire(entry *log.Entry) error {
|
||||
if e.entries != nil {
|
||||
e.entries = append(e.entries, entry)
|
||||
} else {
|
||||
// Log output generated outside a cycle is sent immediately.
|
||||
e.sendEntries([]*log.Entry{entry})
|
||||
}
|
||||
return nil
|
||||
}
|
50
notifications/notifier.go
Normal file
50
notifications/notifier.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type typeNotifier interface {
|
||||
StartNotification()
|
||||
SendNotification()
|
||||
}
|
||||
|
||||
// Notifier can send log output as notification to admins, with optional batching.
|
||||
type Notifier struct {
|
||||
types []typeNotifier
|
||||
}
|
||||
|
||||
// NewNotifier creates and returns a new Notifier, using global configuration.
|
||||
func NewNotifier(c *cli.Context) *Notifier {
|
||||
n := &Notifier{}
|
||||
|
||||
// Parse types and create notifiers.
|
||||
types := c.GlobalStringSlice("notifications")
|
||||
for _, t := range types {
|
||||
var tn typeNotifier
|
||||
switch t {
|
||||
case emailType:
|
||||
tn = newEmailNotifier(c)
|
||||
default:
|
||||
log.Fatalf("Unknown notification type %q", t)
|
||||
}
|
||||
n.types = append(n.types, tn)
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// StartNotification starts a log batch. Notifications will be accumulated after this point and only sent when SendNotification() is called.
|
||||
func (n *Notifier) StartNotification() {
|
||||
for _, t := range n.types {
|
||||
t.StartNotification()
|
||||
}
|
||||
}
|
||||
|
||||
// SendNotification sends any notifications accumulated since StartNotification() was called.
|
||||
func (n *Notifier) SendNotification() {
|
||||
for _, t := range n.types {
|
||||
t.SendNotification()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue