detached dbe3920

fix(review): address scheduler and backup service nitpicks

commit
dbe3920c3cbb0cce3b7a72798aea32af1655933d
author
brice · · 2026-02-08 10:07 UTC
parent
806527c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +26 -23
M internal/adapters/out/docker/runtime.go +2 -4
@@ -624,10 +624,8 @@ func (r *Runtime) PruneImages(ctx context.Context, danglingOnly bool) (runtimepk
624624 spaceReclaimed = uint64(math.MaxInt64)
625625 }
626626
627- spaceReclaimedInt, err := strconv.ParseInt(strconv.FormatUint(spaceReclaimed, 10), 10, 64)
628- if err != nil {
629- return runtimepkg.PruneReport{}, log.WrapErr(err, "failed to map space reclaimed to int64")
630- }
627+ //nolint:gosec // spaceReclaimed is capped to MaxInt64 above
628+ spaceReclaimedInt := int64(spaceReclaimed)
631629
632630 return runtimepkg.PruneReport{
633631 DeletedIDs: deletedIDs,
@@ -624,10 +624,8 @@ func (r *Runtime) PruneImages(ctx context.Context, danglingOnly bool) (runtimepk
624 spaceReclaimed = uint64(math.MaxInt64)624 spaceReclaimed = uint64(math.MaxInt64)
625 }625 }
626 626
627- spaceReclaimedInt, err := strconv.ParseInt(strconv.FormatUint(spaceReclaimed, 10), 10, 64)627+ //nolint:gosec // spaceReclaimed is capped to MaxInt64 above
628- if err != nil {628+ spaceReclaimedInt := int64(spaceReclaimed)
629- return runtimepkg.PruneReport{}, log.WrapErr(err, "failed to map space reclaimed to int64")
630- }
631 629
632 return runtimepkg.PruneReport{630 return runtimepkg.PruneReport{
633 DeletedIDs: deletedIDs,631 DeletedIDs: deletedIDs,
M internal/app/run.go +7 -13
@@ -1536,17 +1536,7 @@ func startBackupScheduler(ctx context.Context, cfg Config, svc *services, log ze
15361536 }
15371537
15381538 func resolveBackupSchedule(raw string) (domain.BackupSchedule, error) {
1539- schedule := domain.BackupSchedule(strings.ToLower(strings.TrimSpace(raw)))
1540- if schedule == "" {
1541- schedule = domain.ScheduleDaily
1542- }
1543-
1544- switch schedule {
1545- case domain.ScheduleHourly, domain.ScheduleDaily, domain.ScheduleWeekly, domain.ScheduleMonthly:
1546- return schedule, nil
1547- default:
1548- return "", fmt.Errorf("backups.schedule must be one of: hourly, daily, weekly, monthly")
1549- }
1539+ return resolveSchedulePreset(raw, "backups.schedule", domain.ScheduleDaily)
15501540 }
15511541
15521542 func startImagePruneScheduler(ctx context.Context, cfg Config, svc *services, log zerowrap.Logger, keepLastGetter func() int) (*cronSvc.Scheduler, error) {
@@ -1609,16 +1599,20 @@ func startImagePruneScheduler(ctx context.Context, cfg Config, svc *services, lo
16091599 }
16101600
16111601 func resolveImagePruneSchedule(raw string) (domain.BackupSchedule, error) {
1602+ return resolveSchedulePreset(raw, "images.prune.schedule", domain.ScheduleDaily)
1603+}
1604+
1605+func resolveSchedulePreset(raw, name string, defaultVal domain.BackupSchedule) (domain.BackupSchedule, error) {
16121606 schedule := domain.BackupSchedule(strings.ToLower(strings.TrimSpace(raw)))
16131607 if schedule == "" {
1614- schedule = domain.ScheduleDaily
1608+ schedule = defaultVal
16151609 }
16161610
16171611 switch schedule {
16181612 case domain.ScheduleHourly, domain.ScheduleDaily, domain.ScheduleWeekly, domain.ScheduleMonthly:
16191613 return schedule, nil
16201614 default:
1621- return "", fmt.Errorf("images.prune.schedule must be one of: hourly, daily, weekly, monthly")
1615+ return "", fmt.Errorf("%s must be one of: hourly, daily, weekly, monthly", name)
16221616 }
16231617 }
16241618
@@ -1536,17 +1536,7 @@ func startBackupScheduler(ctx context.Context, cfg Config, svc *services, log ze
1536 }1536 }
1537 1537
1538 func resolveBackupSchedule(raw string) (domain.BackupSchedule, error) {1538 func resolveBackupSchedule(raw string) (domain.BackupSchedule, error) {
1539- schedule := domain.BackupSchedule(strings.ToLower(strings.TrimSpace(raw)))1539+ return resolveSchedulePreset(raw, "backups.schedule", domain.ScheduleDaily)
1540- if schedule == "" {
1541- schedule = domain.ScheduleDaily
1542- }
1543-
1544- switch schedule {
1545- case domain.ScheduleHourly, domain.ScheduleDaily, domain.ScheduleWeekly, domain.ScheduleMonthly:
1546- return schedule, nil
1547- default:
1548- return "", fmt.Errorf("backups.schedule must be one of: hourly, daily, weekly, monthly")
1549- }
1550 }1540 }
1551 1541
1552 func startImagePruneScheduler(ctx context.Context, cfg Config, svc *services, log zerowrap.Logger, keepLastGetter func() int) (*cronSvc.Scheduler, error) {1542 func startImagePruneScheduler(ctx context.Context, cfg Config, svc *services, log zerowrap.Logger, keepLastGetter func() int) (*cronSvc.Scheduler, error) {
@@ -1609,16 +1599,20 @@ func startImagePruneScheduler(ctx context.Context, cfg Config, svc *services, lo
1609 }1599 }
1610 1600
1611 func resolveImagePruneSchedule(raw string) (domain.BackupSchedule, error) {1601 func resolveImagePruneSchedule(raw string) (domain.BackupSchedule, error) {
1602+ return resolveSchedulePreset(raw, "images.prune.schedule", domain.ScheduleDaily)
1603+}
1604+
1605+func resolveSchedulePreset(raw, name string, defaultVal domain.BackupSchedule) (domain.BackupSchedule, error) {
1612 schedule := domain.BackupSchedule(strings.ToLower(strings.TrimSpace(raw)))1606 schedule := domain.BackupSchedule(strings.ToLower(strings.TrimSpace(raw)))
1613 if schedule == "" {1607 if schedule == "" {
1614- schedule = domain.ScheduleDaily1608+ schedule = defaultVal
1615 }1609 }
1616 1610
1617 switch schedule {1611 switch schedule {
1618 case domain.ScheduleHourly, domain.ScheduleDaily, domain.ScheduleWeekly, domain.ScheduleMonthly:1612 case domain.ScheduleHourly, domain.ScheduleDaily, domain.ScheduleWeekly, domain.ScheduleMonthly:
1619 return schedule, nil1613 return schedule, nil
1620 default:1614 default:
1621- return "", fmt.Errorf("images.prune.schedule must be one of: hourly, daily, weekly, monthly")1615+ return "", fmt.Errorf("%s must be one of: hourly, daily, weekly, monthly", name)
1622 }1616 }
1623 }1617 }
1624 1618
M internal/usecase/backup/service.go +17 -6
@@ -86,6 +86,7 @@ func (s *Service) RunForSchedule(ctx context.Context, schedule domain.BackupSche
8686 for _, domainName := range domainNames {
8787 dbs, err := s.DetectDatabases(ctx, domainName)
8888 if err != nil {
89+ s.log.Error().Err(err).Str("domain", domainName).Msg("scheduled backup database detection failed")
8990 if firstErr == nil {
9091 firstErr = fmt.Errorf("detect databases for %s: %w", domainName, err)
9192 }
@@ -93,13 +94,19 @@ func (s *Service) RunForSchedule(ctx context.Context, schedule domain.BackupSche
9394 }
9495
9596 for _, db := range dbs {
96- if _, err := s.runBackup(ctx, domainName, db.Name, schedule); err != nil && firstErr == nil {
97- firstErr = fmt.Errorf("run backup for %s/%s: %w", domainName, db.Name, err)
97+ if _, err := s.runBackupForDB(ctx, domainName, db, schedule); err != nil {
98+ s.log.Error().Err(err).Str("domain", domainName).Str("db", db.Name).Msg("scheduled backup failed")
99+ if firstErr == nil {
100+ firstErr = fmt.Errorf("run backup for %s/%s: %w", domainName, db.Name, err)
101+ }
98102 }
99103 }
100104
101- if _, err := s.storage.ApplyRetention(ctx, domainName, s.config.Retention); err != nil && firstErr == nil {
102- firstErr = fmt.Errorf("apply retention for %s: %w", domainName, err)
105+ if _, err := s.storage.ApplyRetention(ctx, domainName, s.config.Retention); err != nil {
106+ s.log.Error().Err(err).Str("domain", domainName).Msg("scheduled backup retention failed")
107+ if firstErr == nil {
108+ firstErr = fmt.Errorf("apply retention for %s: %w", domainName, err)
109+ }
103110 }
104111 }
105112
@@ -107,8 +114,6 @@ func (s *Service) RunForSchedule(ctx context.Context, schedule domain.BackupSche
107114 }
108115
109116 func (s *Service) runBackup(ctx context.Context, domainName, dbName string, schedule domain.BackupSchedule) (*domain.BackupResult, error) {
110- started := time.Now().UTC()
111-
112117 dbs, err := s.DetectDatabases(ctx, domainName)
113118 if err != nil {
114119 return nil, err
@@ -119,6 +124,12 @@ func (s *Service) runBackup(ctx context.Context, domainName, dbName string, sche
119124 return nil, err
120125 }
121126
127+ return s.runBackupForDB(ctx, domainName, db, schedule)
128+}
129+
130+func (s *Service) runBackupForDB(ctx context.Context, domainName string, db domain.DBInfo, schedule domain.BackupSchedule) (*domain.BackupResult, error) {
131+ started := time.Now().UTC()
132+
122133 if db.Type != domain.DBTypePostgreSQL {
123134 return nil, fmt.Errorf("unsupported database type: %s", db.Type)
124135 }
@@ -86,6 +86,7 @@ func (s *Service) RunForSchedule(ctx context.Context, schedule domain.BackupSche
86 for _, domainName := range domainNames {86 for _, domainName := range domainNames {
87 dbs, err := s.DetectDatabases(ctx, domainName)87 dbs, err := s.DetectDatabases(ctx, domainName)
88 if err != nil {88 if err != nil {
89+ s.log.Error().Err(err).Str("domain", domainName).Msg("scheduled backup database detection failed")
89 if firstErr == nil {90 if firstErr == nil {
90 firstErr = fmt.Errorf("detect databases for %s: %w", domainName, err)91 firstErr = fmt.Errorf("detect databases for %s: %w", domainName, err)
91 }92 }
@@ -93,13 +94,19 @@ func (s *Service) RunForSchedule(ctx context.Context, schedule domain.BackupSche
93 }94 }
94 95
95 for _, db := range dbs {96 for _, db := range dbs {
96- if _, err := s.runBackup(ctx, domainName, db.Name, schedule); err != nil && firstErr == nil {97+ if _, err := s.runBackupForDB(ctx, domainName, db, schedule); err != nil {
97- firstErr = fmt.Errorf("run backup for %s/%s: %w", domainName, db.Name, err)98+ s.log.Error().Err(err).Str("domain", domainName).Str("db", db.Name).Msg("scheduled backup failed")
99+ if firstErr == nil {
100+ firstErr = fmt.Errorf("run backup for %s/%s: %w", domainName, db.Name, err)
101+ }
98 }102 }
99 }103 }
100 104
101- if _, err := s.storage.ApplyRetention(ctx, domainName, s.config.Retention); err != nil && firstErr == nil {105+ if _, err := s.storage.ApplyRetention(ctx, domainName, s.config.Retention); err != nil {
102- firstErr = fmt.Errorf("apply retention for %s: %w", domainName, err)106+ s.log.Error().Err(err).Str("domain", domainName).Msg("scheduled backup retention failed")
107+ if firstErr == nil {
108+ firstErr = fmt.Errorf("apply retention for %s: %w", domainName, err)
109+ }
103 }110 }
104 }111 }
105 112
@@ -107,8 +114,6 @@ func (s *Service) RunForSchedule(ctx context.Context, schedule domain.BackupSche
107 }114 }
108 115
109 func (s *Service) runBackup(ctx context.Context, domainName, dbName string, schedule domain.BackupSchedule) (*domain.BackupResult, error) {116 func (s *Service) runBackup(ctx context.Context, domainName, dbName string, schedule domain.BackupSchedule) (*domain.BackupResult, error) {
110- started := time.Now().UTC()
111-
112 dbs, err := s.DetectDatabases(ctx, domainName)117 dbs, err := s.DetectDatabases(ctx, domainName)
113 if err != nil {118 if err != nil {
114 return nil, err119 return nil, err
@@ -119,6 +124,12 @@ func (s *Service) runBackup(ctx context.Context, domainName, dbName string, sche
119 return nil, err124 return nil, err
120 }125 }
121 126
127+ return s.runBackupForDB(ctx, domainName, db, schedule)
128+}
129+
130+func (s *Service) runBackupForDB(ctx context.Context, domainName string, db domain.DBInfo, schedule domain.BackupSchedule) (*domain.BackupResult, error) {
131+ started := time.Now().UTC()
132+
122 if db.Type != domain.DBTypePostgreSQL {133 if db.Type != domain.DBTypePostgreSQL {
123 return nil, fmt.Errorf("unsupported database type: %s", db.Type)134 return nil, fmt.Errorf("unsupported database type: %s", db.Type)
124 }135 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close