Compare commits

...

18 Commits

Author SHA1 Message Date
Lennard
3d8960a351 Add "print-config" subcommand (#698)
* Add "show-config" subcommand

* Add integration tests

* Handle wrong additional args

* Support "BackupFilenameExpand" option

* Rename to print-config

* Remove config nil check as it will never be nil

* Rework print config to work with config.resolve()

* Use defer unset()

* Add warning about feature not being stable yet
2026-01-15 12:29:09 +01:00
Lennard
78994f5acd Update copyright notices to 2026 (#708) 2026-01-15 12:28:51 +01:00
dependabot[bot]
8e7738e1c2 Bump github.com/docker/cli (#707)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 29.1.3+incompatible to 29.1.4+incompatible.
- [Commits](https://github.com/docker/cli/compare/v29.1.3...v29.1.4)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 29.1.4+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 08:03:56 +01:00
dependabot[bot]
4531c60429 Bump google.golang.org/api from 0.258.0 to 0.259.0 (#706)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.258.0 to 0.259.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.258.0...v0.259.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-version: 0.259.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-07 19:06:55 +01:00
Frederik Ring
8b6585ad30 Consolidate config value resolution at top level (#705)
* Consolidate env expansion at top level

* Move handling of deprecated config into resolve method

* Handle template interpolation in resolve method

* Clean up and document resolve function
2026-01-04 21:03:43 +01:00
dependabot[bot]
f9f89050d7 Bump filippo.io/age from 1.2.1 to 1.3.1 (#704)
Bumps [filippo.io/age](https://github.com/FiloSottile/age) from 1.2.1 to 1.3.1.
- [Release notes](https://github.com/FiloSottile/age/releases)
- [Commits](https://github.com/FiloSottile/age/compare/v1.2.1...v1.3.1)

---
updated-dependencies:
- dependency-name: filippo.io/age
  dependency-version: 1.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 22:29:07 +01:00
Lennard
80c2c13a8b Use isolated docker config in test sandbox (#700)
* Introduce DOCKER_CONFIG_FILE in test-suite

* Set DOCKER_CONFIG_FILE in CI

---------

Co-authored-by: Frederik Ring <frederik.ring@gmail.com>
2025-12-28 17:58:59 +01:00
Lennard
9bfcb25cd9 make test suite work on macos (#702) 2025-12-23 13:57:52 +01:00
dependabot[bot]
40c043ec23 Bump google.golang.org/api from 0.257.0 to 0.258.0 (#701)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.257.0 to 0.258.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.257.0...v0.258.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-version: 0.258.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-23 07:00:00 +01:00
Lennard
2938b6c1b0 Fix: send failure notifications for config init errors (#697)
* Register notification hooks first in init to allow sending .init() failure notifications

* Error from run_script is not consistently passed to deferred functions

---------

Co-authored-by: Frederik Ring <frederik.ring@gmail.com>
2025-12-22 19:41:19 +01:00
dependabot[bot]
16677e631e Bump github.com/docker/cli (#695)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 29.1.2+incompatible to 29.1.3+incompatible.
- [Commits](https://github.com/docker/cli/compare/v29.1.2...v29.1.3)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 29.1.3+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 07:35:32 +01:00
dependabot[bot]
fa8a0a4bcd Bump google.golang.org/api from 0.256.0 to 0.257.0 (#692)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.256.0 to 0.257.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.256.0...v0.257.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-version: 0.257.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 11:30:55 +01:00
dependabot[bot]
9cb7e7c69d Bump golang.org/x/oauth2 from 0.33.0 to 0.34.0 (#691)
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.33.0 to 0.34.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.33.0...v0.34.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 11:30:39 +01:00
dependabot[bot]
ea1857ba49 Bump github.com/nicholas-fedor/shoutrrr from 0.12.1 to 0.13.0 (#690)
Bumps [github.com/nicholas-fedor/shoutrrr](https://github.com/nicholas-fedor/shoutrrr) from 0.12.1 to 0.13.0.
- [Release notes](https://github.com/nicholas-fedor/shoutrrr/releases)
- [Commits](https://github.com/nicholas-fedor/shoutrrr/compare/v0.12.1...v0.13.0)

---
updated-dependencies:
- dependency-name: github.com/nicholas-fedor/shoutrrr
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 11:30:22 +01:00
dependabot[bot]
7f3fcd6c1a Bump golang.org/x/sync from 0.18.0 to 0.19.0 (#689)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.18.0 to 0.19.0.
- [Commits](https://github.com/golang/sync/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 10:57:10 +01:00
dependabot[bot]
a5046b9678 Bump github.com/docker/cli (#688)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 29.1.1+incompatible to 29.1.2+incompatible.
- [Commits](https://github.com/docker/cli/compare/v29.1.1...v29.1.2)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 29.1.2+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 10:56:52 +01:00
dependabot[bot]
ffe608aec3 Bump alpine from 3.22 to 3.23 (#693)
Bumps alpine from 3.22 to 3.23.

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: '3.23'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 10:56:35 +01:00
Frederik Ring
9213a4db55 Prevent Docker Hub login when secrets are not available (#694) 2025-12-09 10:17:44 +01:00
25 changed files with 435 additions and 205 deletions

View File

@@ -14,6 +14,7 @@ jobs:
- name: Log in to Docker Hub
uses: docker/login-action@v2
continue-on-error: true
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
@@ -22,6 +23,8 @@ jobs:
uses: docker/setup-buildx-action@v2
- name: Run Tests
env:
DOCKER_CONFIG_FILE: /home/runner/.docker/config.json
working-directory: ./test
run: |
BUILD_IMAGE=1 ./test.sh

View File

@@ -9,7 +9,7 @@ RUN go mod download
WORKDIR /app/cmd/backup
RUN go build -o backup .
FROM alpine:3.22
FROM alpine:3.23
WORKDIR /root

View File

@@ -82,5 +82,5 @@ In case your are looking for help or guidance on how to incorporate docker-volum
---
Copyright &copy; 2024 <a target="_blank" href="https://www.offen.software">offen.software</a> and contributors.
Copyright &copy; 2026 <a target="_blank" href="https://www.offen.software">offen.software</a> and contributors.
Distributed under the <a href="https://github.com/offen/docker-volume-backup/tree/main/LICENSE">MPL-2.0 License</a>.

View File

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

View File

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

View File

@@ -11,8 +11,18 @@ func main() {
foreground := flag.Bool("foreground", false, "run the tool in the foreground")
profile := flag.String("profile", "", "collect runtime metrics and log them periodically on the given cron expression")
flag.Parse()
additionalArgs := flag.Args()
c := newCommand()
if len(additionalArgs) > 0 {
switch additionalArgs[0] {
case "print-config":
c.must(runPrintConfig())
return
default:
panic("unknown command: " + additionalArgs[0])
}
}
if *foreground {
opts := foregroundOpts{
profileCronExpression: *profile,

View File

@@ -0,0 +1,47 @@
// Copyright 2025 - offen.software <hioffen@posteo.de>
// SPDX-License-Identifier: MPL-2.0
package main
import (
"errors"
"fmt"
"regexp"
"github.com/offen/docker-volume-backup/internal/errwrap"
)
func runPrintConfig() error {
configurations, err := sourceConfiguration(configStrategyConfd)
if err != nil {
return errwrap.Wrap(err, "error sourcing configuration")
}
formatter := regexp.MustCompile(`\s([A-Z])`)
for _, config := range configurations {
if err := func() (err error) {
unset, warnings, err := config.resolve()
if err != nil {
return errwrap.Wrap(err, "error resolving configuration")
}
defer func() {
if derr := unset(); derr != nil {
err = errors.Join(err, errwrap.Wrap(derr, "error unsetting environment variables"))
}
}()
fmt.Printf("source=%s\n", config.source)
for _, warning := range warnings {
fmt.Printf("warning:%s\n", warning)
}
// insert line breaks before each field name, assuming field names start with uppercase letters
formatted := formatter.ReplaceAllString(fmt.Sprintf("%+v", *config), "\n$1")
fmt.Printf("%s\n", formatted)
return nil
}(); err != nil {
return err
}
}
return nil
}

View File

@@ -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
@@ -63,11 +66,22 @@ func runScript(c *Config) (err error) {
}
if initErr := s.init(); initErr != nil {
if hookErr := s.runHooks(initErr); hookErr != nil {
err = errwrap.Wrap(
nil,
fmt.Sprintf(
"error %v instantiating script followed by %v calling the registered hooks",
initErr,
hookErr,
),
)
return
}
err = errwrap.Wrap(initErr, "error instantiating script")
return
}
return func() (err error) {
err = func() (err error) {
scriptErr := func() error {
if err := s.withLabeledCommands(lifecyclePhaseArchive, func() (err error) {
restartContainersAndServices, err := s.stopContainersAndServices()
@@ -121,4 +135,6 @@ func runScript(c *Config) (err error) {
}
return nil
}()
return
}

View File

@@ -4,7 +4,6 @@
package main
import (
"bytes"
"fmt"
"log/slog"
"os"
@@ -78,32 +77,53 @@ func (s *script) init() error {
s.stats.TookTime = s.stats.EndTime.Sub(s.stats.StartTime)
return nil
})
// Register notifications first so they can fire in case of other init errors.
hookLevel, ok := hookLevels[s.c.NotificationLevel]
if !ok {
return errwrap.Wrap(nil, fmt.Sprintf("unknown NOTIFICATION_LEVEL %s", s.c.NotificationLevel))
}
s.hookLevel = hookLevel
if len(s.c.NotificationURLs) > 0 {
sender, senderErr := shoutrrr.CreateSender(s.c.NotificationURLs...)
if senderErr != nil {
return errwrap.Wrap(senderErr, "error creating sender")
}
s.sender = sender
tmpl := template.New("")
tmpl.Funcs(templateHelpers)
tmpl, err := tmpl.Parse(defaultNotifications)
if err != nil {
return errwrap.Wrap(err, "unable to parse default notifications templates")
}
if fi, err := os.Stat("/etc/dockervolumebackup/notifications.d"); err == nil && fi.IsDir() {
tmpl, err = tmpl.ParseGlob("/etc/dockervolumebackup/notifications.d/*.*")
if err != nil {
return errwrap.Wrap(err, "unable to parse user defined notifications templates")
}
}
s.template = tmpl
// 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()
})
}
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")
@@ -252,68 +272,5 @@ func (s *script) init() error {
s.storages = append(s.storages, googleDriveBackend)
}
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 {
return errwrap.Wrap(nil, fmt.Sprintf("unknown NOTIFICATION_LEVEL %s", s.c.NotificationLevel))
}
s.hookLevel = hookLevel
if len(s.c.NotificationURLs) > 0 {
sender, senderErr := shoutrrr.CreateSender(s.c.NotificationURLs...)
if senderErr != nil {
return errwrap.Wrap(senderErr, "error creating sender")
}
s.sender = sender
tmpl := template.New("")
tmpl.Funcs(templateHelpers)
tmpl, err = tmpl.Parse(defaultNotifications)
if err != nil {
return errwrap.Wrap(err, "unable to parse default notifications templates")
}
if fi, err := os.Stat("/etc/dockervolumebackup/notifications.d"); err == nil && fi.IsDir() {
tmpl, err = tmpl.ParseGlob("/etc/dockervolumebackup/notifications.d/*.*")
if err != nil {
return errwrap.Wrap(err, "unable to parse user defined notifications templates")
}
}
s.template = tmpl
// 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 nil
}

View File

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

View File

@@ -30,6 +30,6 @@ nav_external_links:
url: https://github.com/offen/docker-volume-backup
footer_content: >-
Copyright &copy; 2024 <a target="_blank" href="https://www.offen.software">offen.software</a> and contributors.
Copyright &copy; 2026 <a target="_blank" href="https://www.offen.software">offen.software</a> and contributors.
Distributed under the <a href="https://github.com/offen/docker-volume-backup/tree/main/LICENSE">MPL-2.0 License.</a><br>
Something missing, unclear or not working? Open <a href="https://github.com/offen/docker-volume-backup/issues">an issue</a>.

View File

@@ -0,0 +1,27 @@
---
title: Show loaded configuration
layout: default
parent: How Tos
nav_order: 8
---
# Print loaded configuration
You can print the configuration that `docker-volume-backup` has picked up without running a backup:
```console
docker exec <container_ref> backup print-config
```
If configuration sourcing fails, the error is printed to stdout to aid debugging.
If you want to test a one-off value, pass it directly:
```console
docker exec -e BACKUP_SOURCES=/backup -e NOTIFICATION_URLS=stdout:// <container_ref> backup print-config
```
{: .note }
Output includes secrets exactly as loaded.
{: .warning }
This feature is still in development and might change in future releases.

View File

@@ -19,6 +19,7 @@ You can work around this by either updating `docker-compose` or unquoting your c
You can populate below template according to your requirements and use it as your `env_file`.
The values for each key currently match its default.
If you need to confirm what the container actually loaded, see [Show loaded configuration](../how-tos/print-configuration.md).
{% raw %}
```

35
go.mod
View File

@@ -3,11 +3,11 @@ module github.com/offen/docker-volume-backup
go 1.25.3
require (
filippo.io/age v1.2.1
filippo.io/age v1.3.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3
github.com/cosiner/argv v0.1.0
github.com/docker/cli v29.1.1+incompatible
github.com/docker/cli v29.1.4+incompatible
github.com/gofrs/flock v0.13.0
github.com/joho/godotenv v1.5.1
github.com/klauspost/compress v1.18.2
@@ -15,24 +15,25 @@ require (
github.com/minio/minio-go/v7 v7.0.97
github.com/moby/moby/api v1.52.0
github.com/moby/moby/client v0.2.1
github.com/nicholas-fedor/shoutrrr v0.12.1
github.com/nicholas-fedor/shoutrrr v0.13.1
github.com/offen/envconfig v1.5.0
github.com/otiai10/copy v1.14.1
github.com/pkg/sftp v1.13.10
github.com/robfig/cron/v3 v3.0.1
github.com/studio-b12/gowebdav v0.11.0
golang.org/x/crypto v0.45.0
golang.org/x/oauth2 v0.33.0
golang.org/x/sync v0.18.0
google.golang.org/api v0.256.0
golang.org/x/crypto v0.46.0
golang.org/x/oauth2 v0.34.0
golang.org/x/sync v0.19.0
google.golang.org/api v0.259.0
mvdan.cc/sh/v3 v3.12.0
)
require (
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth v0.18.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
filippo.io/hpke v0.4.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
@@ -50,7 +51,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/googleapis/gax-go/v2 v2.16.0 // indirect
github.com/gorilla/mux v1.7.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
@@ -62,18 +63,18 @@ require (
github.com/prometheus/client_golang v1.11.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/tinylib/msgp v1.3.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
@@ -101,7 +102,7 @@ require (
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
)

102
go.sum
View File

@@ -1,5 +1,5 @@
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -15,8 +15,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
@@ -39,10 +39,12 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
filippo.io/age v1.3.1 h1:hbzdQOJkuaMEpRCLSN1/C5DX74RPcNCk6oqhKMXmZi0=
filippo.io/age v1.3.1/go.mod h1:EZorDTYUxt836i3zdori5IJX/v2Lj6kWFU0cfh6C0D4=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
@@ -106,8 +108,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v29.1.1+incompatible h1:gGQk5qx62yPKRm3bUdKBzmDBSQzp17hlSLbV1F7jjys=
github.com/docker/cli v29.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.1.4+incompatible h1:AI8fwZhqsAsrqZnVv9h6lbexeW/LzNTasf6A4vcNN8M=
github.com/docker/cli v29.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
@@ -217,8 +219,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAV
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
@@ -302,14 +304,14 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nicholas-fedor/shoutrrr v0.12.1 h1:8NjY+I3K7cGHy89ncnaPGUA0ex44XbYK3SAFJX9YMI8=
github.com/nicholas-fedor/shoutrrr v0.12.1/go.mod h1:64qWuPpvTUv9ZppEoR6OdroiFmgf9w11YSaR0h9KZGg=
github.com/nicholas-fedor/shoutrrr v0.13.1 h1:llEoHNbnMM4GfQ9+2Ns3n6ssvNfi3NPWluM0AQiicoY=
github.com/nicholas-fedor/shoutrrr v0.13.1/go.mod h1:kU4cFJpEAtTzl3iV0l+XUXmM90OlC5T01b7roM4/pYM=
github.com/offen/envconfig v1.5.0 h1:LHL4wYIDVeoGxSDI40MShmWfss3gYUlCdstfSiSq4Fk=
github.com/offen/envconfig v1.5.0/go.mod h1:L7ny7R+4JWH3VVnZ+ARHvZysWUiZ2eQcm3L0imU9ACY=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8=
github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM=
github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -362,8 +364,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -386,8 +388,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
@@ -400,10 +402,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWS
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
@@ -416,8 +418,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -478,16 +480,16 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -497,8 +499,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -535,17 +537,17 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -615,8 +617,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ=
google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -652,12 +654,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -670,8 +672,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -683,8 +685,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

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

View File

@@ -41,6 +41,15 @@ IMAGE_TAG=v2.30.0 ./test.sh
By default, two local images are created that persist the image data and provide it to containers at runtime.
#### `DOCKER_CONFIG_FILE`
By default, the sandbox uses an empty Docker config file (`./docker-config.json`).
To use host credentials (e.g. in CI), set `DOCKER_CONFIG_FILE` to the desired `config.json` location:
```sh
DOCKER_CONFIG_FILE=${HOME}/.docker/config.json ./test.sh
```
## Understanding the test setup
The test setup runs each test case in an isolated Docker container, which itself is running an otherwise unused Docker daemon.

View File

@@ -10,7 +10,7 @@ services:
retries: 50
volumes:
- ./:/code
- ${HOME}/.docker/config.json:/root/.docker/config.json
- ${DOCKER_CONFIG_FILE:-./docker-config.json}:/root/.docker/config.json
- ${TARBALL:-.}:/cache/image.tar.gz
- docker_volume_backup_test_sandbox_image:/var/lib/docker/image
- docker_volume_backup_test_sandbox_containerd:/var/lib/docker/containerd
@@ -20,7 +20,7 @@ services:
hostname: worker1
volumes:
- ./:/code
- ${HOME}/.docker/config.json:/root/.docker/config.json
- ${DOCKER_CONFIG_FILE:-./docker-config.json}:/root/.docker/config.json
- ${TARBALL:-.}:/cache/image.tar.gz
- docker_volume_backup_test_sandbox_image:/var/lib/docker/image
- docker_volume_backup_test_sandbox_containerd_1:/var/lib/docker/containerd
@@ -31,7 +31,7 @@ services:
hostname: worker2
volumes:
- ./:/code
- ${HOME}/.docker/config.json:/root/.docker/config.json
- ${DOCKER_CONFIG_FILE:-./docker-config.json}:/root/.docker/config.json
- ${TARBALL:-.}:/cache/image.tar.gz
- docker_volume_backup_test_sandbox_image:/var/lib/docker/image
- docker_volume_backup_test_sandbox_containerd_2:/var/lib/docker/containerd

1
test/docker-config.json Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -46,3 +46,14 @@ if [ "$MESSAGE_BODY" != "Backing up /tmp/test.tar.gz succeeded." ]; then
fail "Unexpected notification body $MESSAGE_BODY"
fi
pass "Custom notification body was used."
NUM_MESSAGES_BEFORE=$(curl -sSL http://admin:custom@localhost:8080/message | jq -r '.messages | length')
docker compose exec -e AWS_S3_BUCKET_NAME=missing-bucket -e AWS_ACCESS_KEY_ID_FILE=/tmp/missing backup backup \
&& fail "Expected backup to fail due to missing AWS_ACCESS_KEY_ID_FILE."
pass "Backup failed with missing AWS_ACCESS_KEY_ID_FILE as expected."
NUM_MESSAGES_AFTER=$(curl -sSL http://admin:custom@localhost:8080/message | jq -r '.messages | length')
if [ "$NUM_MESSAGES_AFTER" != "$((NUM_MESSAGES_BEFORE + 1))" ]; then
fail "Expected one additional notification after failure, got $NUM_MESSAGES_AFTER total."
fi
pass "Failure notification was sent."

View File

@@ -0,0 +1,2 @@
BACKUP_SOURCES=/conf-backup
NOTIFICATION_URLS_FILE=/run/secrets/notification_urls

View File

@@ -0,0 +1,6 @@
services:
backup:
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
volumes:
- ${CONF_DIR}:/etc/dockervolumebackup/conf.d:ro
- ${SECRET_FILE}:/run/secrets/notification_urls:ro

View File

@@ -0,0 +1,10 @@
services:
backup:
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
environment:
BACKUP_FILENAME: "backup-$${BACKUP_TAG}.tar"
BACKUP_FILENAME_EXPAND: true
BACKUP_TAG: expanded
BACKUP_SOURCES: /backup
NOTIFICATION_URLS: "stdout://"
AWS_S3_BUCKET_NAME: example-bucket

68
test/print-config/run.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/bin/sh
set -e
cd "$(dirname "$0")"
. ../util.sh
current_test=$(basename $(pwd))
info "print-config with environment variables"
docker compose up -d --quiet-pull
logs=$(docker compose exec -T backup backup print-config)
echo "$logs"
if ! echo "$logs" | grep -q "source=from environment"; then
fail "Missing source line."
fi
pass "Source line present."
if ! echo "$logs" | grep -q "BackupSources:/backup"; then
fail "Missing BACKUP_SOURCES in output."
fi
pass "BACKUP_SOURCES present."
if ! echo "$logs" | grep -q "BackupFilename:backup-expanded.tar"; then
fail "Missing expanded BACKUP_FILENAME in output."
fi
pass "Expanded BACKUP_FILENAME present."
if ! echo "$logs" | grep -q "NotificationURLs:\[stdout://\]"; then
fail "Missing NOTIFICATION_URLS in output."
fi
pass "NOTIFICATION_URLS present."
if ! echo "$logs" | grep -q "AwsS3BucketName:example-bucket"; then
fail "Missing AWS_S3_BUCKET_NAME in output."
fi
pass "AWS_S3_BUCKET_NAME present."
docker compose down
info "print-config with conf.d and _FILE"
export CONF_DIR=$(pwd)/conf.d
export SECRET_FILE=$(mktemp)
printf "stdout://\n" > "$SECRET_FILE"
docker compose -f docker-compose.confd.yml up -d --quiet-pull
logs=$(docker compose -f docker-compose.confd.yml exec -T backup backup print-config)
echo "$logs"
if ! echo "$logs" | grep -q "source=01print-config.env"; then
fail "Missing conf.d source line."
fi
pass "conf.d source line present."
if ! echo "$logs" | grep -q "BackupSources:/conf-backup"; then
fail "Missing conf.d BACKUP_SOURCES in output."
fi
pass "conf.d BACKUP_SOURCES present."
if ! echo "$logs" | grep -q "NotificationURLs:\\[stdout://"; then
fail "Missing conf.d NOTIFICATION_URLS in output."
fi
pass "conf.d NOTIFICATION_URLS present."
docker compose -f docker-compose.confd.yml down
rm -f "$SECRET_FILE"

View File

@@ -29,7 +29,7 @@ if [ ! -z "$MATCH_PATTERN" ]; then
find_args="$find_args -name $MATCH_PATTERN"
fi
for dir in $(find $find_args | sort); do
for dir in $(find . $find_args | sort); do
dir=$(echo $dir | cut -c 3-)
echo "################################################"
echo "Now running ${dir}"
@@ -58,7 +58,7 @@ for dir in $(find $find_args | sort); do
docker exec $svc /bin/sh -c "docker load -i /cache/image.tar.gz"
done
for executable in $(find $dir -type f -executable | sort); do
for executable in $(find $dir -type f -perm -u+x | sort); do
context="manager"
if [ -f "$executable.context" ]; then
context=$(cat "$executable.context")