Merge pull request #106 from rdamazio/master

Adding basic (but flexible) notification system which hooks into logrus.
pull/104/head
stffabi 7 years ago committed by GitHub
commit f365014b8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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
```

@ -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.")
}

@ -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
}

@ -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…
Cancel
Save