Blame | Last modification | View Log | Download | RSS feed
#! /usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
use Time::Local qw( timelocal_posix timegm_posix );
use Getopt::Long;
Getopt::Long::Configure ("bundling");
use constant SECONDS_IN_DAY => 86400;
my $config = {};
# runs a command, redirecting stderr to stdout (which it ignores)
# then returns 0 and $output on success.
# if error, returns error code and string describing error
sub run {
my $command = shift;
my $output = qx/$command 2>&1/;
if ($? == -1) {
return (-1,"failed to execute: $!");
} elsif ($? & 127) {
return ($?, sprintf "child died with signal %d, %s coredump",
($? & 127), ($? & 128) ? 'with' : 'without' );
} else {
return ($? >> 8, sprintf "child exited with value %d", $? >> 8 ) if $? >> 8;
}
return (0,$output);
}
sub dateToUnixTime {
my ( $date, $time ) = @_;
my ($year,$month,$day) = split( /\D/, $date );
my ($hour,$minute,$second) = split( /\D/, $time );
$second = '00' unless $second;
return timelocal_posix( $second,$minute,$hour,$day,$month-1,$year-1900);
}
sub getSnaps {
my $config = shift;
my %snaps;
my ($error,$output) = &run(
# 'H' uses a single tab between fields
"zfs list -H" .
# if they asked for recursion, add the 'r' flag
( $config->{'recurse'} ? 'r' : '' ) .
# type snap, and the dataset name
"t snap $config->{dataset}" );
# die $output;
my @snaps = split( "\n", $output );
chomp @snaps;
while ( my $thisSnap = pop @snaps ) {
my ($name,$used,$avail,$refer,$mountpoint) = split( "\t", $thisSnap );
if ( $name =~ m/$config->{'filter'}/ ) {
my $created = &dateToUnixTime( $1,$2 );
$snaps{$name}->{'date'} = $created if ( $created < $config->{'pruneBefore'} );
}
}
return \%snaps;
}
sub makeDestroyCommands {
my @commands;
# die Dumper( @_ ) . "\n";
while ( my $snap = shift ) {
push @commands, "zfs destroy $snap";
}
return \@commands;
}
sub help {
my $message = shift;
print "\n== $message ==\n\n" if $message;
my $help = <<" EOF";
usage: $0 [options] dataset
--dataset - Dataset to process. May also be defined with no flags
REQUIRED
--recurse - If set, will recurse through child datasets
default: do not recurse
--ttl - Time To Live, in days. Anything older will be destroyed
default: 90 days
--filter - Perl Regular Expression to match snapshots. Non-match are
ignored
default: matches YYYY.MM.DD.HH.MM where . is any character
--dryrun - Do not actually do the destroy. -n is short version
--verbose - Be verbose (only one level)
Simple script to prune old snapshots. Snapshots must have date/time
stamp, which must be surrounded by parenthesis in filter.
Examples:
$0 storage
destroy all snapshots older than 90 days (default) in dataset storage
$0 -r -d 'storage'
destroy all snapshots older than 90 days in dataset storage and all
child datasets
$0 -rvt 120 -f 'weekly_(\\d{4}-\\d{2}-\\d{2})_(\\d{2}-\\d{2})' storage
destroy all snapshots older and 120 days from storage/ and all
children which have the form weekly_YYYY-MM-DD_HH-MM
EOF
$help =~ s/^ {6}//gm;
print $help;
exit();
}
# set some defaults
# default time to live, in days. Everything older will be destroyed
$config->{'ttl'} = 90;
# regex to match snapshot names. Default is just YYYY?MM?DD?HH?MM, where ? is aany delimiter
$config->{'filter'} = '(\d{4}.\d{2}.\d{2}).(\d{2}.\d{2})';
GetOptions( $config,
'dataset|d=s',
'ttl|t=i',
'filter|f=s',
'dryrun|n',
'recurse|r',
'verbose|v',
'help|h'
);
$config->{'dataset'} = @ARGV ? shift : '' unless $config->{'dataset'};
#print Dumper( $config );
&help() if $config->{'help'};
&help( 'No dataset defined') unless $config->{'dataset'};
$config->{'now'} = time;
printf "Starting prune of ZFS Snapshots at %s\n", scalar localtime( $config->{'now'} ) if $config->{'verbose'};
$config->{'pruneBefore'} = $config->{'now'} - $config->{'ttl'} * SECONDS_IN_DAY;
printf "Pruning before %s \n", scalar localtime($config->{'pruneBefore'}) if $config->{'verbose'};
$config->{'snaps'} = &getSnaps( $config );
#die Dumper( $config->{'snaps'} ) . "\n";
my $commands = &makeDestroyCommands( sort keys %{ $config->{'snaps'} } );
#die Dumper( $commands ) . "\n";
if ( @$commands ) {
for ( my $i=0; $i < @$commands; $i++ ) {
print $commands->[$i] . "\n" if $config->{'dryrun'} || $config->{'verbose'};
next if $config->{'dryrun'};
#my ($error,$output) = &run ($commands->[$i]);
#print "Error running command\n$commands->[$i]\n$output\n" if ( $error );
}
} else {
print "No snapshots found to destroy\n" if $config->{'verbose'};
}
printf "Prune completed at %s\n", scalar localtime( time ) if $config->{'verbose'};
1;
Generated by GNU Enscript 1.6.5.90.