diff --git a/cmd/backup/config.go b/cmd/backup/config.go index 864ff114..5510bb26 100644 --- a/cmd/backup/config.go +++ b/cmd/backup/config.go @@ -4,12 +4,15 @@ package main import ( + "bytes" "crypto/x509" "encoding/pem" "fmt" "os" "regexp" "strconv" + "strings" + "text/template" "time" "github.com/offen/docker-volume-backup/internal/errwrap" @@ -225,3 +228,92 @@ func (c *Config) applyEnv() (func() error, error) { } return unset, nil } + +// resolve is responsible for performing all implicit logic that transforms a configuration object +// into what is actually being used at runtime. E.g. environment variables are expanded or +// deprecated config options are transposed into their up to date successors. The caller is +// responsible for calling the returned reset function after usage of the config is done. +func (c *Config) resolve() (reset func() error, warnings []string, err error) { + reset, aErr := c.applyEnv() + if aErr != nil { + err = errwrap.Wrap(aErr, "error applying env") + return + } + + if c.BackupFilenameExpand { + c.BackupFilename = os.ExpandEnv(c.BackupFilename) + c.BackupLatestSymlink = os.ExpandEnv(c.BackupLatestSymlink) + c.BackupPruningPrefix = os.ExpandEnv(c.BackupPruningPrefix) + } + + if c.EmailNotificationRecipient != "" { + emailURL := fmt.Sprintf( + "smtp://%s:%s@%s:%d/?from=%s&to=%s", + c.EmailSMTPUsername, + c.EmailSMTPPassword, + c.EmailSMTPHost, + c.EmailSMTPPort, + c.EmailNotificationSender, + c.EmailNotificationRecipient, + ) + c.NotificationURLs = append(c.NotificationURLs, emailURL) + warnings = append(warnings, + "Using EMAIL_* keys for providing notification configuration has been deprecated and will be removed in the next major version.", + "Please use NOTIFICATION_URLS instead. Refer to the README for an upgrade guide.", + ) + } + + if c.BackupFromSnapshot { + warnings = append(warnings, + "Using BACKUP_FROM_SNAPSHOT has been deprecated and will be removed in the next major version.", + "Please use `archive-pre` and `archive-post` commands to prepare your backup sources. Refer to the documentation for an upgrade guide.", + ) + } + + if c.BackupStopDuringBackupLabel != "" && c.BackupStopContainerLabel != "" { + err = errwrap.Wrap(nil, "both BACKUP_STOP_DURING_BACKUP_LABEL and BACKUP_STOP_CONTAINER_LABEL have been set, cannot continue") + return + } + if c.BackupStopContainerLabel != "" { + warnings = append(warnings, + "Using BACKUP_STOP_CONTAINER_LABEL has been deprecated and will be removed in the next major version.", + "Please use BACKUP_STOP_DURING_BACKUP_LABEL instead. Refer to the docs for an upgrade guide.", + ) + c.BackupStopDuringBackupLabel = c.BackupStopContainerLabel + } + + tmplFileName, tErr := template.New("extension").Parse(c.BackupFilename) + if tErr != nil { + err = errwrap.Wrap(tErr, "unable to parse backup file extension template") + return + } + + var bf bytes.Buffer + if tErr := tmplFileName.Execute(&bf, map[string]string{ + "Extension": func() string { + if c.BackupCompression == "none" { + return "tar" + } + return fmt.Sprintf("tar.%s", c.BackupCompression) + }(), + }); tErr != nil { + err = errwrap.Wrap(tErr, "error executing backup file extension template") + return + } + c.BackupFilename = bf.String() + + if c.AzureStorageEndpoint != "" { + endpointTemplate, tErr := template.New("endpoint").Parse(c.AzureStorageEndpoint) + if tErr != nil { + err = errwrap.Wrap(tErr, "error parsing endpoint template") + return + } + var ep bytes.Buffer + if tErr := endpointTemplate.Execute(&ep, map[string]string{"AccountName": c.AzureStorageAccountName}); tErr != nil { + err = errwrap.Wrap(tErr, "error executing endpoint template") + return + } + c.AzureStorageEndpoint = fmt.Sprintf("%s/", strings.TrimSuffix(ep.String(), "/")) + } + return +} diff --git a/cmd/backup/create_archive.go b/cmd/backup/create_archive.go index 1b6aad25..bbc18e0b 100644 --- a/cmd/backup/create_archive.go +++ b/cmd/backup/create_archive.go @@ -18,12 +18,6 @@ func (s *script) createArchive() error { backupSources := s.c.BackupSources if s.c.BackupFromSnapshot { - s.logger.Warn( - "Using BACKUP_FROM_SNAPSHOT has been deprecated and will be removed in the next major version.", - ) - s.logger.Warn( - "Please use `archive-pre` and `archive-post` commands to prepare your backup sources. Refer to the documentation for an upgrade guide.", - ) backupSources = filepath.Join("/tmp", s.c.BackupSources) // copy before compressing guard against a situation where backup folder's content are still growing. s.registerHook(hookLevelPlumbing, func(error) error { diff --git a/cmd/backup/run_script.go b/cmd/backup/run_script.go index 5ff4b700..6e40b6e1 100644 --- a/cmd/backup/run_script.go +++ b/cmd/backup/run_script.go @@ -43,7 +43,7 @@ func runScript(c *Config) (err error) { } }() - unset, err := s.c.applyEnv() + unset, warnings, err := s.c.resolve() if err != nil { return errwrap.Wrap(err, "error applying env") } @@ -52,6 +52,9 @@ func runScript(c *Config) (err error) { err = errors.Join(err, errwrap.Wrap(derr, "error unsetting environment variables")) } }() + for _, w := range warnings { + s.logger.Warn(w) + } if s.c != nil && s.c.BackupJitter > 0 { max := s.c.BackupJitter diff --git a/cmd/backup/script.go b/cmd/backup/script.go index 123c8fcf..c82bfd50 100644 --- a/cmd/backup/script.go +++ b/cmd/backup/script.go @@ -4,7 +4,6 @@ package main import ( - "bytes" "fmt" "log/slog" "os" @@ -79,24 +78,6 @@ func (s *script) init() error { return nil }) // Register notifications first so they can fire in case of other init errors. - if s.c.EmailNotificationRecipient != "" { - emailURL := fmt.Sprintf( - "smtp://%s:%s@%s:%d/?from=%s&to=%s", - s.c.EmailSMTPUsername, - s.c.EmailSMTPPassword, - s.c.EmailSMTPHost, - s.c.EmailSMTPPort, - s.c.EmailNotificationSender, - s.c.EmailNotificationRecipient, - ) - 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( - "Please use NOTIFICATION_URLS instead. Refer to the README for an upgrade guide.", - ) - } hookLevel, ok := hookLevels[s.c.NotificationLevel] if !ok { @@ -143,30 +124,6 @@ func (s *script) init() error { } s.file = path.Join("/tmp", s.c.BackupFilename) - - tmplFileName, tErr := template.New("extension").Parse(s.file) - if tErr != nil { - return errwrap.Wrap(tErr, "unable to parse backup file extension template") - } - - var bf bytes.Buffer - if tErr := tmplFileName.Execute(&bf, map[string]string{ - "Extension": func() string { - if s.c.BackupCompression == "none" { - return "tar" - } - return fmt.Sprintf("tar.%s", s.c.BackupCompression) - }(), - }); tErr != nil { - return errwrap.Wrap(tErr, "error executing backup file extension template") - } - s.file = bf.String() - - if s.c.BackupFilenameExpand { - s.file = os.ExpandEnv(s.file) - s.c.BackupLatestSymlink = os.ExpandEnv(s.c.BackupLatestSymlink) - s.c.BackupPruningPrefix = os.ExpandEnv(s.c.BackupPruningPrefix) - } s.file = timeutil.Strftime(&s.stats.StartTime, s.file) _, err := os.Stat("/var/run/docker.sock") diff --git a/cmd/backup/stop_restart.go b/cmd/backup/stop_restart.go index cb903f71..d774a35d 100644 --- a/cmd/backup/stop_restart.go +++ b/cmd/backup/stop_restart.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "os" "sync" "time" @@ -113,23 +112,9 @@ func (s *script) stopContainersAndServices() (func() error, error) { return noop, errwrap.Wrap(err, "error determining swarm state") } - labelValue := s.c.BackupStopDuringBackupLabel - if s.c.BackupStopContainerLabel != "" { - s.logger.Warn( - "Using BACKUP_STOP_CONTAINER_LABEL has been deprecated and will be removed in the next major version.", - ) - s.logger.Warn( - "Please use BACKUP_STOP_DURING_BACKUP_LABEL instead. Refer to the docs for an upgrade guide.", - ) - if _, ok := os.LookupEnv("BACKUP_STOP_DURING_BACKUP_LABEL"); ok { - return noop, errwrap.Wrap(nil, "both BACKUP_STOP_DURING_BACKUP_LABEL and BACKUP_STOP_CONTAINER_LABEL have been set, cannot continue") - } - labelValue = s.c.BackupStopContainerLabel - } - stopDuringBackupLabel := fmt.Sprintf( "docker-volume-backup.stop-during-backup=%s", - labelValue, + s.c.BackupStopDuringBackupLabel, ) stopDuringBackupNoRestartLabel := fmt.Sprintf( @@ -144,7 +129,7 @@ func (s *script) stopContainersAndServices() (func() error, error) { var containersToStop []handledContainer for _, c := range allContainers.Items { - hasStopDuringBackupLabel, hasStopDuringBackupNoRestartLabel, err := checkStopLabels(c.Labels, labelValue, s.c.BackupStopDuringBackupNoRestartLabel) + hasStopDuringBackupLabel, hasStopDuringBackupNoRestartLabel, err := checkStopLabels(c.Labels, s.c.BackupStopDuringBackupLabel, s.c.BackupStopDuringBackupNoRestartLabel) if err != nil { return noop, errwrap.Wrap(err, "error querying for containers to stop") } @@ -169,7 +154,7 @@ func (s *script) stopContainersAndServices() (func() error, error) { } for _, service := range allServices { - hasStopDuringBackupLabel, hasStopDuringBackupNoRestartLabel, err := checkStopLabels(service.Spec.Labels, labelValue, s.c.BackupStopDuringBackupNoRestartLabel) + hasStopDuringBackupLabel, hasStopDuringBackupNoRestartLabel, err := checkStopLabels(service.Spec.Labels, s.c.BackupStopDuringBackupLabel, s.c.BackupStopDuringBackupNoRestartLabel) if err != nil { return noop, errwrap.Wrap(err, "error querying for services to scale down") } diff --git a/internal/storage/azure/azure.go b/internal/storage/azure/azure.go index 6d7dde3f..21d0171a 100644 --- a/internal/storage/azure/azure.go +++ b/internal/storage/azure/azure.go @@ -4,16 +4,13 @@ package azure import ( - "bytes" "context" "errors" "fmt" "os" "path" "path/filepath" - "strings" "sync" - "text/template" "time" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" @@ -49,16 +46,6 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error return nil, errwrap.Wrap(nil, "using primary account key and connection string are mutually exclusive") } - endpointTemplate, err := template.New("endpoint").Parse(opts.Endpoint) - if err != nil { - return nil, errwrap.Wrap(err, "error parsing endpoint template") - } - var ep bytes.Buffer - if err := endpointTemplate.Execute(&ep, opts); err != nil { - return nil, errwrap.Wrap(err, "error executing endpoint template") - } - normalizedEndpoint := fmt.Sprintf("%s/", strings.TrimSuffix(ep.String(), "/")) - var client *azblob.Client if opts.PrimaryAccountKey != "" { cred, err := azblob.NewSharedKeyCredential(opts.AccountName, opts.PrimaryAccountKey) @@ -66,11 +53,12 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error return nil, errwrap.Wrap(err, "error creating shared key Azure credential") } - client, err = azblob.NewClientWithSharedKeyCredential(normalizedEndpoint, cred, nil) + client, err = azblob.NewClientWithSharedKeyCredential(opts.Endpoint, cred, nil) if err != nil { return nil, errwrap.Wrap(err, "error creating azure client from primary account key") } } else if opts.ConnectionString != "" { + var err error client, err = azblob.NewClientFromConnectionString(opts.ConnectionString, nil) if err != nil { return nil, errwrap.Wrap(err, "error creating azure client from connection string") @@ -80,7 +68,7 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error if err != nil { return nil, errwrap.Wrap(err, "error creating managed identity credential") } - client, err = azblob.NewClient(normalizedEndpoint, cred, nil) + client, err = azblob.NewClient(opts.Endpoint, cred, nil) if err != nil { return nil, errwrap.Wrap(err, "error creating azure client from managed identity") }