Skip to content

Commit 0e4f128

Browse files
committed
Added 'delete' command and improve 'list' command
Resolve for #34
1 parent ba996f1 commit 0e4f128

File tree

2 files changed

+140
-45
lines changed

2 files changed

+140
-45
lines changed

main.go

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,50 @@ func main() {
7272
{
7373
Name: "list",
7474
Usage: "Print list of backups and exit",
75-
UsageText: "clickhouse-backup list",
75+
UsageText: "clickhouse-backup list [all|local|s3] [latest|penult]",
7676
Action: func(c *cli.Context) error {
7777
config := getConfig(c)
78-
fmt.Println("Local backups:")
79-
printLocalBackups(*config)
80-
fmt.Println("Backups on S3:")
81-
printS3Backups(*config)
78+
switch c.Args().Get(0) {
79+
case "local":
80+
return printLocalBackups(*config, c.Args().Get(1))
81+
case "s3":
82+
return printS3Backups(*config, c.Args().Get(1))
83+
case "all", "":
84+
fmt.Println("Local backups:")
85+
if err := printLocalBackups(*config, c.Args().Get(1)); err != nil {
86+
return err
87+
}
88+
fmt.Println("Backups on S3:")
89+
if err := printS3Backups(*config, c.Args().Get(1)); err != nil {
90+
return err
91+
}
92+
default:
93+
fmt.Fprintf(os.Stderr, "Unknown command '%s'\n", c.Args().Get(0))
94+
cli.ShowCommandHelpAndExit(c, c.Command.Name, 1)
95+
}
96+
return nil
97+
},
98+
Flags: cliapp.Flags,
99+
},
100+
{
101+
Name: "delete",
102+
Usage: "Delete specific backup",
103+
UsageText: "clickhouse-backup delete <local|s3> <backup_name>",
104+
Action: func(c *cli.Context) error {
105+
config := getConfig(c)
106+
if c.Args().Get(1) == "" {
107+
fmt.Fprintln(os.Stderr, "Backup name must be defined")
108+
cli.ShowCommandHelpAndExit(c, c.Command.Name, 1)
109+
}
110+
switch c.Args().Get(0) {
111+
case "local":
112+
return removeBackupLocal(*config, c.Args().Get(1))
113+
case "s3":
114+
return removeBackupS3(*config, c.Args().Get(1))
115+
default:
116+
fmt.Fprintf(os.Stderr, "Unknown command '%s'\n", c.Args().Get(0))
117+
cli.ShowCommandHelpAndExit(c, c.Command.Name, 1)
118+
}
82119
return nil
83120
},
84121
Flags: cliapp.Flags,
@@ -283,7 +320,7 @@ func restoreSchema(config Config, backupName string, tablePattern string, dryRun
283320
}
284321
if backupName == "" {
285322
fmt.Println("Select backup for restore:")
286-
printLocalBackups(config)
323+
printLocalBackups(config, "all")
287324
os.Exit(1)
288325
}
289326
dataPath := getDataPath(config)
@@ -326,18 +363,37 @@ func restoreSchema(config Config, backupName string, tablePattern string, dryRun
326363
return nil
327364
}
328365

329-
func printLocalBackups(config Config) error {
366+
func printBackups(backupList []Backup, format string) error {
367+
switch format {
368+
case "latest", "last", "l":
369+
if len(backupList) < 1 {
370+
return fmt.Errorf("No backups found")
371+
}
372+
fmt.Println(backupList[len(backupList)-1].Name)
373+
case "penult", "prev", "previous", "p":
374+
if len(backupList) < 2 {
375+
return fmt.Errorf("No penult backup is found")
376+
}
377+
fmt.Println(backupList[len(backupList)-2].Name)
378+
case "all", "":
379+
if len(backupList) == 0 {
380+
fmt.Println("No backups found")
381+
}
382+
for _, backup := range backupList {
383+
fmt.Printf("- '%s' (created at %s)\n", backup.Name, backup.Date.Format("02-01-2006 15:04:05"))
384+
}
385+
default:
386+
return fmt.Errorf("'%s' undefined", format)
387+
}
388+
return nil
389+
}
390+
391+
func printLocalBackups(config Config, format string) error {
330392
backupList, err := listLocalBackups(config)
331393
if err != nil && !os.IsNotExist(err) {
332394
return err
333395
}
334-
if len(backupList) == 0 {
335-
fmt.Println("No backups found")
336-
}
337-
for _, backup := range backupList {
338-
fmt.Printf("- '%s' (created at %s)\n", backup.Name, backup.Date.Format("02-01-2006 15:04:05"))
339-
}
340-
return nil
396+
return printBackups(backupList, format)
341397
}
342398

343399
func listLocalBackups(config Config) ([]Backup, error) {
@@ -375,7 +431,7 @@ func listLocalBackups(config Config) ([]Backup, error) {
375431
return result, nil
376432
}
377433

378-
func printS3Backups(config Config) error {
434+
func printS3Backups(config Config, format string) error {
379435
s3 := &S3{Config: &config.S3}
380436
if err := s3.Connect(); err != nil {
381437
return fmt.Errorf("can't connect to s3 with %v", err)
@@ -384,13 +440,7 @@ func printS3Backups(config Config) error {
384440
if err != nil {
385441
return err
386442
}
387-
if len(backupList) == 0 {
388-
fmt.Println("No backups found")
389-
}
390-
for _, backup := range backupList {
391-
fmt.Printf("- '%s' (uploaded at %s)\n", backup.Name, backup.Date.Format("02-01-2006 15:04:05"))
392-
}
393-
return nil
443+
return printBackups(backupList, format)
394444
}
395445

396446
func freeze(config Config, tablePattern string, dryRun bool) error {
@@ -503,7 +553,7 @@ func restoreData(config Config, backupName string, tablePattern string, dryRun b
503553
}
504554
if backupName == "" {
505555
fmt.Println("Select backup for restore:")
506-
printLocalBackups(config)
556+
printLocalBackups(config, "all")
507557
os.Exit(1)
508558
}
509559
dataPath := getDataPath(config)
@@ -579,7 +629,7 @@ func upload(config Config, backupName string, diffFrom string, dryRun bool) erro
579629
}
580630
if backupName == "" {
581631
fmt.Println("Select backup for upload:")
582-
printLocalBackups(config)
632+
printLocalBackups(config, "all")
583633
os.Exit(1)
584634
}
585635
dataPath := getDataPath(config)
@@ -627,7 +677,7 @@ func download(config Config, backupName string, dryRun bool) error {
627677
}
628678
if backupName == "" {
629679
fmt.Println("Select backup for download:")
630-
printS3Backups(config)
680+
printS3Backups(config, "all")
631681
os.Exit(1)
632682
}
633683
dataPath := getDataPath(config)
@@ -696,6 +746,45 @@ func removeOldBackupsLocal(config Config, dryRun bool) error {
696746
return nil
697747
}
698748

749+
func removeBackupLocal(config Config, backupName string) error {
750+
backupList, err := listLocalBackups(config)
751+
if err != nil {
752+
return err
753+
}
754+
dataPath := getDataPath(config)
755+
if dataPath == "" {
756+
return ErrUnknownClickhouseDataPath
757+
}
758+
for _, backup := range backupList {
759+
if backup.Name == backupName {
760+
return os.RemoveAll(path.Join(dataPath, "backup", backupName))
761+
}
762+
}
763+
return fmt.Errorf("backup '%s' not found", backupName)
764+
}
765+
766+
func removeBackupS3(config Config, backupName string) error {
767+
dataPath := getDataPath(config)
768+
if dataPath == "" {
769+
return ErrUnknownClickhouseDataPath
770+
}
771+
s3 := &S3{Config: &config.S3}
772+
if err := s3.Connect(); err != nil {
773+
return fmt.Errorf("can't connect to s3 with: %v", err)
774+
}
775+
backupList, err := s3.BackupList()
776+
if err != nil {
777+
return err
778+
}
779+
for _, backup := range backupList {
780+
if backup.Name == backupName {
781+
return s3.RemoveBackup(backupName)
782+
}
783+
}
784+
return fmt.Errorf("backup '%s' not found on s3", backupName)
785+
}
786+
787+
699788
func getConfig(ctx *cli.Context) *Config {
700789
configPath := ctx.String("config")
701790
if configPath == defaultConfigPath {

s3.go

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,25 @@ func (s *S3) BackupList() ([]Backup, error) {
586586
return result, nil
587587
}
588588

589+
func (s *S3) RemoveBackup(backupName string) error {
590+
objects := []s3manager.BatchDeleteObject{}
591+
s.remotePager(s.Config.Path, false, func(page *s3.ListObjectsV2Output) {
592+
for _, c := range page.Contents {
593+
if strings.HasPrefix(*c.Key, path.Join(s.Config.Path, backupName)) {
594+
objects = append(objects, s3manager.BatchDeleteObject{
595+
Object: &s3.DeleteObjectInput{
596+
Bucket: aws.String(s.Config.Bucket),
597+
Key: c.Key,
598+
},
599+
})
600+
}
601+
}
602+
})
603+
batcher := s3manager.NewBatchDelete(s.session)
604+
return batcher.Delete(aws.BackgroundContext(),
605+
&s3manager.DeleteObjectsIterator{Objects: objects})
606+
}
607+
589608
func (s *S3) RemoveOldBackups(keep int) error {
590609
if keep < 1 {
591610
return nil
@@ -595,29 +614,16 @@ func (s *S3) RemoveOldBackups(keep int) error {
595614
return err
596615
}
597616
backupsToDelete := GetBackupsToDelete(backupList, keep)
598-
objects := []s3manager.BatchDeleteObject{}
599-
s.remotePager(s.Config.Path, false, func(page *s3.ListObjectsV2Output) {
600-
for _, c := range page.Contents {
601-
for _, backupToDelete := range backupsToDelete {
602-
if strings.HasPrefix(*c.Key, path.Join(s.Config.Path, backupToDelete.Name)) {
603-
objects = append(objects, s3manager.BatchDeleteObject{
604-
Object: &s3.DeleteObjectInput{
605-
Bucket: aws.String(s.Config.Bucket),
606-
Key: c.Key,
607-
},
608-
})
609-
}
610-
}
617+
for _, backupToDelete := range backupsToDelete {
618+
if s.DryRun {
619+
log.Printf("Delete '%s'\n", backupToDelete.Name)
620+
continue
611621
}
612-
})
613-
if s.DryRun {
614-
for _, o := range objects {
615-
log.Println("Delete", *o.Object.Key)
622+
if err := s.RemoveBackup(backupToDelete.Name); err != nil {
623+
return err
616624
}
617-
return nil
618625
}
619-
batcher := s3manager.NewBatchDelete(s.session)
620-
return batcher.Delete(aws.BackgroundContext(), &s3manager.DeleteObjectsIterator{Objects: objects})
626+
return nil
621627
}
622628

623629
func (s *S3) newSyncFolderIterator(localPath, dstPath string) (*SyncFolderIterator, map[string]fileInfo, error) {

0 commit comments

Comments
 (0)