package Brackup::Target::Amazon;
use strict;
use warnings;
use base 'Brackup::Target';
use Net::Amazon::S3 0.37;
# fields in object:
# s3 -- Net::Amazon::S3
# access_key_id
# sec_access_key_id
# chunk_bucket : $self->{access_key_id} . "-chunks";
# backup_bucket : $self->{access_key_id} . "-backups";
#
sub new {
my ($class, $confsec) = @_;
my $self = $class->SUPER::new($confsec);
$self->{access_key_id} = $confsec->value("aws_access_key_id")
or die "No 'aws_access_key_id'";
$self->{sec_access_key_id} = $confsec->value("aws_secret_access_key")
or die "No 'aws_secret_access_key'";
$self->_common_s3_init;
my $s3 = $self->{s3};
my $buckets = $s3->buckets or die "Failed to get bucket list";
unless (grep { $_->{bucket} eq $self->{chunk_bucket} } @{ $buckets->{buckets} }) {
$s3->add_bucket({ bucket => $self->{chunk_bucket} })
or die "Chunk bucket creation failed\n";
}
unless (grep { $_->{bucket} eq $self->{backup_bucket} } @{ $buckets->{buckets} }) {
$s3->add_bucket({ bucket => $self->{backup_bucket} })
or die "Backup bucket creation failed\n";
}
return $self;
}
sub _common_s3_init {
my $self = shift;
$self->{chunk_bucket} = $self->{access_key_id} . "-chunks";
$self->{backup_bucket} = $self->{access_key_id} . "-backups";
$self->{s3} = Net::Amazon::S3->new({
aws_access_key_id => $self->{access_key_id},
aws_secret_access_key => $self->{sec_access_key_id},
});
}
# ghetto
sub _prompt {
my ($q) = @_;
print "$q";
my $ans = <STDIN>;
$ans =~ s/^\s+//;
$ans =~ s/\s+$//;
return $ans;
}
sub new_from_backup_header {
my ($class, $header) = @_;
my $accesskey = ($ENV{'AWS_KEY'} || _prompt("Your Amazon AWS access key? "))
or die "Need your Amazon access key.\n";
my $sec_accesskey = ($ENV{'AWS_SEC_KEY'} || _prompt("Your Amazon AWS secret access key? "))
or die "Need your Amazon secret access key.\n";
my $self = bless {}, $class;
$self->{access_key_id} = $accesskey;
$self->{sec_access_key_id} = $sec_accesskey;
$self->_common_s3_init;
return $self;
}
sub has_chunk {
my ($self, $chunk) = @_;
my $dig = $chunk->backup_digest; # "sha1:sdfsdf" format scalar
my $res = eval { $self->{s3}->head_key({ bucket => $self->{chunk_bucket}, key => $dig }); };
return 0 unless $res;
return 0 if $@ && $@ =~ /key not found/;
return 0 unless $res->{content_type} eq "x-danga/brackup-chunk";
return 1;
}
sub load_chunk {
my ($self, $dig) = @_;
my $bucket = $self->{s3}->bucket($self->{chunk_bucket});
my $val = $bucket->get_key($dig)
or return 0;
return \ $val->{value};
}
sub store_chunk {
my ($self, $chunk) = @_;
my $dig = $chunk->backup_digest;
my $blen = $chunk->backup_length;
my $chunkref = $chunk->chunkref;
my $try = sub {
eval {
$self->{s3}->add_key({
bucket => $self->{chunk_bucket},
key => $dig,
value => $$chunkref,
content_type => 'x-danga/brackup-chunk',
});
};
};
my $rv;
my $n_fails = 0;
while (!$rv && $n_fails < 5) {
$rv = $try->();
last if $rv;
# transient failure?
$n_fails++;
warn "Error uploading chunk $chunk [$@]... will do retry \#$n_fails in 5 seconds ...\n";
sleep 5;
}
unless ($rv) {
warn "Error uploading chunk again: " . $self->{s3}->errstr . "\n";
return 0;
}
return 1;
}
sub delete_chunk {
my ($self, $dig) = @_;
my $bucket = $self->{s3}->bucket($self->{chunk_bucket});
return $bucket->delete_key($dig);
}
# returns a list of names of all chunks
sub chunks {
my $self = shift;
my $chunks = $self->{s3}->list_bucket_all({ bucket => $self->{chunk_bucket} });
return map { $_->{key} } @{ $chunks->{keys} };
}
sub store_backup_meta {
my ($self, $name, $file) = @_;
my $rv = eval { $self->{s3}->add_key({
bucket => $self->{backup_bucket},
key => $name,
value => $file,
content_type => 'x-danga/brackup-meta',
})};
return $rv;
}
sub backups {
my $self = shift;
my @ret;
my $backups = $self->{s3}->list_bucket_all({ bucket => $self->{backup_bucket} });
foreach my $backup (@{ $backups->{keys} }) {
push @ret, Brackup::TargetBackupStatInfo->new($self, $backup->{key},
time => $backup->{last_modified},
size => $backup->{size});
}
return @ret;
}
sub get_backup {
my $self = shift;
my ($name, $output_file) = @_;
my $bucket = $self->{s3}->bucket($self->{backup_bucket});
my $val = $bucket->get_key($name)
or return 0;
$output_file ||= "$name.brackup";
open(my $out, ">$output_file") or die "Failed to open $output_file: $!\n";
my $outv = syswrite($out, $val->{value});
die "download/write error" unless $outv == do { use bytes; length $val->{value} };
close $out;
return 1;
}
sub delete_backup {
my $self = shift;
my $name = shift;
my $bucket = $self->{s3}->bucket($self->{backup_bucket});
return $bucket->delete_key($name);
}
1;
=head1 NAME
Brackup::Target::Amazon - backup to Amazon's S3 service
=head1 EXAMPLE
In your ~/.brackup.conf file:
[TARGET:amazon]
type = Amazon
aws_access_key_id = ...
aws_secret_access_key = ....
=head1 CONFIG OPTIONS
=over
=item B<type>
Must be "B<Amazon>".
=item B<aws_access_key_id>
Your Amazon Web Services access key id.
=item B<aws_secret_access_key>
Your Amazon Web Services secret password for the above access key. (not your Amazon password)
=back
=head1 SEE ALSO
L<Brackup::Target>
L<Net::Amazon::S3> -- required module to use Brackup::Target::Amazon
syntax highlighted by Code2HTML, v. 0.9.1