Skip to content

Commit a453ca4

Browse files
authored
Ensure restores can target the backup id or alias (#243)
* Ensure restores can target the backup id or alias * Fix test * Suppress cyclomatic complexity warnings in test files * specify test pattern in deepsource config
1 parent 471419e commit a453ca4

File tree

6 files changed

+125
-40
lines changed

6 files changed

+125
-40
lines changed

.deepsource.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
version = 1
22

3+
test_patterns = [
4+
"**/*_test.go"
5+
]
6+
37
[[analyzers]]
48
name = "shell"
59

cmd/flexctl/backups.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ var backupCreateCmd = &cobra.Command{
3636
}
3737

3838
if err := createBackup(cmd); err != nil {
39-
return err
39+
return fmt.Errorf("failed to create backup: %v", err)
4040
}
4141

4242
fmt.Println("Backup completed successfully!")
@@ -47,7 +47,7 @@ var backupCreateCmd = &cobra.Command{
4747
}
4848

4949
var backupShowCmd = &cobra.Command{
50-
Use: "show",
50+
Use: "show <backup-id>",
5151
Short: "Shows details about a specific backup",
5252
Long: `Shows details about a specific backup.`,
5353
RunE: func(cmd *cobra.Command, args []string) error {
@@ -137,7 +137,7 @@ func createBackup(cmd *cobra.Command) error {
137137
fmt.Println("Performing backup...")
138138

139139
if _, err := barman.Backup(ctx, cfg); err != nil {
140-
return fmt.Errorf("failed to create backup: %v", err)
140+
return err
141141
}
142142

143143
return nil
@@ -190,7 +190,7 @@ func listBackups(cmd *cobra.Command) error {
190190
}
191191

192192
table := tablewriter.NewWriter(os.Stdout)
193-
table.SetHeader([]string{"ID", "Name", "Status", "End time", "Begin WAL"})
193+
table.SetHeader([]string{"ID/Name", "Alias", "Status", "End time", "Begin WAL"})
194194

195195
// Set table alignment, borders, padding, etc. as needed
196196
table.SetAlignment(tablewriter.ALIGN_LEFT)

cmd/flexctl/main.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@ import (
88
)
99

1010
func main() {
11-
var rootCmd = &cobra.Command{Use: "flexctl"}
11+
var rootCmd = &cobra.Command{
12+
Use: "flexctl",
13+
SilenceErrors: true,
14+
SilenceUsage: true,
15+
}
1216

1317
// Backup commands
1418
var backupCmd = &cobra.Command{Use: "backup"}
1519

1620
rootCmd.AddCommand(backupCmd)
1721
backupCmd.AddCommand(backupListCmd)
18-
backupCmd.AddCommand(backupCreateCmd)
1922
backupCmd.AddCommand(backupShowCmd)
23+
backupCmd.AddCommand(backupCreateCmd)
2024

2125
if err := rootCmd.Execute(); err != nil {
2226
fmt.Println(err)
@@ -25,12 +29,12 @@ func main() {
2529
}
2630

2731
func init() {
28-
// Backup commands
32+
// Backup list
2933
backupListCmd.Flags().StringP("status", "s", "", "Filter backups by status (Not applicable for JSON output)")
3034
backupListCmd.Flags().BoolP("json", "", false, "Output in JSON format")
31-
35+
// Backup show
3236
backupShowCmd.Flags().BoolP("json", "", false, "Output in JSON format")
33-
37+
// Backup create
3438
backupCreateCmd.Flags().StringP("name", "n", "", "Name of the backup")
3539
backupCreateCmd.Flags().BoolP("immediate-checkpoint", "", false, "Forces Postgres to perform an immediate checkpoint")
3640
}

internal/flypg/barman.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,21 +127,25 @@ func (b *Barman) Backup(ctx context.Context, cfg BackupConfig) ([]byte, error) {
127127
}
128128

129129
if cfg.Name != "" {
130+
// Ensure the alias is unique, otherwise we won't be able to restore using it.
131+
if err := b.validateBackupName(ctx, cfg.Name); err != nil {
132+
return nil, err
133+
}
130134
args = append(args, "-n", cfg.Name)
131135
}
132136

133137
return utils.RunCmd(ctx, "postgres", "barman-cloud-backup", args...)
134138
}
135139

136140
// RestoreBackup returns the command string used to restore a base backup.
137-
func (b *Barman) RestoreBackup(ctx context.Context, backupID string) ([]byte, error) {
141+
func (b *Barman) RestoreBackup(ctx context.Context, name string) ([]byte, error) {
138142
args := []string{
139143
"--cloud-provider", providerDefault,
140144
"--endpoint-url", b.endpoint,
141145
"--profile", b.authProfile,
142146
b.BucketURL(),
143147
b.bucketDirectory,
144-
backupID,
148+
name,
145149
defaultRestoreDir,
146150
}
147151

@@ -287,6 +291,21 @@ func (*Barman) parseBackups(backupBytes []byte) (BackupList, error) {
287291
return backupList, nil
288292
}
289293

294+
func (b *Barman) validateBackupName(ctx context.Context, name string) error {
295+
backupList, err := b.ListBackups(ctx)
296+
if err != nil {
297+
return fmt.Errorf("failed to list backups: %s", err)
298+
}
299+
300+
for _, backup := range backupList.Backups {
301+
if backup.Name == name {
302+
return fmt.Errorf("backup name '%s' already exists", name)
303+
}
304+
}
305+
306+
return nil
307+
}
308+
290309
func formatTimestamp(timestamp string) (string, error) {
291310
if strings.HasSuffix(timestamp, "Z") {
292311
timestamp = strings.TrimSuffix(timestamp, "Z") + "+00:00"

internal/flypg/barman_restore.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ func (b *BarmanRestore) restoreFromBackup(ctx context.Context) error {
164164
}
165165
case b.recoveryTargetName != "":
166166
// Resolve the target base backup
167-
backupID, err = b.resolveBackupFromID(backups, b.recoveryTargetName)
167+
backupID, err = b.resolveBackupFromName(backups, b.recoveryTargetName)
168168
if err != nil {
169-
return fmt.Errorf("failed to resolve backup target by id: %s", err)
169+
return fmt.Errorf("failed to resolve backup target by id/name: %s", err)
170170
}
171171
default:
172172
backupID, err = b.resolveBackupFromTime(backups, time.Now().Format(time.RFC3339))
@@ -192,18 +192,19 @@ func (b *BarmanRestore) restoreFromBackup(ctx context.Context) error {
192192
return nil
193193
}
194194

195-
func (*BarmanRestore) resolveBackupFromID(backupList BackupList, id string) (string, error) {
195+
func (*BarmanRestore) resolveBackupFromName(backupList BackupList, name string) (string, error) {
196196
if len(backupList.Backups) == 0 {
197197
return "", fmt.Errorf("no backups found")
198198
}
199199

200200
for _, backup := range backupList.Backups {
201-
if backup.ID == id {
201+
// Allow for either the backup ID or the backup name to be used.
202+
if backup.ID == name || backup.Name == name {
202203
return backup.ID, nil
203204
}
204205
}
205206

206-
return "", fmt.Errorf("no backup found with id %s", id)
207+
return "", fmt.Errorf("no backup found with id/name %s", name)
207208
}
208209

209210
func (*BarmanRestore) resolveBackupFromTime(backupList BackupList, restoreStr string) (string, error) {

internal/flypg/barman_restore_test.go

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ const backupsResponse = `{
4343
},
4444
{
4545
"backup_label": "'START WAL LOCATION: 0/8000028 (file 000000010000000000000008)\\nCHECKPOINT LOCATION: 0/8000098\\nBACKUP METHOD: streamed\\nBACKUP FROM: primary\\nSTART TIME: 2024-06-25 19:44:13 UTC\\nLABEL: Barman backup cloud 20240625T194412\\nSTART TIMELINE: 1\\n'",
46-
"backup_name": "test-backup-2",
4746
"begin_offset": 40,
4847
"begin_time": "Tue Jun 25 19:44:12 2024",
4948
"begin_wal": "000000010000000000000008",
@@ -87,6 +86,7 @@ const backupsResponse = `{
8786
"timeline": 1,
8887
"version": 150006,
8988
"xlog_segment_size": 16777216,
89+
"backup_name": "test-backup-1",
9090
"backup_id": "20240625T194412"
9191
},
9292
{
@@ -212,6 +212,19 @@ func TestNewBarmanRestore(t *testing.T) {
212212
}
213213
})
214214

215+
t.Run("target-name-with-alias", func(t *testing.T) {
216+
t.Setenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG", "https://my-key:my-secret@fly.storage.tigris.dev/my-bucket/my-directory?targetName=test-backup-1")
217+
218+
restore, err := NewBarmanRestore(os.Getenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG"))
219+
if err != nil {
220+
t.Fatalf("NewBarmanRestore failed with: %v", err)
221+
}
222+
223+
if restore.recoveryTargetName != "test-backup-1" {
224+
t.Fatalf("expected recovery target name to be test-backup-1, got %s", restore.recoveryTargetName)
225+
}
226+
})
227+
215228
t.Run("target-name-with-options", func(t *testing.T) {
216229
t.Setenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG", "https://my-key:my-secret@fly.storage.tigris.dev/my-bucket/my-directory?targetName=20240705T051010&targetAction=shutdown&targetTimeline=2&targetInclusive=false")
217230

@@ -277,36 +290,57 @@ func TestParseBackups(t *testing.T) {
277290
t.Fatalf("expected 2 backups, got %d", len(list.Backups))
278291
}
279292

280-
firstBackup := list.Backups[0]
281-
if firstBackup.ID != "20240702T210544" {
282-
t.Fatalf("expected backup ID to be 20240625T194412, got %s", firstBackup.ID)
283-
}
293+
t.Run("first-backup", func(t *testing.T) {
294+
backup := list.Backups[0]
295+
if backup.ID != "20240702T210544" {
296+
t.Fatalf("expected backup ID to be 20240625T194412, got %s", backup.ID)
297+
}
284298

285-
if firstBackup.StartTime != "Tue Jun 24 19:44:20 2024" {
286-
t.Fatalf("expected start time to be Tue Jun 24 19:44:20 2024, got %s", firstBackup.StartTime)
287-
}
299+
if backup.StartTime != "Tue Jun 24 19:44:20 2024" {
300+
t.Fatalf("expected start time to be Tue Jun 24 19:44:20 2024, got %s", backup.StartTime)
301+
}
288302

289-
if firstBackup.EndTime != "" {
290-
t.Fatalf("expected end time to be empty, but got %s", firstBackup.EndTime)
291-
}
303+
if backup.EndTime != "" {
304+
t.Fatalf("expected end time to be empty, but got %s", backup.EndTime)
305+
}
292306

293-
if firstBackup.Status != "FAILED" {
294-
t.Fatalf("expected status to be FAILED, got %s", firstBackup.Status)
295-
}
307+
if backup.Status != "FAILED" {
308+
t.Fatalf("expected status to be FAILED, got %s", backup.Status)
309+
}
296310

297-
secondBackup := list.Backups[2]
311+
if backup.Name != "" {
312+
t.Fatalf("expected name to be empty, but got %s", backup.Name)
313+
}
298314

299-
if secondBackup.ID != "20240626T172443" {
300-
t.Fatalf("expected backup ID to be 20240626T172443, got %s", secondBackup.ID)
301-
}
315+
})
302316

303-
if secondBackup.StartTime != "Wed Jun 26 17:24:43 2024" {
304-
t.Fatalf("expected start time to be Wed Jun 26 17:24:43 2024, got %s", secondBackup.StartTime)
305-
}
317+
t.Run("second-backup", func(t *testing.T) {
318+
backup := list.Backups[1]
319+
if backup.Status != "DONE" {
320+
t.Fatalf("expected status to be DONE, got %s", backup.Status)
321+
}
306322

307-
if secondBackup.EndTime != "Wed Jun 26 17:27:02 2024" {
308-
t.Fatalf("expected end time to be Wed Jun 26 17:27:02 2024, got %s", secondBackup.EndTime)
309-
}
323+
if backup.Name != "test-backup-1" {
324+
t.Fatalf("expected name to be test-backup-1, got %s", backup.Name)
325+
}
326+
})
327+
328+
t.Run("third-backup", func(t *testing.T) {
329+
backup := list.Backups[2]
330+
331+
if backup.ID != "20240626T172443" {
332+
t.Fatalf("expected backup ID to be 20240626T172443, got %s", backup.ID)
333+
}
334+
335+
if backup.StartTime != "Wed Jun 26 17:24:43 2024" {
336+
t.Fatalf("expected start time to be Wed Jun 26 17:24:43 2024, got %s", backup.StartTime)
337+
}
338+
339+
if backup.EndTime != "Wed Jun 26 17:27:02 2024" {
340+
t.Fatalf("expected end time to be Wed Jun 26 17:27:02 2024, got %s", backup.EndTime)
341+
}
342+
343+
})
310344
})
311345
}
312346

@@ -360,6 +394,29 @@ func TestResolveBackupTarget(t *testing.T) {
360394
t.Fatalf("expected backup ID to be 20240626T172443, got %s", backupID)
361395
}
362396
})
397+
398+
t.Run("resolve-backup-by-name", func(t *testing.T) {
399+
backupID, err := restore.resolveBackupFromName(list, "20240625T194412")
400+
if err != nil {
401+
t.Fatalf("unexpected error: %v", err)
402+
}
403+
404+
if backupID != "20240625T194412" {
405+
t.Fatalf("expected backup ID to be 20240625T194412, got %s", backupID)
406+
}
407+
})
408+
409+
t.Run("resolve-backup-by-name-with-alias", func(t *testing.T) {
410+
// resolve backup by alias
411+
backupID, err := restore.resolveBackupFromName(list, "test-backup-1")
412+
if err != nil {
413+
t.Fatalf("unexpected error: %v", err)
414+
}
415+
416+
if backupID != "20240625T194412" {
417+
t.Fatalf("expected backup ID to be 20240625T194412, got %s", backupID)
418+
}
419+
})
363420
}
364421

365422
func setRestoreDefaultEnv(t *testing.T) {

0 commit comments

Comments
 (0)