mirror of
https://github.com/offen/docker-volume-backup.git
synced 2024-11-22 05:10:28 +01:00
Enable notifications on multiple levels
This commit is contained in:
parent
08d78a0bd6
commit
8bad0656b3
19
README.md
19
README.md
@ -236,9 +236,9 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# BACKUP_STOP_CONTAINER_LABEL="service1"
|
# BACKUP_STOP_CONTAINER_LABEL="service1"
|
||||||
|
|
||||||
########### NOTIFICATIONS ON FAILED BACKUP RUNS
|
########### NOTIFICATIONS
|
||||||
|
|
||||||
# In case a backup fails, notifications (email, Slack, etc.) can be sent out.
|
# Notifications (email, Slack, etc.) can be sent out when a backup run finishes.
|
||||||
# Configuration is provided as a comma-separated list of URLs as consumed
|
# Configuration is provided as a comma-separated list of URLs as consumed
|
||||||
# by `shoutrrr`: https://containrrr.dev/shoutrrr/v0.5/services/overview/
|
# by `shoutrrr`: https://containrrr.dev/shoutrrr/v0.5/services/overview/
|
||||||
# When providing multiple URLs or an URL that contains a comma, the values
|
# When providing multiple URLs or an URL that contains a comma, the values
|
||||||
@ -249,17 +249,22 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# NOTIFICATION_URLS=smtp://username:password@host:587/?fromAddress=sender@example.com&toAddresses=recipient@example.com
|
# NOTIFICATION_URLS=smtp://username:password@host:587/?fromAddress=sender@example.com&toAddresses=recipient@example.com
|
||||||
|
|
||||||
########### EMAIL NOTIFICATIONS ON FAILED BACKUP RUNS
|
# By default, notifications will only be sent out when a backup run fails.
|
||||||
|
# To receive notifications for every run, set `NOTIFICATION_LEVEL` to `always`
|
||||||
|
|
||||||
|
# NOTIFICATION_LEVEL="always"
|
||||||
|
|
||||||
|
########### EMAIL NOTIFICATIONS
|
||||||
|
|
||||||
# ************************************************************************
|
# ************************************************************************
|
||||||
# Providing notification configuration like this has been deprecated
|
# Providing notification configuration like this has been deprecated
|
||||||
# and will be removed in the next major version. Please use NOTIFICATION_ULRS
|
# and will be removed in the next major version. Please use NOTIFICATION_URLS
|
||||||
# as documented above instead.
|
# as documented above instead.
|
||||||
# ************************************************************************
|
# ************************************************************************
|
||||||
|
|
||||||
# In case SMTP credentials are provided, notification emails can be sent out on
|
# In case SMTP credentials are provided, notification emails can be sent out when
|
||||||
# failed backup runs. These emails will contain the start time, the error
|
# a backup run finished. These emails will contain the start time, the error
|
||||||
# message and all log output prior to the failure.
|
# message on failure and all prior log output.
|
||||||
|
|
||||||
# The recipient(s) of the notification. Supply a comma separated list
|
# The recipient(s) of the notification. Supply a comma separated list
|
||||||
# of adresses if you want to notify multiple recipients. If this is
|
# of adresses if you want to notify multiple recipients. If this is
|
||||||
|
@ -44,7 +44,7 @@ func main() {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if pArg := recover(); pArg != nil {
|
if pArg := recover(); pArg != nil {
|
||||||
if err, ok := pArg.(error); ok {
|
if err, ok := pArg.(error); ok {
|
||||||
if hookErr := s.runHooks(err, hookLevelCleanup, hookLevelFailure); hookErr != nil {
|
if hookErr := s.runHooks(err, hookLevelCleanup, hookLevelFailure, hookLevelAlways); hookErr != nil {
|
||||||
s.logger.Errorf("An error occurred calling the registered hooks: %s", hookErr)
|
s.logger.Errorf("An error occurred calling the registered hooks: %s", hookErr)
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -52,7 +52,7 @@ func main() {
|
|||||||
panic(pArg)
|
panic(pArg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.runHooks(nil, hookLevelCleanup); err != nil {
|
if err := s.runHooks(nil, hookLevelCleanup, hookLevelAlways); err != nil {
|
||||||
s.logger.Errorf(
|
s.logger.Errorf(
|
||||||
"Backup procedure ran successfully, but an error ocurred calling the registered hooks: %v",
|
"Backup procedure ran successfully, but an error ocurred calling the registered hooks: %v",
|
||||||
err,
|
err,
|
||||||
@ -115,6 +115,7 @@ type config struct {
|
|||||||
AwsIamRoleEndpoint string `split_words:"true"`
|
AwsIamRoleEndpoint string `split_words:"true"`
|
||||||
GpgPassphrase string `split_words:"true"`
|
GpgPassphrase string `split_words:"true"`
|
||||||
NotificationURLs []string `envconfig:"NOTIFICATION_URLS"`
|
NotificationURLs []string `envconfig:"NOTIFICATION_URLS"`
|
||||||
|
NotificationLevel string `split_words:"true" default:"failure"`
|
||||||
EmailNotificationRecipient string `split_words:"true"`
|
EmailNotificationRecipient string `split_words:"true"`
|
||||||
EmailNotificationSender string `split_words:"true" default:"noreply@nohost"`
|
EmailNotificationSender string `split_words:"true" default:"noreply@nohost"`
|
||||||
EmailSMTPHost string `envconfig:"EMAIL_SMTP_HOST"`
|
EmailSMTPHost string `envconfig:"EMAIL_SMTP_HOST"`
|
||||||
@ -208,38 +209,19 @@ func newScript() (*script, error) {
|
|||||||
s.c.EmailNotificationRecipient,
|
s.c.EmailNotificationRecipient,
|
||||||
)
|
)
|
||||||
s.c.NotificationURLs = append(s.c.NotificationURLs, emailURL)
|
s.c.NotificationURLs = append(s.c.NotificationURLs, emailURL)
|
||||||
s.logger.Warn("Using EMAIL_* keys for providing notification configuration has been deprecated and will be removed in the next major version.")
|
s.logger.Warn(
|
||||||
s.logger.Warn("Please use NOTIFICATION_URLS instead. Refer to the README for an upgrade guide.")
|
"Using EMAIL_* keys for providing notification configuration has been deprecated and will be removed in the next major version.",
|
||||||
|
)
|
||||||
|
s.logger.Warn(
|
||||||
|
"Please use NOTIFICATION_URLS instead. Refer to the README for an upgrade guide.",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.c.NotificationURLs) > 0 {
|
notificationLevel := hookLevelFailure
|
||||||
s.hooks = append(s.hooks, hook{hookLevelFailure, func(err error, start time.Time, logOutput string) error {
|
if s.c.NotificationLevel == "always" {
|
||||||
sender, senderErr := shoutrrr.CreateSender(s.c.NotificationURLs...)
|
notificationLevel = hookLevelAlways
|
||||||
if senderErr != nil {
|
|
||||||
return fmt.Errorf("notifications: error creating sender: %w", senderErr)
|
|
||||||
}
|
|
||||||
body := fmt.Sprintf(
|
|
||||||
"Running docker-volume-backup failed with error: %s\n\nLog output of the failed run was:\n\n%s\n", err, logOutput,
|
|
||||||
)
|
|
||||||
|
|
||||||
results := sender.Send(body, &sTypes.Params{
|
|
||||||
"title": fmt.Sprintf(
|
|
||||||
"Failure running docker-volume-backup at %s", start.Format(time.RFC3339),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
var errs []error
|
|
||||||
for _, result := range results {
|
|
||||||
if result != nil {
|
|
||||||
errs = append(errs, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) != 0 {
|
|
||||||
return fmt.Errorf("notifications: error sending message: %w", join(errs...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}})
|
|
||||||
}
|
}
|
||||||
|
s.registerHook(notificationLevel, s.sendNotification)
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@ -247,10 +229,47 @@ func newScript() (*script, error) {
|
|||||||
var noop = func() error { return nil }
|
var noop = func() error { return nil }
|
||||||
|
|
||||||
// registerHook adds the given action at the given level.
|
// registerHook adds the given action at the given level.
|
||||||
func (s *script) registerHook(level hookLevel, action func(err error, start time.Time, logOutput string) error) {
|
func (s *script) registerHook(level hookLevel, action func(err error) error) {
|
||||||
s.hooks = append(s.hooks, hook{level, action})
|
s.hooks = append(s.hooks, hook{level, action})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendNotification sends a notification to third party services, containing
|
||||||
|
// information about the result of a backup run
|
||||||
|
func (s *script) sendNotification(err error) error {
|
||||||
|
if len(s.c.NotificationURLs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sender, senderErr := shoutrrr.CreateSender(s.c.NotificationURLs...)
|
||||||
|
if senderErr != nil {
|
||||||
|
return fmt.Errorf("notifications: error creating sender: %w", senderErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body, title string
|
||||||
|
if err != nil {
|
||||||
|
body = fmt.Sprintf(
|
||||||
|
"Running docker-volume-backup failed with error: %s\n\nLog output of the failed run was:\n\n%s\n", err, s.output.String(),
|
||||||
|
)
|
||||||
|
title = fmt.Sprintf("Failure running docker-volume-backup at %s", s.start.Format(time.RFC3339))
|
||||||
|
} else {
|
||||||
|
body = fmt.Sprintf(
|
||||||
|
"Running docker-volume-backup succeeded.\n\nLog output was:\n\n%s\n", s.output.String(),
|
||||||
|
)
|
||||||
|
title = fmt.Sprintf("Success running docker-volume-backup at %s", s.start.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for _, result := range sender.Send(body, &sTypes.Params{"title": title}) {
|
||||||
|
if result != nil {
|
||||||
|
errs = append(errs, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return fmt.Errorf("notifications: error sending message: %w", join(errs...))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// stopContainers stops all Docker containers that are marked as to being
|
// stopContainers stops all Docker containers that are marked as to being
|
||||||
// stopped during the backup and returns a function that can be called to
|
// stopped during the backup and returns a function that can be called to
|
||||||
// restart everything that has been stopped.
|
// restart everything that has been stopped.
|
||||||
@ -373,7 +392,7 @@ func (s *script) takeBackup() error {
|
|||||||
if s.c.BackupFromSnapshot {
|
if s.c.BackupFromSnapshot {
|
||||||
backupSources = filepath.Join("/tmp", s.c.BackupSources)
|
backupSources = filepath.Join("/tmp", s.c.BackupSources)
|
||||||
// copy before compressing guard against a situation where backup folder's content are still growing.
|
// copy before compressing guard against a situation where backup folder's content are still growing.
|
||||||
s.registerHook(hookLevelCleanup, func(error, time.Time, string) error {
|
s.registerHook(hookLevelCleanup, func(error) error {
|
||||||
if err := remove(backupSources); err != nil {
|
if err := remove(backupSources); err != nil {
|
||||||
return fmt.Errorf("takeBackup: error removing snapshot: %w", err)
|
return fmt.Errorf("takeBackup: error removing snapshot: %w", err)
|
||||||
}
|
}
|
||||||
@ -390,7 +409,7 @@ func (s *script) takeBackup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tarFile := s.file
|
tarFile := s.file
|
||||||
s.registerHook(hookLevelCleanup, func(error, time.Time, string) error {
|
s.registerHook(hookLevelCleanup, func(error) error {
|
||||||
if err := remove(tarFile); err != nil {
|
if err := remove(tarFile); err != nil {
|
||||||
return fmt.Errorf("takeBackup: error removing tar file: %w", err)
|
return fmt.Errorf("takeBackup: error removing tar file: %w", err)
|
||||||
}
|
}
|
||||||
@ -414,7 +433,7 @@ func (s *script) encryptBackup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gpgFile := fmt.Sprintf("%s.gpg", s.file)
|
gpgFile := fmt.Sprintf("%s.gpg", s.file)
|
||||||
s.registerHook(hookLevelCleanup, func(error, time.Time, string) error {
|
s.registerHook(hookLevelCleanup, func(error) error {
|
||||||
if err := remove(gpgFile); err != nil {
|
if err := remove(gpgFile); err != nil {
|
||||||
return fmt.Errorf("encryptBackup: error removing gpg file: %w", err)
|
return fmt.Errorf("encryptBackup: error removing gpg file: %w", err)
|
||||||
}
|
}
|
||||||
@ -648,7 +667,7 @@ func (s *script) runHooks(err error, levels ...hookLevel) error {
|
|||||||
if hook.level != level {
|
if hook.level != level {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if actionErr := hook.action(err, s.start, s.output.String()); actionErr != nil {
|
if actionErr := hook.action(err); actionErr != nil {
|
||||||
actionErrors = append(actionErrors, fmt.Errorf("runHooks: error running hook: %w", actionErr))
|
actionErrors = append(actionErrors, fmt.Errorf("runHooks: error running hook: %w", actionErr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -762,12 +781,13 @@ func (b *bufferingWriter) Write(p []byte) (n int, err error) {
|
|||||||
// reaches a certain point (e.g. unsuccessful backup)
|
// reaches a certain point (e.g. unsuccessful backup)
|
||||||
type hook struct {
|
type hook struct {
|
||||||
level hookLevel
|
level hookLevel
|
||||||
action func(err error, start time.Time, logOutput string) error
|
action func(err error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type hookLevel int
|
type hookLevel int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
hookLevelFailure hookLevel = iota
|
hookLevelFailure hookLevel = iota
|
||||||
|
hookLevelAlways
|
||||||
hookLevelCleanup
|
hookLevelCleanup
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user