Refactor calling of hooks on exit

This commit is contained in:
Frederik Ring 2021-12-18 09:56:05 +01:00
parent 1f727f698f
commit 4207146fb6

View File

@ -17,6 +17,7 @@ import (
"time" "time"
"github.com/containrrr/shoutrrr" "github.com/containrrr/shoutrrr"
"github.com/containrrr/shoutrrr/pkg/router"
sTypes "github.com/containrrr/shoutrrr/pkg/types" sTypes "github.com/containrrr/shoutrrr/pkg/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@ -45,7 +46,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, hookLevelError); hookErr != nil { if hookErr := s.runHooks(err); 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)
@ -53,7 +54,7 @@ func main() {
panic(pArg) panic(pArg)
} }
if err := s.runHooks(nil, hookLevelInfo); err != nil { if err := s.runHooks(nil); 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,
@ -88,7 +89,9 @@ type script struct {
cli *client.Client cli *client.Client
mc *minio.Client mc *minio.Client
logger *logrus.Logger logger *logrus.Logger
sender *router.ServiceRouter
hooks []hook hooks []hook
hookLevel hookLevel
start time.Time start time.Time
file string file string
@ -218,11 +221,33 @@ func newScript() (*script, error) {
) )
} }
notificationLevel, ok := notificationLevels[s.c.NotificationLevel] hookLevel, ok := hookLevels[s.c.NotificationLevel]
if !ok { if !ok {
return nil, fmt.Errorf("newScript: unknown NOTIFICATION_LEVEL %s", s.c.NotificationLevel) return nil, fmt.Errorf("newScript: unknown NOTIFICATION_LEVEL %s", s.c.NotificationLevel)
} }
s.registerHook(notificationLevel, s.sendNotification) s.hookLevel = hookLevel
if len(s.c.NotificationURLs) > 0 {
sender, senderErr := shoutrrr.CreateSender(s.c.NotificationURLs...)
if senderErr != nil {
return nil, fmt.Errorf("newScript: error creating sender: %w", senderErr)
}
s.sender = sender
// To prevent duplicate notifications, ensure the regsistered callbacks
// run mutually exclusive.
s.registerHook(hookLevelError, func(err error) error {
if err == nil {
return nil
}
return s.notifyFailure(err)
})
s.registerHook(hookLevelInfo, func(err error) error {
if err != nil {
return nil
}
return s.notifySuccess()
})
}
return s, nil return s, nil
} }
@ -234,39 +259,40 @@ 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 // notifyFailure sends a notification about a failed backup run
// information about the result of a backup run func (s *script) notifyFailure(err error) error {
func (s *script) sendNotification(err error) error { body := fmt.Sprintf(
if len(s.c.NotificationURLs) == 0 { "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))
if err := s.sendNotification(title, body); err != nil {
return fmt.Errorf("notifyFailure: error notifying: %w", err)
}
return nil return nil
} }
sender, senderErr := shoutrrr.CreateSender(s.c.NotificationURLs...) // notifyFailure sends a notification about a successful backup run
if senderErr != nil { func (s *script) notifySuccess() error {
return fmt.Errorf("notifications: error creating sender: %w", senderErr) title := fmt.Sprintf("Success running docker-volume-backup at %s", s.start.Format(time.RFC3339))
} body := fmt.Sprintf(
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(), "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)) if err := s.sendNotification(title, body); err != nil {
return fmt.Errorf("notifySuccess: error notifying: %w", err)
}
return nil
} }
// sendNotification sends a notification to all configured third party services
func (s *script) sendNotification(title, body string) error {
var errs []error var errs []error
for _, result := range sender.Send(body, &sTypes.Params{"title": title}) { for _, result := range s.sender.Send(body, &sTypes.Params{"title": title}) {
if result != nil { if result != nil {
errs = append(errs, result) errs = append(errs, result)
} }
} }
if len(errs) != 0 { if len(errs) != 0 {
return fmt.Errorf("notifications: error sending message: %w", join(errs...)) return fmt.Errorf("sendNotification: error sending message: %w", join(errs...))
} }
return nil return nil
} }
@ -661,13 +687,13 @@ func (s *script) pruneOldBackups() error {
// runHooks runs all hooks that have been registered using the // runHooks runs all hooks that have been registered using the
// given levels in the defined ordering. In case executing a hook returns an // given levels in the defined ordering. In case executing a hook returns an
// error, the following hooks will still be run before the function returns. // error, the following hooks will still be run before the function returns.
func (s *script) runHooks(err error, level hookLevel) error { func (s *script) runHooks(err error) error {
sort.SliceStable(s.hooks, func(i, j int) bool { sort.SliceStable(s.hooks, func(i, j int) bool {
return s.hooks[i].level < s.hooks[j].level return s.hooks[i].level < s.hooks[j].level
}) })
var actionErrors []error var actionErrors []error
for _, hook := range s.hooks { for _, hook := range s.hooks {
if hook.level > level { if hook.level > s.hookLevel {
continue continue
} }
if actionErr := hook.action(err); actionErr != nil { if actionErr := hook.action(err); actionErr != nil {
@ -790,11 +816,11 @@ type hookLevel int
const ( const (
hookLevelPlumbing hookLevel = iota hookLevelPlumbing hookLevel = iota
hookLevelInfo
hookLevelError hookLevelError
hookLevelInfo
) )
var notificationLevels = map[string]hookLevel{ var hookLevels = map[string]hookLevel{
"info": hookLevelInfo, "info": hookLevelInfo,
"error": hookLevelError, "error": hookLevelError,
} }