File Coverage

blib/lib/AWS/S3/Bucket.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1              
2             package AWS::S3::Bucket;
3              
4 3     3   13718 use Carp 'confess';
  3         4  
  3         177  
5 3     3   11 use Moose;
  3         3  
  3         18  
6 3     3   9979 use IO::Socket::INET;
  3         5  
  3         28  
7 3     3   1467 use AWS::S3::ResponseParser;
  0            
  0            
8             use AWS::S3::FileIterator;
9              
10             has 's3' => (
11             is => 'ro',
12             isa => 'AWS::S3',
13             required => 1,
14             );
15              
16             has 'name' => (
17             is => 'ro',
18             isa => 'Str',
19             required => 1,
20             );
21              
22             has 'creation_date' => (
23             is => 'ro',
24             isa => 'Str',
25             required => 0,
26             );
27              
28             use MooseX::Types -declare => [qw/ACLShorts/];
29              
30             has 'acl' => (
31             is => 'rw',
32             isa => 'Str',
33             required => 0,
34             lazy => 1,
35             clearer => '_clear_acl',
36             default => sub {
37             my $self = shift;
38             my $type = 'GetBucketAccessControl';
39             return $self->_get_property( $type )->response->decoded_content();
40             },
41             trigger => sub {
42             my ( $self, $new_val, $old_val ) = @_;
43              
44             my %shorts = map { $_ => undef } qw(
45             private public-read public-read-write authenticated-read
46             );
47              
48             my %acl = ();
49             if ( $new_val =~ m{<} ) {
50             $acl{acl_xml} = $new_val;
51             }
52             elsif ( exists $shorts{$new_val} ) {
53             $acl{acl_short} = $new_val;
54             }
55             else {
56             die "Attempt to set an invalid value for acl: '$new_val'";
57             }
58              
59             my $type = 'SetBucketAccessControl';
60             my $req = $self->s3->request( $type, %acl, bucket => $self->name, );
61             my $response = $req->request();
62              
63             return if $response->response->code == 404;
64              
65             if ( my $msg = $response->friendly_error() ) {
66             die $msg;
67             } # end if()
68              
69             $self->_clear_acl;
70             }
71             );
72              
73             has 'location_constraint' => (
74             is => 'ro',
75             isa => 'Str',
76             required => 0,
77             lazy => 1,
78             default => sub {
79             my $self = shift;
80              
81             my $type = 'GetBucketLocationConstraint';
82             my $response = $self->_get_property( $type );
83              
84             my $constraint = $response->xpc->findvalue( '//s3:LocationConstraint' );
85             if ( defined $constraint && $constraint eq '' ) {
86             return;
87             } else {
88             return $constraint;
89             }
90             }
91             );
92              
93             has 'policy' => (
94             is => 'rw',
95             isa => 'Str',
96             required => 0,
97             lazy => 1,
98             clearer => '_clear_policy',
99             default => sub {
100             my $self = shift;
101              
102             my $type = 'GetBucketPolicy';
103             my $req = $self->s3->request( $type, bucket => $self->name, );
104             my $response = $req->request();
105              
106             eval { $response->_parse_errors };
107             if ( my $msg = $response->friendly_error() ) {
108             if ( $response->error_code eq 'NoSuchBucketPolicy' ) {
109             return '';
110             } else {
111             die $msg;
112             } # end if()
113             } # end if()
114              
115             return $response->response->decoded_content();
116             },
117             trigger => sub {
118             my ( $self, $policy ) = @_;
119              
120             my $type = 'SetBucketPolicy';
121             my $req = $self->s3->request(
122             $type,
123             bucket => $self->name,
124             policy => $policy,
125             );
126             my $response = $req->request();
127              
128             #warn "NewPolicy:($policy).......\n";
129             #warn $response->response->as_string;
130             if ( my $msg = $response->friendly_error() ) {
131             die $msg;
132             } # end if()
133              
134             $self->_clear_policy;
135              
136             }
137             );
138              
139             # XXX: Not tested.
140             sub enable_cloudfront_distribution {
141             my ( $s, $cloudfront_dist ) = @_;
142              
143             $cloudfront_dist->isa( 'AWS::CloudFront::Distribution' )
144             or die "Usage: enable_cloudfront_distribution( <AWS::CloudFront::Distribution object> )";
145              
146             my $ident = $cloudfront_dist->cf->create_origin_access_identity( Comment => "Access to s3://" . $s->name, );
147             $s->policy( <<"JSON");
148             {
149             "Version":"2008-10-17",
150             "Id":"PolicyForCloudFrontPrivateContent",
151             "Statement":[{
152             "Sid": "Grant a CloudFront Origin Identity access to support private content",
153             "Effect":"Allow",
154             "Principal": {
155             "CanonicalUser":"@{[ $ident->S3CanonicalUserId ]}"
156             },
157             "Action": "s3:GetObject",
158             "Resource": "arn:aws:s3:::@{[ $s->name ]}/*"
159             }
160             ]
161             }
162             JSON
163             } # end enable_cloudfront_distribution()
164              
165             sub files {
166             my ( $s, %args ) = @_;
167              
168             return AWS::S3::FileIterator->new( %args, bucket => $s, );
169             } # end files()
170              
171             sub file {
172             my ( $s, $key ) = @_;
173              
174             my $type = 'GetFileInfo';
175              
176             my $parser = $s->_get_property( $type, key => $key )
177             or return;
178              
179             my $res = $parser->response;
180             confess "Cannot get file: ", $res->as_string, " " unless $res->is_success;
181             return AWS::S3::File->new(
182             bucket => $s,
183             key => $key || undef,
184             size => $res->header( 'content-length' ) || 0,
185             contenttype => $res->header( 'content-type' ) || 'application/octet-stream',
186             etag => $res->header( 'etag' ) || undef,
187             lastmodified => $res->header( 'last-modified' ) || undef,
188             is_encrypted => ( $res->header( 'x-amz-server-side-encryption' ) || '' ) eq 'AES256' ? 1 : 0,
189             );
190             } # end file()
191              
192             sub add_file {
193             my ( $s, %args ) = @_;
194              
195             my $file = AWS::S3::File->new(
196             %args,
197             bucket => $s
198             );
199             $file->contents( $args{contents} );
200             return $file;
201             } # end add_file()
202              
203             sub delete {
204             my ( $s ) = @_;
205              
206             my $type = 'DeleteBucket';
207              
208             my $req = $s->s3->request( $type, bucket => $s->name, );
209             my $response = $req->request();
210              
211             if ( my $msg = $response->friendly_error() ) {
212             die $msg;
213             } # end if()
214              
215             return 1;
216             } # end delete()
217              
218             # Working as of v0.023
219             sub delete_multi {
220             my ( $s, @keys ) = @_;
221              
222             die "You can only delete up to 1000 keys at once"
223             if @keys > 1000;
224             my $type = 'DeleteMulti';
225              
226             my $req = $s->s3->request(
227             $type,
228             bucket => $s->name,
229             keys => \@keys,
230             );
231             my $response = $req->request();
232              
233             if ( my $msg = $response->friendly_error() ) {
234             die $msg;
235             } # end if()
236              
237             return 1;
238             } # end delete_multi()
239              
240             sub _get_property {
241             my ( $s, $type, %args ) = @_;
242              
243             my $req = $s->s3->request( $type, bucket => $s->name, %args );
244             my $response = $req->request();
245              
246             return if $response->response->code == 404;
247              
248             if ( my $msg = $response->friendly_error() ) {
249             die $msg;
250             } # end if()
251              
252             return $response;
253             } # end _get_property()
254              
255             __PACKAGE__->meta->make_immutable;
256              
257             __END__
258              
259             =pod
260              
261             =head1 NAME
262              
263             AWS::S3::Bucket - Object representation of S3 Buckets
264              
265             =head1 SYNOPSIS
266              
267             See L<The SYNOPSIS from AWS::S3|AWS::S3/SYNOPSIS> for usage details.
268              
269             =head1 CONSTRUCTOR
270              
271             Call C<new()> with the following parameters.
272              
273             =head1 PUBLIC PROPERTIES
274              
275             =head2 s3
276              
277             Required. An L<AWS::S3> object.
278              
279             Read-only.
280              
281             =head2 name
282              
283             Required. String.
284              
285             The name of the bucket.
286              
287             Read-only.
288              
289             =head2 creation_date
290              
291             String. Returned from the S3 service itself.
292              
293             Read-only.
294              
295             =head2 acl
296              
297             String. Returns XML string.
298              
299             Read-only.
300              
301             See also L<PUT Bucket ACL|http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?RESTBucketPUTacl.html>
302              
303             =head2 location_constraint
304              
305             String. Read-only.
306              
307             =over 4
308              
309             =item * EU
310              
311             =item * us-west-1
312              
313             =item * us-west-2
314              
315             =item * ap-southeast-1
316              
317             =item * ap-northeast-1
318              
319             =back
320              
321             The default value is undef which means 'US'.
322              
323             See also L<PUT Bucket|http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?RESTBucketPUT.html>
324              
325             =head2 policy
326              
327             Read-only. String of JSON.
328              
329             Looks something like this:
330              
331             {
332             "Version":"2008-10-17",
333             "Id":"aaaa-bbbb-cccc-dddd",
334             "Statement" : [
335             {
336             "Effect":"Deny",
337             "Sid":"1",
338             "Principal" : {
339             "AWS":["1-22-333-4444","3-55-678-9100"]
340             },
341             "Action":["s3:*"],
342             "Resource":"arn:aws:s3:::bucket/*",
343             }
344             ]
345             }
346              
347             See also L<GET Bucket Policy|http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?RESTBucketGETpolicy.html>
348              
349             =head1 PUBLIC METHODS
350              
351             =head2 files( page_size => $size, page_number => $number, [[marker => $marker,] pattern => qr/$pattern/ ] )
352              
353             Returns a L<AWS::S3::FileIterator> object with the supplied arguments.
354              
355             Use the L<AWS::S3::FileIterator> to page through your results.
356              
357             =head2 file( $key )
358              
359             Finds the file with that C<$key> and returns an L<AWS::S3::File> object for it.
360              
361             =head2 delete_multi( \@keys )
362              
363             Given an ArrayRef of the keys you want to delete, C<delete_multi> can only delete
364             up to 1000 keys at once. Empty your buckets for deletion quickly like this:
365              
366             my $deleted = 0;
367             my $bucket = $s->bucket( 'foobar' );
368             my $iter = $bucket->files( page_size => 1000, page_number => 1 );
369             while( my @files = $iter->next_page )
370             {
371             $bucket->delete_multi( map { $_->key } @files );
372             $deleted += @files;
373             # Reset to page 1:
374             $iter->page_number( 1 );
375             warn "Deleted $deleted files so far\n";
376             }# end while()
377            
378             # NOW you can delete your bucket (if you want) because it's empty:
379             $bucket->delete;
380              
381             =head1 SEE ALSO
382              
383             L<The Amazon S3 API Documentation|http://docs.amazonwebservices.com/AmazonS3/latest/API/>
384              
385             L<AWS::S3::Bucket>
386              
387             L<AWS::S3::File>
388              
389             L<AWS::S3::FileIterator>
390              
391             L<AWS::S3::Owner>
392              
393             =cut
394