mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-26 02:14:44 +01:00
e951beaef5
Job names are derived from job type + user-defined name in config file CLI now has subcommands corresponding 1:1 to the config file sections: push,pull,autosnap,prune A subcommand always expects a job name, thus executes exactly one job. Dict-style syntax also used for PullACL and Sink sections. jobrun package is currently only used for autosnap, all others need to be invoked repeatedly via external tool. Plan is to re-integrate jobrun in an explicit daemon-mode (subcommand).
140 lines
3.2 KiB
Go
140 lines
3.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/spf13/cobra"
|
|
"github.com/zrepl/zrepl/util"
|
|
"github.com/zrepl/zrepl/zfs"
|
|
"os"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
var pruneArgs struct {
|
|
job string
|
|
dryRun bool
|
|
}
|
|
|
|
var PruneCmd = &cobra.Command{
|
|
Use: "prune",
|
|
Short: "perform pruning",
|
|
Run: cmdPrune,
|
|
}
|
|
|
|
func init() {
|
|
PruneCmd.Flags().StringVar(&pruneArgs.job, "job", "", "job to run")
|
|
PruneCmd.Flags().BoolVarP(&pruneArgs.dryRun, "dryrun", "n", false, "dry run")
|
|
RootCmd.AddCommand(PruneCmd)
|
|
}
|
|
|
|
func cmdPrune(cmd *cobra.Command, args []string) {
|
|
|
|
if len(args) < 1 {
|
|
log.Printf("must specify exactly one job as positional argument")
|
|
os.Exit(1)
|
|
}
|
|
|
|
job, ok := conf.Prunes[args[0]]
|
|
if !ok {
|
|
log.Printf("could not find prune job: %s", args[0])
|
|
os.Exit(1)
|
|
}
|
|
|
|
log.Printf("Beginning prune job:\n%s", job)
|
|
ctx := PruneContext{job, time.Now(), pruneArgs.dryRun}
|
|
err := doPrune(ctx, log)
|
|
if err != nil {
|
|
log.Printf("Prune job failed with error: %s", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
}
|
|
|
|
type PruneContext struct {
|
|
Prune *Prune
|
|
Now time.Time
|
|
DryRun bool
|
|
}
|
|
|
|
type retentionGridAdaptor struct {
|
|
zfs.FilesystemVersion
|
|
}
|
|
|
|
func (a retentionGridAdaptor) Date() time.Time {
|
|
return a.Creation
|
|
}
|
|
|
|
func (a retentionGridAdaptor) LessThan(b util.RetentionGridEntry) bool {
|
|
return a.CreateTXG < b.(retentionGridAdaptor).CreateTXG
|
|
}
|
|
|
|
func doPrune(ctx PruneContext, log Logger) error {
|
|
|
|
if ctx.DryRun {
|
|
log.Printf("doing dry run")
|
|
}
|
|
|
|
prune := ctx.Prune
|
|
|
|
// ZFSListSnapsFiltered --> todo can extend fsfilter or need new? Have already something per fs
|
|
// Dedicated snapshot object? Adaptor object to FilesystemVersion?
|
|
|
|
filesystems, err := zfs.ZFSListMapping(prune.DatasetFilter)
|
|
if err != nil {
|
|
return fmt.Errorf("error applying filesystem filter: %s", err)
|
|
}
|
|
|
|
for _, fs := range filesystems {
|
|
|
|
fsversions, err := zfs.ZFSListFilesystemVersions(fs, prune.SnapshotFilter)
|
|
if err != nil {
|
|
return fmt.Errorf("error listing filesytem versions of %s: %s", fs, err)
|
|
}
|
|
|
|
if len(fsversions) == 0 {
|
|
continue
|
|
}
|
|
|
|
adaptors := make([]util.RetentionGridEntry, len(fsversions))
|
|
for fsv := range fsversions {
|
|
adaptors[fsv] = retentionGridAdaptor{fsversions[fsv]}
|
|
}
|
|
|
|
sort.SliceStable(adaptors, func(i, j int) bool {
|
|
return adaptors[i].LessThan(adaptors[j])
|
|
})
|
|
ctx.Now = adaptors[len(adaptors)-1].Date()
|
|
|
|
describe := func(a retentionGridAdaptor) string {
|
|
timeSince := a.Creation.Sub(ctx.Now)
|
|
const day time.Duration = 24 * time.Hour
|
|
days := timeSince / day
|
|
remainder := timeSince % day
|
|
return fmt.Sprintf("%s@%dd%s from now", a.ToAbsPath(fs), days, remainder)
|
|
}
|
|
|
|
keep, remove := prune.RetentionPolicy.FitEntries(ctx.Now, adaptors)
|
|
for _, a := range remove {
|
|
r := a.(retentionGridAdaptor)
|
|
log.Printf("remove %s", describe(r))
|
|
// do echo what we'll do and exec zfs destroy if not dry run
|
|
// special handling for EBUSY (zfs hold)
|
|
// error handling for clones? just echo to cli, skip over, and exit with non-zero status code (we're idempotent)
|
|
if !ctx.DryRun {
|
|
err := zfs.ZFSDestroy(r.ToAbsPath(fs))
|
|
if err != nil {
|
|
// handle
|
|
log.Printf("error: %s", err)
|
|
}
|
|
}
|
|
}
|
|
for _, a := range keep {
|
|
r := a.(retentionGridAdaptor)
|
|
log.Printf("would keep %s", describe(r))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|