root/trunk/lib/Brackup/Target/Amazon.pm @ 165

Revision 165, 6.5 kB (checked in by mart, 23 months ago)

added the aws_prefix option to configure multiple backup targets
on a single Amazon account.

Patch from Alessandro Ranellucci.

Line 
1package Brackup::Target::Amazon;
2use strict;
3use warnings;
4use base 'Brackup::Target';
5use Net::Amazon::S3 0.37;
6
7# fields in object:
8#   s3  -- Net::Amazon::S3
9#   access_key_id
10#   sec_access_key_id
11#   prefix
12#   chunk_bucket : $self->{prefix} . "-chunks";
13#   backup_bucket : $self->{prefix} . "-backups";
14#
15
16sub new {
17    my ($class, $confsec) = @_;
18    my $self = $class->SUPER::new($confsec);
19
20    $self->{access_key_id}     = $confsec->value("aws_access_key_id")
21        or die "No 'aws_access_key_id'";
22    $self->{sec_access_key_id} = $confsec->value("aws_secret_access_key")
23        or die "No 'aws_secret_access_key'";
24    $self->{prefix} = $confsec->value("aws_prefix") || $self->{access_key_id};
25
26    $self->_common_s3_init;
27
28    my $s3      = $self->{s3};
29    my $buckets = $s3->buckets or die "Failed to get bucket list";
30
31    unless (grep { $_->{bucket} eq $self->{chunk_bucket} } @{ $buckets->{buckets} }) {
32        $s3->add_bucket({ bucket => $self->{chunk_bucket} })
33            or die "Chunk bucket creation failed\n";
34    }
35
36    unless (grep { $_->{bucket} eq $self->{backup_bucket} } @{ $buckets->{buckets} }) {
37        $s3->add_bucket({ bucket => $self->{backup_bucket} })
38            or die "Backup bucket creation failed\n";
39    }
40
41    return $self;
42}
43
44sub _common_s3_init {
45    my $self = shift;
46    $self->{chunk_bucket}  = $self->{prefix} . "-chunks";
47    $self->{backup_bucket} = $self->{prefix} . "-backups";
48    $self->{s3}            = Net::Amazon::S3->new({
49        aws_access_key_id     => $self->{access_key_id},
50        aws_secret_access_key => $self->{sec_access_key_id},
51    });
52}
53
54# ghetto
55sub _prompt {
56    my ($q) = @_;
57    print "$q";
58    my $ans = <STDIN>;
59    $ans =~ s/^\s+//;
60    $ans =~ s/\s+$//;
61    return $ans;
62}
63
64sub new_from_backup_header {
65    my ($class, $header) = @_;
66
67    my $accesskey     = ($ENV{'AWS_KEY'} || _prompt("Your Amazon AWS access key? "))
68        or die "Need your Amazon access key.\n";
69    my $sec_accesskey = ($ENV{'AWS_SEC_KEY'} || _prompt("Your Amazon AWS secret access key? "))
70        or die "Need your Amazon secret access key.\n";
71    my $prefix = ($ENV{'AWS_PREFIX'} || _prompt("Your Amazon AWS prefix? (Leave empty if none) "));
72
73    my $self = bless {}, $class;
74    $self->{access_key_id}     = $accesskey;
75    $self->{sec_access_key_id} = $sec_accesskey;
76    $self->{prefix}            = $prefix || $self->{access_key_id};
77    $self->_common_s3_init;
78    return $self;
79}
80
81sub has_chunk {
82    my ($self, $chunk) = @_;
83    my $dig = $chunk->backup_digest;   # "sha1:sdfsdf" format scalar
84
85    my $res = eval { $self->{s3}->head_key({ bucket => $self->{chunk_bucket}, key => $dig }); };
86    return 0 unless $res;
87    return 0 if $@ && $@ =~ /key not found/;
88    return 0 unless $res->{content_type} eq "x-danga/brackup-chunk";
89    return 1;
90}
91
92sub load_chunk {
93    my ($self, $dig) = @_;
94    my $bucket = $self->{s3}->bucket($self->{chunk_bucket});
95
96    my $val = $bucket->get_key($dig)
97        or return 0;
98    return \ $val->{value};
99}
100
101sub store_chunk {
102    my ($self, $chunk) = @_;
103    my $dig = $chunk->backup_digest;
104    my $blen = $chunk->backup_length;
105    my $chunkref = $chunk->chunkref;
106
107    my $try = sub {
108        eval {
109            $self->{s3}->add_key({
110                bucket        => $self->{chunk_bucket},
111                key           => $dig,
112                value         => $$chunkref,
113                content_type  => 'x-danga/brackup-chunk',
114            });
115        };
116    };
117
118    my $rv;
119    my $n_fails = 0;
120    while (!$rv && $n_fails < 5) {
121        $rv = $try->();
122        last if $rv;
123
124        # transient failure?
125        $n_fails++;
126        warn "Error uploading chunk $chunk [$@]... will do retry \#$n_fails in 5 seconds ...\n";
127        sleep 5;
128    }
129    unless ($rv) {
130        warn "Error uploading chunk again: " . $self->{s3}->errstr . "\n";
131        return 0;
132    }
133    return 1;
134}
135
136sub delete_chunk {
137    my ($self, $dig) = @_;
138    my $bucket = $self->{s3}->bucket($self->{chunk_bucket});
139    return $bucket->delete_key($dig);
140}
141
142# returns a list of names of all chunks
143sub chunks {
144    my $self = shift;
145   
146    my $chunks = $self->{s3}->list_bucket_all({ bucket => $self->{chunk_bucket} });
147    return map { $_->{key} } @{ $chunks->{keys} };
148}
149
150sub store_backup_meta {
151    my ($self, $name, $file) = @_;
152
153    my $rv = eval { $self->{s3}->add_key({
154        bucket        => $self->{backup_bucket},
155        key           => $name,
156        value         => $file,
157        content_type  => 'x-danga/brackup-meta',
158    })};
159
160    return $rv;
161}
162
163sub backups {
164    my $self = shift;
165
166    my @ret;
167    my $backups = $self->{s3}->list_bucket_all({ bucket => $self->{backup_bucket} });
168    foreach my $backup (@{ $backups->{keys} }) {
169        push @ret, Brackup::TargetBackupStatInfo->new($self, $backup->{key},
170                                                      time => $backup->{last_modified},
171                                                      size => $backup->{size});
172    }
173    return @ret;
174}
175
176sub get_backup {
177    my $self = shift;
178    my ($name, $output_file) = @_;
179       
180    my $bucket = $self->{s3}->bucket($self->{backup_bucket});
181    my $val = $bucket->get_key($name)
182        or return 0;
183       
184        $output_file ||= "$name.brackup";
185    open(my $out, ">$output_file") or die "Failed to open $output_file: $!\n";
186    my $outv = syswrite($out, $val->{value});
187    die "download/write error" unless $outv == do { use bytes; length $val->{value} };
188    close $out;
189    return 1;
190}
191
192sub delete_backup {
193    my $self = shift;
194    my $name = shift;
195       
196    my $bucket = $self->{s3}->bucket($self->{backup_bucket});
197    return $bucket->delete_key($name);
198}
199
2001;
201
202=head1 NAME
203
204Brackup::Target::Amazon - backup to Amazon's S3 service
205
206=head1 EXAMPLE
207
208In your ~/.brackup.conf file:
209
210  [TARGET:amazon]
211  type = Amazon
212  aws_access_key_id  = ...
213  aws_secret_access_key =  ....
214  aws_prefix =  ....
215
216=head1 CONFIG OPTIONS
217
218All options may be omitted unless specified.
219
220=over
221
222=item B<type>
223
224I<(Mandatory.)> Must be "B<Amazon>".
225
226=item B<aws_access_key_id>
227
228I<(Mandatory.)> Your Amazon Web Services access key id.
229
230=item B<aws_secret_access_key>
231
232I<(Mandatory.)> Your Amazon Web Services secret password for the above access key.  (not your Amazon password)
233
234=item B<aws_prefix>
235
236If you want to setup multiple backup targets on a single Amazon account you can
237use different prefixes. This string is used to name the S3 buckets created by
238Brackup. If not specified it defaults to the AWS access key id.
239
240=back
241
242=head1 SEE ALSO
243
244L<Brackup::Target>
245
246L<Net::Amazon::S3> -- required module to use Brackup::Target::Amazon
247
Note: See TracBrowser for help on using the browser.