github.com/advanderveer/restic@v0.8.1-0.20171209104529-42a8c19aaea6/cmd/restic/cmd_forget.go (about) 1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "sort" 7 "strings" 8 9 "github.com/restic/restic/internal/errors" 10 "github.com/restic/restic/internal/restic" 11 "github.com/spf13/cobra" 12 ) 13 14 var cmdForget = &cobra.Command{ 15 Use: "forget [flags] [snapshot ID] [...]", 16 Short: "Remove snapshots from the repository", 17 Long: ` 18 The "forget" command removes snapshots according to a policy. Please note that 19 this command really only deletes the snapshot object in the repository, which 20 is a reference to data stored there. In order to remove this (now unreferenced) 21 data after 'forget' was run successfully, see the 'prune' command. `, 22 DisableAutoGenTag: true, 23 RunE: func(cmd *cobra.Command, args []string) error { 24 return runForget(forgetOptions, globalOptions, args) 25 }, 26 } 27 28 // ForgetOptions collects all options for the forget command. 29 type ForgetOptions struct { 30 Last int 31 Hourly int 32 Daily int 33 Weekly int 34 Monthly int 35 Yearly int 36 KeepTags restic.TagLists 37 38 Host string 39 Tags restic.TagLists 40 Paths []string 41 Compact bool 42 43 // Grouping 44 GroupBy string 45 DryRun bool 46 Prune bool 47 } 48 49 var forgetOptions ForgetOptions 50 51 func init() { 52 cmdRoot.AddCommand(cmdForget) 53 54 f := cmdForget.Flags() 55 f.IntVarP(&forgetOptions.Last, "keep-last", "l", 0, "keep the last `n` snapshots") 56 f.IntVarP(&forgetOptions.Hourly, "keep-hourly", "H", 0, "keep the last `n` hourly snapshots") 57 f.IntVarP(&forgetOptions.Daily, "keep-daily", "d", 0, "keep the last `n` daily snapshots") 58 f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last `n` weekly snapshots") 59 f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots") 60 f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots") 61 62 f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)") 63 // Sadly the commonly used shortcut `H` is already used. 64 f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`") 65 // Deprecated since 2017-03-07. 66 f.StringVar(&forgetOptions.Host, "hostname", "", "only consider snapshots with the given `hostname` (deprecated)") 67 f.Var(&forgetOptions.Tags, "tag", "only consider snapshots which include this `taglist` in the format `tag[,tag,...]` (can be specified multiple times)") 68 f.StringArrayVar(&forgetOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` (can be specified multiple times)") 69 f.BoolVarP(&forgetOptions.Compact, "compact", "c", false, "use compact format") 70 71 f.StringVarP(&forgetOptions.GroupBy, "group-by", "g", "host,paths", "string for grouping snapshots by host,paths,tags") 72 f.BoolVarP(&forgetOptions.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done") 73 f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed") 74 75 f.SortFlags = false 76 } 77 78 func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { 79 repo, err := OpenRepository(gopts) 80 if err != nil { 81 return err 82 } 83 84 lock, err := lockRepoExclusive(repo) 85 defer unlockRepo(lock) 86 if err != nil { 87 return err 88 } 89 90 // group by hostname and dirs 91 type key struct { 92 Hostname string 93 Paths []string 94 Tags []string 95 } 96 snapshotGroups := make(map[string]restic.Snapshots) 97 98 var GroupByTag bool 99 var GroupByHost bool 100 var GroupByPath bool 101 var GroupOptionList []string 102 103 GroupOptionList = strings.Split(opts.GroupBy, ",") 104 105 for _, option := range GroupOptionList { 106 switch option { 107 case "host": 108 GroupByHost = true 109 case "paths": 110 GroupByPath = true 111 case "tags": 112 GroupByTag = true 113 case "": 114 default: 115 return errors.Fatal("unknown grouping option: '" + option + "'") 116 } 117 } 118 119 removeSnapshots := 0 120 121 ctx, cancel := context.WithCancel(gopts.ctx) 122 defer cancel() 123 for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) { 124 if len(args) > 0 { 125 // When explicit snapshots args are given, remove them immediately. 126 if !opts.DryRun { 127 h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()} 128 if err = repo.Backend().Remove(gopts.ctx, h); err != nil { 129 return err 130 } 131 Verbosef("removed snapshot %v\n", sn.ID().Str()) 132 removeSnapshots++ 133 } else { 134 Verbosef("would have removed snapshot %v\n", sn.ID().Str()) 135 } 136 } else { 137 // Determining grouping-keys 138 var tags []string 139 var hostname string 140 var paths []string 141 142 if GroupByTag { 143 tags = sn.Tags 144 sort.StringSlice(tags).Sort() 145 } 146 if GroupByHost { 147 hostname = sn.Hostname 148 } 149 if GroupByPath { 150 paths = sn.Paths 151 } 152 153 sort.StringSlice(sn.Paths).Sort() 154 var k []byte 155 var err error 156 157 k, err = json.Marshal(key{Tags: tags, Hostname: hostname, Paths: paths}) 158 159 if err != nil { 160 return err 161 } 162 snapshotGroups[string(k)] = append(snapshotGroups[string(k)], sn) 163 } 164 } 165 if len(args) > 0 { 166 return nil 167 } 168 169 policy := restic.ExpirePolicy{ 170 Last: opts.Last, 171 Hourly: opts.Hourly, 172 Daily: opts.Daily, 173 Weekly: opts.Weekly, 174 Monthly: opts.Monthly, 175 Yearly: opts.Yearly, 176 Tags: opts.KeepTags, 177 } 178 179 if policy.Empty() { 180 Verbosef("no policy was specified, no snapshots will be removed\n") 181 return nil 182 } 183 184 for k, snapshotGroup := range snapshotGroups { 185 var key key 186 if json.Unmarshal([]byte(k), &key) != nil { 187 return err 188 } 189 190 // Info 191 Verbosef("snapshots") 192 var infoStrings []string 193 if GroupByTag { 194 infoStrings = append(infoStrings, "tags ["+strings.Join(key.Tags, ", ")+"]") 195 } 196 if GroupByHost { 197 infoStrings = append(infoStrings, "host ["+key.Hostname+"]") 198 } 199 if GroupByPath { 200 infoStrings = append(infoStrings, "paths ["+strings.Join(key.Paths, ", ")+"]") 201 } 202 if infoStrings != nil { 203 Verbosef(" for (" + strings.Join(infoStrings, ", ") + ")") 204 } 205 Verbosef(":\n\n") 206 207 keep, remove := restic.ApplyPolicy(snapshotGroup, policy) 208 209 if len(keep) != 0 && !gopts.Quiet { 210 Printf("keep %d snapshots:\n", len(keep)) 211 PrintSnapshots(globalOptions.stdout, keep, opts.Compact) 212 Printf("\n") 213 } 214 215 if len(remove) != 0 && !gopts.Quiet { 216 Printf("remove %d snapshots:\n", len(remove)) 217 PrintSnapshots(globalOptions.stdout, remove, opts.Compact) 218 Printf("\n") 219 } 220 221 removeSnapshots += len(remove) 222 223 if !opts.DryRun { 224 for _, sn := range remove { 225 h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()} 226 err = repo.Backend().Remove(gopts.ctx, h) 227 if err != nil { 228 return err 229 } 230 } 231 } 232 } 233 234 if removeSnapshots > 0 && opts.Prune { 235 Verbosef("%d snapshots have been removed, running prune\n", removeSnapshots) 236 if !opts.DryRun { 237 return pruneRepository(gopts, repo) 238 } 239 } 240 241 return nil 242 }