File Coverage

blib/lib/AWS/S3/Bucket.pm
Criterion Covered Total %
statement 51 58 87.9
branch 9 18 50.0
condition 6 12 50.0
subroutine 13 13 100.0
pod 3 6 50.0
total 82 107 76.6


line stmt bran cond sub pod time code
1              
2             package AWS::S3::Bucket;
3              
4 6     6   45 use Carp 'confess';
  6         13  
  6         521  
5 6     6   42 use Moose;
  6         10  
  6         53  
6 6     6   50359 use IO::Socket::INET;
  6         14  
  6         82  
7 6     6   4579 use AWS::S3::ResponseParser;
  6         13  
  6         265  
8 6     6   4192 use AWS::S3::FileIterator;
  6         47  
  6         663  
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 6     6   65 use MooseX::Types -declare => [qw/ACLShorts/];
  6         16  
  6         49  
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 1     1 0 105 my ( $s, $cloudfront_dist ) = @_;
142              
143 1 50       46 $cloudfront_dist->isa( 'AWS::CloudFront::Distribution' )
144             or die "Usage: enable_cloudfront_distribution( <AWS::CloudFront::Distribution object> )";
145              
146 0         0 my $ident = $cloudfront_dist->cf->create_origin_access_identity( Comment => "Access to s3://" . $s->name, );
147 0         0 $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 0         0 "CanonicalUser":"@{[ $ident->S3CanonicalUserId ]}"
156             },
157             "Action": "s3:GetObject",
158 0         0 "Resource": "arn:aws:s3:::@{[ $s->name ]}/*"
159             }
160             ]
161             }
162             JSON
163             } # end enable_cloudfront_distribution()
164              
165             sub files {
166 1     1 1 7 my ( $s, %args ) = @_;
167              
168 1         14 return AWS::S3::FileIterator->new( %args, bucket => $s, );
169             } # end files()
170              
171             sub file {
172 1     1 1 94 my ( $s, $key ) = @_;
173              
174 1         2 my $type = 'GetFileInfo';
175              
176 1 50       14 my $parser = $s->_get_property( $type, key => $key )
177             or return;
178              
179 1         45 my $res = $parser->response;
180 1 50       37 confess "Cannot get file: ", $res->as_string, " " unless $res->is_success;
181 1 50 50     12 return AWS::S3::File->new(
      50        
      50        
      50        
      50        
      50        
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 1     1 0 6 my ( $s, %args ) = @_;
194              
195 1         41 my $file = AWS::S3::File->new(
196             %args,
197             bucket => $s
198             );
199 1         40 $file->contents( $args{contents} );
200 1         12 return $file;
201             } # end add_file()
202              
203             sub delete {
204 1     1 0 10 my ( $s ) = @_;
205              
206 1         3 my $type = 'DeleteBucket';
207              
208 1         42 my $req = $s->s3->request( $type, bucket => $s->name, );
209 1         8 my $response = $req->request();
210              
211 1 50       206 if ( my $msg = $response->friendly_error() ) {
212 0         0 die $msg;
213             } # end if()
214              
215 1         43 return 1;
216             } # end delete()
217              
218             # Working as of v0.023
219             sub delete_multi {
220 1     1 1 6 my ( $s, @keys ) = @_;
221              
222 1 50       5 die "You can only delete up to 1000 keys at once"
223             if @keys > 1000;
224 1         3 my $type = 'DeleteMulti';
225              
226 1         51 my $req = $s->s3->request(
227             $type,
228             bucket => $s->name,
229             keys => \@keys,
230             );
231 1         7 my $response = $req->request();
232              
233 1 50       268 if ( my $msg = $response->friendly_error() ) {
234 0         0 die $msg;
235             } # end if()
236              
237 1         46 return 1;
238             } # end delete_multi()
239              
240             sub _get_property {
241 1     1   4 my ( $s, $type, %args ) = @_;
242              
243 1         51 my $req = $s->s3->request( $type, bucket => $s->name, %args );
244 1         9 my $response = $req->request();
245              
246 1 50       231 return if $response->response->code == 404;
247              
248 1 50       50 if ( my $msg = $response->friendly_error() ) {
249 0         0 die $msg;
250             } # end if()
251              
252 1         48 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