Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
FROM golang:1.26-alpine AS builder

WORKDIR /app
COPY . .
COPY go.mod go.sum ./
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, this should go in a dedicated PR. Thank you.

RUN go mod download
COPY . .
WORKDIR /app/cmd/backup
RUN go build -o backup .

Expand Down
1 change: 1 addition & 0 deletions cmd/backup/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ type Config struct {
GoogleDriveImpersonateSubject string `split_words:"true"`
GoogleDriveEndpoint string `split_words:"true"`
GoogleDriveTokenURL string `split_words:"true"`
ActivateLazyRestart bool `split_words:"true" default:"false"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think naming this BACKUP_USE_LAZY_RESTART would be more in line with what's already there.

source string
additionalEnvVars map[string]string
}
Expand Down
8 changes: 6 additions & 2 deletions cmd/backup/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
"github.com/offen/docker-volume-backup/internal/errwrap"
)

const LOCK_FILE = "/var/lock/dockervolumebackup.lock"

// lock opens a lockfile at the given location, keeping it locked until the
// caller invokes the returned release func. In case the lock is currently blocked
// by another execution, it will repeatedly retry until the lock is available
// or the given timeout is exceeded.
func (s *script) lock(lockfile string) (func() error, error) {
func (s *script) lock() (func() error, error) {
start := time.Now()
defer func() {
s.stats.LockedTime = time.Since(start)
Expand All @@ -26,7 +28,8 @@
deadline := time.NewTimer(s.c.LockTimeout)
defer deadline.Stop()

fileLock := flock.New(lockfile)
fileLock := flock.New(LOCK_FILE)
defer s.removeWaitingFile()

Check failure on line 32 in cmd/backup/lock.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `s.removeWaitingFile` is not checked (errcheck)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused by the usage of lock and waiting file. These two lines in particular make me think they are the same, but somehow they aren't, no? Why is this defer not below, right after the waiting file has been written?


for {
acquired, err := fileLock.TryLock()
Expand All @@ -41,6 +44,7 @@
}

if !s.encounteredLock {
s.writeWaitingFile()

Check failure on line 47 in cmd/backup/lock.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `s.writeWaitingFile` is not checked (errcheck)
s.logger.Info(
fmt.Sprintf(
"Exclusive lock was not available on first attempt. Will retry until it becomes available or the timeout of %s is exceeded.",
Expand Down
22 changes: 11 additions & 11 deletions cmd/backup/run_script.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,6 @@ func runScript(c *Config) (err error) {

s := newScript(c)

unlock, lockErr := s.lock("/var/lock/dockervolumebackup.lock")
if lockErr != nil {
err = errwrap.Wrap(lockErr, "error acquiring file lock")
return
}
defer func() {
if derr := unlock(); derr != nil {
err = errors.Join(err, errwrap.Wrap(derr, "error releasing file lock"))
}
}()

unset, warnings, err := s.c.resolve()
if err != nil {
return errwrap.Wrap(err, "error applying env")
Expand Down Expand Up @@ -81,6 +70,17 @@ func runScript(c *Config) (err error) {
return
}

unlock, lockErr := s.lock()
if lockErr != nil {
err = errwrap.Wrap(lockErr, "error acquiring file lock")
return
}
defer func() {
if derr := unlock(); derr != nil {
err = errors.Join(err, errwrap.Wrap(derr, "error releasing file lock"))
}
}()

err = func() (err error) {
scriptErr := func() error {
if err := s.withLabeledCommands(lifecyclePhaseArchive, func() (err error) {
Expand Down
17 changes: 17 additions & 0 deletions cmd/backup/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"text/template"
"time"

"github.com/google/uuid"
"github.com/offen/docker-volume-backup/internal/errwrap"
"github.com/offen/docker-volume-backup/internal/storage"
"github.com/offen/docker-volume-backup/internal/storage/azure"
Expand All @@ -30,6 +31,8 @@ import (
// script holds all the stateful information required to orchestrate a
// single backup run.
type script struct {
id string

cli *client.Client
storages []storage.Backend
logger *slog.Logger
Expand All @@ -43,6 +46,10 @@ type script struct {

encounteredLock bool

isSwarm bool
containersToStop []handledContainer
servicesToScaleDown []handledSwarmService

c *Config
}

Expand Down Expand Up @@ -72,6 +79,8 @@ func newScript(c *Config) *script {
}

func (s *script) init() error {
s.id = uuid.New().String()

s.registerHook(hookLevelPlumbing, func(error) error {
s.stats.EndTime = time.Now()
s.stats.TookTime = s.stats.EndTime.Sub(s.stats.StartTime)
Expand Down Expand Up @@ -135,6 +144,14 @@ func (s *script) init() error {
}
return nil
})
s.isSwarm, err = isSwarm(s.cli)
if err != nil {
return errwrap.Wrap(err, "error determining swarm state")
}
err = s.determineContainersAndServicesToStop()
if err != nil {
return errwrap.Wrap(err, "error determining containers and services to stop")
}
}

logFunc := func(logType storage.LogLevel, context string, msg string, params ...any) {
Expand Down
Loading
Loading