Codebase list libamazon-s3-perl / b90274a
0.61 - delete_keys(), refactoring, unit tests Rob Lauer 1 year, 1 month ago
16 changed file(s) with 1383 addition(s) and 909 deletion(s). Raw diff Collapse all Expand all
0 Wed Mar 29 08:12:48 2023 Rob Lauer <rlauer6@comcast.net>
1
2 [0.61 - delete_keys(), refactoring]:
3 * s3-perl.pl
4 - added ASCII table output, refactored
5 * NEWS.md: updated
6 * src/main/perl/lib/Amazon/S3.pm.in
7 - some perlcritic refactoring
8 - pod updates
9 (list_bucket): use different markers for v1, v2
10 (list_bucket_all)
11 - throw $EVAL_ERROR "The server has stopped responding"
12 (_make_request): light refactoring
13 (_sendrequest): accept keep_root and pass to _xpc_of_content()
14 (_xpc_of_content): NoAttr => $TRUE
15 (_remember_errors): return false if no error
16 * src/main/perl/lib/Amazon/S3/Bucket.pm.in
17 - some perlcritic refactoring
18 (add_key): check reftype properly
19 (set_acl): send conten-length
20 * src/main/perl/lib/Amazon/S3/Constants.pm.in
21 - additional constants for refactoring
22 - move subs from t/01-api.t to S3TestUtils.pm
23 * src/main/perl/t/01-api.t: refactoring
24 * src/main/perl/t/02-logger.t: likewise
25 * src/main/perl/t/03-region.t: likewise
26 * src/main/perl/t/04-list-buckets.t: likewise
27 * src/main/perl/t/05-multipart-upload.t: likewise
28 * src/main/perl/t/06-list-multipart-uploads.t: likewise
29 * src/main/perl/test.localstack: converted to bash script
30 * src/main/perl/S3TestUtils.pm: new
31 * cpan/buildspec.yml: add above to distribution
32
033 Mon Mar 27 10:47:54 2023 Rob Lauer <rlauer6@comcast.net>
134
235 [0.61 - delete_keys()]:
66 notice of such bugs and the versions in which they were fixed will be
77 noted here, as well.
88
9 # perl-Amazon-S3 0.61 (2023-03-27)
9 # perl-Amazon-S3 0.61 (2023-03-30)
1010
11 > This version adds a new method for bulk deletion of keys.
11 > This version adds a new method for bulk deletion of keys. Some
12 > refactoring of the code has been done and unit tests have been
13 > cleaned up.
1214
1315 ## Enhancements
1416
66
77 # SYNOPSIS
88
9 #!/usr/bin/perl
10 use warnings;
11 use strict;
12
139 use Amazon::S3;
14
15 use vars qw/$OWNER_ID $OWNER_DISPLAYNAME/;
1610
1711 my $aws_access_key_id = "Fill me in!";
1812 my $aws_secret_access_key = "Fill me in too!";
2822
2923 # create a bucket
3024 my $bucket_name = $aws_access_key_id . '-net-amazon-s3-test';
25
3126 my $bucket = $s3->add_bucket( { bucket => $bucket_name } )
3227 or die $s3->err . ": " . $s3->errstr;
3328
3429 # store a key with a content-type and some optional metadata
3530 my $keyname = 'testing.txt';
31
3632 my $value = 'T';
33
3734 $bucket->add_key(
3835 $keyname, $value,
3936 { content_type => 'text/plain',
5047 # list keys in the bucket
5148 $response = $bucket->list
5249 or die $s3->err . ": " . $s3->errstr;
50
5351 print $response->{bucket}."\n";
52
5453 for my $key (@{ $response->{keys} }) {
5554 print "\t".$key->{key}."\n";
5655 }
5756
5857 # delete key from bucket
5958 $bucket->delete_key($keyname);
59
60 # delete multiple keys from bucket
61 $bucket->delete_keys([$key1, $key2, $key3]);
6062
6163 # delete bucket
6264 $bucket->delete_bucket;
6365
6466 # DESCRIPTION
6567
68 This documentation refers to version 0.61.
69
6670 `Amazon::S3` provides a portable client interface to Amazon Simple
6771 Storage System (S3).
6872
69 _This module is rather dated. For a much more robust and modern
70 implementation of an S3 interface try `Net::Amazon::S3`.
71 `Amazon::S3` ostensibly was intended to be a drop-in replacement for
72 `Net:Amazon::S3` that "traded some performance in return for
73 portability". That statement is no longer accurate as
74 `Net::Amazon::S3` implements much more of the S3 API and may have
75 changed the interface in ways that might break your
76 applications. However, `Net::Amazon::S3` is today dependent on
73 This module is rather dated, however with some help from a few
74 contributors it has had some recent updates. Recent changes include
75 implementations of:
76
77 - ListObjectsV2
78 - CopyObject
79 - DeleteObjects
80
81 Additionally, this module now implements Signature Version 4 signing,
82 unit tests have been updated and more documentation has been added or
83 corrected. Credentials are encrypted if you have encryption modules installed.
84
85 ## Comparison to Other Perl S3 Modules
86
87 Other implementations for accessing Amazon's S3 service include
88 `Net::Amazon::S3` and the `Paws` project. `Amazon::S3` ostensibly
89 was intended to be a drop-in replacement for `Net:Amazon::S3` that
90 "traded some performance in return for portability". That statement is
91 no longer accurate as `Amazon::S3` may have changed the interface in
92 ways that might break your applications if you are relying on
93 compatibility with `Net::Amazon::S3`.
94
95 However, `Net::Amazon::S3` and `Paws::S3` today, are dependent on
7796 `Moose` which may in fact level the playing field in terms of
7897 performance penalties that may have been introduced by recent updates
79 to `Amazon::S3`. YMMV, however, this module may still appeal to some
80 that favor simplicity of the interface and a lower number of
81 dependencies. Below is the original description of the module._
98 to `Amazon::S3`. Changes to `Amazon::S3` include the use of more
99 Perl modules in lieu of raw Perl code to increase maintainability and
100 stability as well as some refactoring. `Amazon::S3` also strives now
101 to adhere to best practices as much as possible.
102
103 `Paws::S3` is a much more robust implementation of
104 a Perl S3 interface, however this module may still appeal to
105 those that favor simplicity of the interface and a lower number of
106 dependencies. Below is the original description of the module.
82107
83108 > Amazon S3 is storage for the Internet. It is designed to
84109 > make web-scale computing easier for developers. Amazon S3
113138 # LIMITATIONS AND DIFFERENCES WITH EARLIER VERSIONS
114139
115140 As noted, this module is no longer a _drop-in_ replacement for
116 `Net::Amazon::S3` and has limitations and differences that may make
117 the use of this module in your applications
118 questionable. Additionally, one of the original intents of this fork
119 of `Net::Amazon::S3` was to reduce the dependencies and make it
120 _easy to install_. Recent changes to this module have introduced new
121 dependencies in order to improve the maintainability and provide
122 additional features. Installing CPAN modules is never easy, especially
123 when the dependencies of the dependencies are impossible to control
124 and include XS modules.
141 `Net::Amazon::S3` and has limitations and differences that may impact
142 the use of this module in your applications. Additionally, one of the
143 original intents of this fork of `Net::Amazon::S3` was to reduce the
144 number of dependencies and make it _easy to install_. Recent changes
145 to this module have introduced new dependencies in order to improve
146 the maintainability and provide additional features. Installing CPAN
147 modules is never easy, especially when the dependencies of the
148 dependencies are impossible to control and include XS modules.
125149
126150 - MINIMUM PERL
127151
146170
147171 HTML::HeadParser 2.14
148172 LWP 6.13
149 Amazon::S3 0.55
173 Amazon::S3
150174
151175 ...other versions _may_ work...YMMV.
152176
171195 parameter. This implies that you need to supply the bucket's region
172196 when signing requests for any API call that involves a specific
173197 bucket. Starting with version 0.55 of this module,
174 `Amazon::S3::Bucket` provides a new method (`region()` and accepts
198 `Amazon::S3::Bucket` provides a new method (`region()`) and accepts
175199 in the constructor a `region` parameter. If a region is not
176200 supplied, the region for the bucket will be set to the region set in
177201 the `account` object (`Amazon::S3`) that you passed to the bucket's
194218
195219 - Multipart Upload Support
196220
197 There is limited testing for multipart uploads.
221 There is some limited testing for multipart uploads.
198222
199223 For more information regarding multi-part uploads visit the link below.
200224
335359
336360 Unfortunately, while this will prevent [Net::Amazon::Signature::V4](https://metacpan.org/pod/Net%3A%3AAmazon%3A%3ASignature%3A%3AV4)
337361 from hanging on to your credentials, you credentials will be stored in
338 the [Amazon::S3](https://metacpan.org/pod/Amazon%3A%3AS3) object.
362 the `Amazon::S3` object.
339363
340364 Starting with version 0.55 of this module, if you have installed
341365 [Crypt::CBC](https://metacpan.org/pod/Crypt%3A%3ACBC) and [Crypt::Blowfish](https://metacpan.org/pod/Crypt%3A%3ABlowfish), your credentials will be
353377
354378 - 5. Do nothing...send the credentials, use the default signer.
355379
356 In this case, both the [Amazon::S3](https://metacpan.org/pod/Amazon%3A%3AS3) class and the
380 In this case, both the `Amazon::S3` class and the
357381 [Net::Amazon::Signature::V4](https://metacpan.org/pod/Net%3A%3AAmazon%3A%3ASignature%3A%3AV4) have your credentials. Caveat Emptor.
358382
359383 See Also [Amazon::Credentials](https://metacpan.org/pod/Amazon%3A%3ACredentials) for more information about safely
719743
720744 Your AWS access key
721745
722 - AWS\_ACCESS\_KEY\_SECRET
746 - AWS\_SECRET\_ACCESS\_KEY
723747
724748 Your AWS sekkr1t passkey. Be forewarned that setting this environment variable
725749 on a shared system might leak that information to another user. Be careful.
729753 Doesn't matter what you set it to. Just has to be set if you want
730754 to skip ACLs tests.
731755
756 - AMAZON\_S3\_SKIP\_PERMISSIONS
757
758 Skip tests that check for enforcement of ACLs...as of this version,
759 LocalStack for example does not support enforcement of ACLs.
760
732761 - AMAZON\_S3\_SKIP\_REGION\_CONSTRAINT\_TEST
733762
734763 Doesn't matter what you set it to. Just has to be set if you want
751780
752781 _Consider using an S3 mocking service like `minio` or `LocalStack`
753782 if you want to create real tests for your applications or this module._
783
784 Here's bash script for testing using LocalStack
785
786 #!/bin/bash
787 # -*- mode: sh; -*-
788
789 BUCKET=net-amazon-s3-test-test
790 ENDPOINT_URL=s3.localhost.localstack.cloud:4566
791
792 AMAZON_S3_EXPENSIVE_TESTS=1 \
793 AMAZON_S3_HOST=$ENDPOINT_URL \
794 AMAZON_S3_LOCALSTACK=1 \
795 AWS_ACCESS_KEY_ID=test \
796 AWS_ACCESS_SECRET_KEY=test \
797 AMAZON_S3_DOMAIN_BUCKET_NAMES=1 make test 2>&1 | tee test.log
798
799 To run the tests...clone the project and build the software.
800
801 cd src/main/perl
802 ./test.localstack
754803
755804 # ADDITIONAL INFORMATION
756805
1212 - ChangeLog
1313 - README.md
1414 - README-TESTING.md
15 - src/main/perl/S3TestUtils.pm
1516 path:
1617 pm_module: src/main/perl/lib
1718 tests: src/main/perl/t
33
44 use strict;
55 use warnings;
6
7 use locale; # for proper sorting
68
79 use Amazon::Credentials;
810 use Amazon::S3;
1012 use Data::Dumper;
1113 use English qw(-no_match_vars);
1214 use File::HomeDir;
13 use Getopt::Long qw(:config no_ignore_case);
15 use Getopt::Long qw(:config no_ignore_case);
1416 use Log::Log4perl qw(:easy);
1517
1618 use Readonly;
1820 Readonly our $TRUE => 1;
1921 Readonly our $FALSE => 0;
2022
23 Readonly our $DEFAULT_HOST => 's3.amazonaws.com';
24
25 Readonly our $EMPTY => q{};
26
2127 ########################################################################
2228 sub _bucket {
2329 ########################################################################
2531
2632 return $s3->bucket(
2733 { bucket => $bucket_name,
28 verify_region => $TRUE
34 verify_region => $TRUE,
2935 }
3036 );
3137 }
113119 ########################################################################
114120 my ( $s3, %options ) = @_;
115121
116 return $s3->list_bucket_all_v2( { bucket => $options{bucket} } );
122 my $prefix = $options{prefix};
123 my $response;
124
125 if ( defined $prefix ) {
126
127 $prefix =~ s/^\///xsm;
128
129 $response = $s3->list_bucket_all_v2(
130 { bucket => $options{bucket},
131 prefix => $prefix
132 }
133 );
134 }
135 else {
136 $response = $s3->list_bucket_all_v2( { bucket => $options{bucket} } );
137 }
138
139 return $response
140 if !$options{table};
141
142 my $data
143 = [ reverse sort { $a->{key} cmp $b->{key} } @{ $response->{keys} } ];
144
145 my $cols = [qw(key size last_modified etag)];
146
147 my $heading = $response->{bucket};
148
149 if ( $options{prefix} ) {
150 $heading = sprintf '%s/%s', $heading, $options{prefix};
151 }
152
153 my $table = easy_table(
154 data => $data,
155 columns => $cols,
156 table_options => { headingText => $heading },
157 fix_headings => $TRUE,
158 );
159
160 return $table->drawit;
117161 }
118162
119163 ########################################################################
121165 ########################################################################
122166 my ( $s3, %options ) = @_;
123167
124 return $s3->buckets();
168 my $buckets = $s3->buckets();
169
170 return
171 if !$buckets;
172
173 my $data = $buckets->{buckets};
174
175 my $table;
176
177 if ( $options{table} ) {
178
179 $table = eval {
180 use Text::ASCIITable::EasyTable;
181
182 return easy_table(
183 data => [ sort { $a->{bucket} cmp $b->{bucket} } @{$data} ],
184 columns => [qw( bucket region creation_date)],
185 table_options => { headingText => 'Buckets' },
186 fix_headings => $TRUE,
187 );
188
189 };
190 }
191
192 return $table ? $table->drawit : $data;
125193 }
126194
127195 ########################################################################
136204 -d, --debug debug output
137205 -h, --help this
138206 -H, --host default: s3.amazonaws.com
207 -o, --output json or keys when listing contents of a bucket, otherwise Dumper output
139208 -p, --profile AWS credentials profile, default is hunt for them
140209 -r, --region region, default: us-east-1
210 -t, --table output keys and bucket list as tables
141211
142212 Commands Args Description
143213 -------- ---- -----------
144214 Buckets create(-bucket) - create a new bucket
145 list(-bucket) - list the contents of a bucket
215 list(-bucket) prefix list the contents of a bucket (all) or just prefix
146216 remove(-bucket) - remove a bucket (must be empty)
147217 show-(buckets) -
148218
151221 delete(-key) key delete an object
152222 get(-key) key [filename] fetch an object and optionally store to file
153223
224 Hint: output can be shown in ASCII tables if you have Text::ASCIITable::EasyTable installed.
154225 END_OF_HELP
155226
156227 return;
160231 sub main {
161232 ########################################################################
162233
163 my %options;
234 my %options = ( output => $EMPTY );
164235
165236 GetOptions(
166 \%options, 'bucket=s', 'debug', 'host|H=s',
167 'region=s', 'help|h', 'profile=s'
237 \%options, 'bucket=s', 'debug', 'host|H=s',
238 'region=s', 'help|h', 'profile=s', 'output=s',
239 'table',
168240 );
169241
170242 if ( $options{help} ) {
171243 help();
244
172245 exit 0;
173246 }
174247
180253 }
181254 );
182255
183 my $command = lc( shift @ARGV // q{} );
256 my $command = lc( shift @ARGV // $EMPTY );
184257 $command =~ s/-(.*)$//xsm;
185258
186259 my $args = [@ARGV]; # save for debugging
187260
188 $options{key} = shift @ARGV;
261 $options{key} = shift @ARGV;
262 $options{prefix} = $options{key};
189263
190264 $options{file} = shift @ARGV;
191265 $options{name} = $options{file}; # copy key
192266
193 my $host = $options{host} // q{};
267 my $host = $options{host} // $DEFAULT_HOST;
194268 $host =~ s/^https?:\/\///xsm;
195269
196270 my $s3 = Amazon::S3->new(
204278
205279 DEBUG(
206280 sub {
207 return sprintf "%s, %s, %s\n", $s3->err // q{}, $s3->errstr // q{},
281 return sprintf "%s, %s, %s\n", $s3->err // $EMPTY,
282 $s3->errstr // $EMPTY,
208283 Dumper( [ $s3->error ] );
209284 }
210285 );
248323 );
249324 }
250325
251 print Dumper( [ 'result', $result ] );
326 if ( $options{output} eq 'json' ) {
327 print JSON->new->pretty->encode($result);
328 }
329 elsif ( ref $result ) {
330 print Dumper( [$result] );
331 }
332 else {
333 print $result;
334 }
252335 }
253336
254337 return;
0 package S3TestUtils;
1
2 use strict;
3 use warnings;
4
5 use Data::Dumper;
6 use English qw(-no_match_vars);
7 use List::Util qw(any);
8 use Readonly;
9 use Test::More;
10
11 use parent qw(Exporter);
12
13 # chars
14 Readonly our $EMPTY => q{};
15 Readonly our $SLASH => q{/};
16
17 # booleans
18 Readonly our $TRUE => 1;
19 Readonly our $FALSE => 0;
20
21 # mocking services
22 Readonly our $DEFAULT_LOCAL_STACK_HOST => 'localhost:4566';
23 Readonly our $DEFAULT_MINIO_HOST => 'localhost:9000';
24
25 # http codes
26 Readonly our $HTTP_OK => '200';
27 Readonly our $HTTP_FORBIDDEN => '403';
28 Readonly our $HTTP_CONFLICT => '409';
29
30 # misc
31 Readonly our $TEST_BUCKET_PREFIX => 'net-amazon-s3-test';
32
33 # create a domain name for this if AMAZON_S3_DNS_BUCKET_NAMES is true
34 Readonly our $MOCK_SERVICES_BUCKET_NAME => $TEST_BUCKET_PREFIX . '-test';
35
36 Readonly our $PUBLIC_READ_POLICY => <<END_OF_POLICY;
37 <Grant>
38 <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
39 xsi:type="Group">
40 <URI>http://acs.amazonaws.com/groups/global/AllUsers</URI>
41 </Grantee>
42 <Permission>READ</Permission>
43 </Grant>
44 END_OF_POLICY
45
46 our %EXPORT_TAGS = (
47 constants => [
48 qw(
49 $EMPTY
50 $SLASH
51 $TRUE
52 $FALSE
53 $DEFAULT_LOCAL_STACK_HOST
54 $HTTP_OK
55 $HTTP_CONFLICT
56 $HTTP_FORBIDDEN
57 $TEST_BUCKET_PREFIX
58 $MOCK_SERVICES_BUCKET_NAME
59 $PUBLIC_READ_POLICY
60 )
61 ],
62 subs => [
63 qw(
64 add_keys
65 check_test_bucket
66 create_bucket
67 get_s3_service
68 is_aws
69 make_bucket_name
70 set_s3_host
71 )
72 ],
73 );
74
75 our @EXPORT_OK = map { @{ $EXPORT_TAGS{$_} } } ( keys %EXPORT_TAGS );
76
77 ########################################################################
78 sub make_bucket_name {
79 ########################################################################
80 return $MOCK_SERVICES_BUCKET_NAME
81 if !is_aws();
82
83 my $suffix = eval {
84 require Data::UUID;
85
86 return lc Data::UUID->new->create_str();
87 };
88
89 $suffix //= join $EMPTY, map { ( 'A' .. 'Z', 'a' .. 'z', 0 .. 9 )[$_] }
90 map { int rand 62 } ( 0 .. 15 );
91
92 my $bucket_name = sprintf '%s-%s', $TEST_BUCKET_PREFIX, $suffix;
93
94 return $bucket_name;
95 }
96
97 ########################################################################
98 sub is_aws {
99 ########################################################################
100 return ( $ENV{AMAZON_S3_LOCALSTACK} || $ENV{AMAZON_S3_MINIO} )
101 ? $FALSE
102 : $TRUE;
103 }
104
105 ########################################################################
106 sub check_test_bucket {
107 ########################################################################
108 my ($s3) = @_;
109
110 # list all buckets that I own
111 my $response = eval { return $s3->buckets; };
112
113 if ( $EVAL_ERROR || !$response ) {
114 diag(
115 Dumper( [ error => [ $response, $s3->err, $s3->errstr, $s3->error ] ] )
116 );
117
118 BAIL_OUT($EVAL_ERROR);
119 }
120
121 my ( $owner_id, $owner_displayname )
122 = @{$response}{qw(owner_id owner_displayname)};
123
124 my $bucket_name = make_bucket_name();
125
126 my @buckets = map { $_->{bucket} } @{ $response->{buckets} };
127
128 if ( any { $_ =~ /$bucket_name/xsm } @buckets ) {
129 BAIL_OUT( 'test bucket already exists: ' . $bucket_name );
130 }
131
132 return ( $owner_id, $owner_displayname );
133 }
134
135 ########################################################################
136 sub set_s3_host {
137 ########################################################################
138 my $host = $ENV{AMAZON_S3_HOST};
139
140 $host //= 's3.amazonaws.com';
141
142 ## no critic (RequireLocalizedPunctuationVars)
143
144 if ( exists $ENV{AMAZON_S3_LOCALSTACK} ) {
145
146 $host //= $DEFAULT_LOCAL_STACK_HOST;
147
148 $ENV{AWS_ACCESS_KEY_ID} = 'test';
149
150 $ENV{AWS_SECRET_ACCESS_KEY} = 'test';
151
152 $ENV{AMAZON_S3_EXPENSIVE_TESTS} = $TRUE;
153
154 $ENV{AMAZON_S3_SKIP_PERMISSIONS} = $TRUE;
155 }
156 elsif ( exists $ENV{AMAZON_S3_MINIO} ) {
157
158 $host //= $DEFAULT_MINIO_HOST;
159
160 $ENV{AMAZON_S3_SKIP_ACLS} = $TRUE;
161
162 $ENV{AMAZON_S3_EXPENSIVE_TESTS} = $TRUE;
163
164 $ENV{AMAZON_S3_SKIP_REGION_CONSTRAINT_TEST} = $TRUE;
165 }
166
167 return $host;
168 }
169
170 ########################################################################
171 sub get_s3_service {
172 ########################################################################
173 my ($host) = @_;
174
175 my $s3 = eval {
176
177 if ( $ENV{AMAZON_S3_CREDENTIALS} ) {
178 require Amazon::Credentials;
179
180 return Amazon::S3->new(
181 { credentials => Amazon::Credentials->new,
182 host => $host,
183 secure => is_aws(),
184 dns_bucket_names => $ENV{AMAZON_S3_DNS_BUCKET_NAMES},
185 level => $ENV{DEBUG} ? 'trace' : 'error',
186 }
187 );
188
189 }
190 else {
191 return Amazon::S3->new(
192 { aws_access_key_id => $ENV{AWS_ACCESS_KEY_ID},
193 aws_secret_access_key => $ENV{AWS_SECRET_ACCESS_KEY},
194 token => $ENV{AWS_SESSION_TOKEN},
195 host => $host,
196 secure => is_aws(),
197 dns_bucket_names => $ENV{AMAZON_S3_DNS_BUCKET_NAMES},
198 level => $ENV{DEBUG} ? 'trace' : 'error',
199 }
200 );
201 }
202 };
203
204 return $s3;
205 }
206
207 ########################################################################
208 sub create_bucket {
209 ########################################################################
210 my ( $s3, $bucket_name ) = @_;
211
212 $bucket_name = $SLASH . $bucket_name;
213
214 my $bucket_obj
215 = eval { return $s3->add_bucket( { bucket => $bucket_name } ); };
216
217 return $bucket_obj;
218 }
219
220 ########################################################################
221 sub add_keys {
222 ########################################################################
223 my ( $bucket_obj, $max_keys, $prefix ) = @_;
224
225 $prefix //= q{};
226
227 foreach my $key ( 1 .. $max_keys ) {
228 my $keyname = sprintf '%stesting-%02d.txt', $prefix, $key;
229 my $value = 'T';
230
231 $bucket_obj->add_key( $keyname, $value );
232 }
233
234 return $max_keys;
235 }
236
237 1;
22 use strict;
33 use warnings;
44
5 use Amazon::S3::Constants qw{:all};
5 use Amazon::S3::Constants qw(:all);
66 use Carp;
77 use Data::Dumper;
8 use Digest::MD5 qw(md5 md5_hex);
8 use Digest::MD5 qw(md5 md5_hex);
99 use Digest::MD5::File qw(file_md5 file_md5_hex);
10 use English qw(-no_match_vars);
10 use English qw(-no_match_vars);
1111 use File::stat;
1212 use IO::File;
1313 use IO::Scalar;
1414 use MIME::Base64;
1515 use Scalar::Util qw(reftype);
1616 use URI;
17 use XML::Simple;
18
19 use parent qw{Class::Accessor::Fast};
20
21 our $VERSION = '@PACKAGE_VERSION@'; ## no critic
17 use XML::Simple; ## no critic (DiscouragedModules)
18
19 use parent qw(Class::Accessor::Fast);
20
21 our $VERSION = '@PACKAGE_VERSION@'; ## no critic (RequireInterpolation)
2222
2323 __PACKAGE__->mk_accessors(
2424 qw(
2929 region
3030 logger
3131 verify_region
32 )
32 ),
3333 );
3434
3535 ########################################################################
7070 }
7171
7272 return $self;
73 } ## end sub new
73 }
7474
7575 ########################################################################
7676 sub _uri {
9090
9191 if ( $account->dns_bucket_names ) {
9292 $uri =~ s/^\///xsm;
93 } ## end if ( $self->account->dns_bucket_names)
93 }
9494
9595 return $uri;
96 } ## end sub _uri
96 }
9797
9898 ########################################################################
9999 sub add_key {
111111 $conf->{'x-amz-acl'} = $conf->{acl_short};
112112
113113 delete $conf->{acl_short};
114 } ## end if ( $conf->{acl_short...})
115
116 if ( ref $value eq 'SCALAR' ) {
114 }
115
116 if ( ref($value) && reftype($value) eq 'SCALAR' ) {
117117 my $md5_hex = file_md5_hex( ${$value} );
118118 my $md5 = pack 'H*', $md5_hex;
119119
126126 $value = _content_sub( ${$value}, $self->buffer_size );
127127
128128 $conf->{'x-amz-content-sha256'} = 'UNSIGNED-PAYLOAD';
129 } ## end if ( ref $value eq 'SCALAR')
129 }
130130 else {
131131 $conf->{'Content-Length'} ||= length $value;
132132
135135 my $md5_base64 = encode_base64($md5);
136136
137137 $conf->{'Content-MD5'} = $md5_base64;
138 } ## end else [ if ( ref $value eq 'SCALAR')]
138 }
139139
140140 # If we're pushing to a bucket that's under
141141 # DNS flux, we might get a 307 Since LWP doesn't support actually
145145 return $self->_add_key(
146146 { headers => $conf,
147147 data => $value,
148 key => $key
149 }
148 key => $key,
149 },
150150 );
151151 };
152152
156156 if ($EVAL_ERROR) {
157157 my $rsp = $account->last_response;
158158
159 if ( $rsp->code eq '301' ) {
159 if ( $rsp->code eq $HTTP_MOVED_PERMANENTLY ) {
160160 $self->region( $rsp->headers->{'x-amz-bucket-region'} );
161161 }
162162
163163 $retval = $self->_add_key(
164164 { headers => $conf,
165165 data => $value,
166 key => $key
167 }
166 key => $key,
167 },
168168 );
169169 }
170170
171171 return $retval;
172 } ## end sub add_key
172 }
173173
174174 ########################################################################
175175 sub _add_key {
186186 headers => $headers,
187187 data => $data,
188188 region => $self->region,
189 }
189 },
190190 );
191 } ## end if ( ref $value )
191 }
192192 else {
193193 return $account->_send_request_expect_nothing(
194194 { method => 'PUT',
196196 headers => $headers,
197197 data => $data,
198198 region => $self->region,
199 }
199 },
200200 );
201201 }
202 } ## end else [ if ( ref $value ) ]
202 }
203203
204204 ########################################################################
205205 sub add_key_filename {
207207 my ( $self, $key, $value, $conf ) = @_;
208208
209209 return $self->add_key( $key, \$value, $conf );
210 } ## end sub add_key_filename
210 }
211211
212212 ########################################################################
213213 sub upload_multipart_object {
288288
289289 return ( \$buffer, $bytes );
290290 };
291
292291 }
293292
294293 my $headers = $parameters{headers} || {};
298297 $logger->trace( sprintf 'multipart id: %s', $id );
299298
300299 my $part = 1;
300
301301 my %parts;
302
302303 my $key = $parameters{key};
303304
304 eval {
305 my $retval = eval {
305306 while (1) {
306307 my ( $buffer, $length ) = $parameters{callback}->();
307308 last if !$buffer;
308309
309310 my $etag = $self->upload_part_of_multipart_upload(
310 { id => $id, key => $key, data => $buffer, part => $part } );
311 { id => $id,
312 key => $key,
313 data => $buffer,
314 part => $part,
315 },
316 );
311317
312318 $parts{ $part++ } = $etag;
313319 }
342348 { region => $self->region,
343349 method => 'POST',
344350 path => $self->_uri($key) . '?uploads=',
345 headers => $conf
346 }
351 headers => $conf,
352 },
347353 );
348354
349355 my $response = $acct->_do_http($request);
353359 my $r = $acct->_xpc_of_content( $response->content );
354360
355361 return $r->{UploadId};
356 } ## end sub initiate_multipart_upload
362 }
357363
358364 #
359365 # Upload a part of a file as part of a multipart upload operation
420426 method => 'PUT',
421427 path => $self->_uri($key) . $params,
422428 headers => $conf,
423 data => $data
424 }
429 data => $data,
430 },
425431 );
426432
427433 my $response = $acct->_do_http($request);
434440 if ($etag) {
435441 $etag =~ s/^"//xsm;
436442 $etag =~ s/"$//xsm;
437 } ## end if ($etag)
443 }
438444
439445 return $etag;
440 } ## end sub upload_part_of_multipart_upload
446 }
441447
442448 ########################################################################
443449 sub make_xml_document_simple {
447453 my $xml = q{<?xml version="1.0" encoding="UTF-8"?>};
448454 my $xml_template
449455 = '<Part><PartNumber>%s</PartNumber><ETag>%s</ETag></Part>';
456
450457 my @parts;
451458
452459 foreach my $part_num ( sort { $a <=> $b } keys %{$parts_hr} ) {
496503 my $conf = {
497504 'Content-MD5' => $md5_base64,
498505 'Content-Length' => length $content,
499 'Content-Type' => 'application/xml'
506 'Content-Type' => 'application/xml',
500507 };
501508
502509 my $acct = $self->account;
507514 method => 'POST',
508515 path => $self->_uri($key) . $params,
509516 headers => $conf,
510 data => $content
511 }
517 data => $content,
518 },
512519 );
513520
514521 my $response = $acct->_do_http($request);
519526 }
520527
521528 return $TRUE;
522 } ## end sub complete_multipart_upload
529 }
523530
524531 #
525532 # Stop a multipart upload
541548 my $request = $acct->_make_request(
542549 { region => $self->region,
543550 method => 'DELETE',
544 path => $self->_uri($key) . $params
545 }
551 path => $self->_uri($key) . $params,
552 },
546553 );
547554
548555 my $response = $acct->_do_http($request);
550557 $acct->_croak_if_response_error($response);
551558
552559 return $TRUE;
553 } ## end sub abort_multipart_upload
560 }
554561
555562 #
556563 # List all the uploaded parts for an ongoing multipart upload
574581 { region => $self->region,
575582 method => 'GET',
576583 path => $self->_uri($key) . $params,
577 headers => $conf
578 }
584 headers => $conf,
585 },
579586 );
580587
581588 my $response = $acct->_do_http($request);
584591
585592 # Just return the XML, let the caller figure out what to do with it
586593 return $response->content;
587 } ## end sub list_multipart_upload_parts
594 }
588595
589596 #
590597 # List all the currently active multipart upload operations
601608 { region => $self->region,
602609 method => 'GET',
603610 path => $self->_uri() . '?uploads',
604 headers => $conf
605 }
611 headers => $conf,
612 },
606613 );
607614
608615 my $response = $acct->_do_http($request);
611618
612619 # Just return the XML, let the caller figure out what to do with it
613620 return $response->content;
614 } ## end sub list_multipart_uploads
621 }
615622
616623 ########################################################################
617624 sub head_key {
619626 my ( $self, $key ) = @_;
620627
621628 return $self->get_key( $key, 'HEAD' );
622 } ## end sub head_key
629 }
623630
624631 ########################################################################
625632 sub get_key {
630637
631638 if ( ref $filename ) {
632639 $filename = ${$filename};
633 } ## end if ( ref $filename )
640 }
634641
635642 my $acct = $self->account;
636643
640647 { region => $self->region,
641648 method => $method,
642649 path => $uri,
643 headers => {}
644 }
650 headers => {},
651 },
645652 );
646653
647654 my $retval;
649656 my $response = $acct->_do_http( $request, $filename );
650657
651658 return $retval
652 if $response->code == 404;
659 if $response->code eq $HTTP_NOT_FOUND;
653660
654661 $acct->_croak_if_response_error($response);
655662
658665 if ($etag) {
659666 $etag =~ s/^"//xsm;
660667 $etag =~ s/"$//xsm;
661 } ## end if ($etag)
668 }
662669
663670 $retval = {
664671 content_length => $response->content_length || 0,
678685 # etag so it should be lc'd for comparison.
679686 croak "Computed and Response MD5's do not match: $md5 : $etag"
680687 if $md5 ne lc $etag;
681 } ## end if ( $method eq 'GET' )
688 }
682689
683690 foreach my $header ( $response->headers->header_field_names ) {
684691 next if $header !~ /x-amz-meta-/ixsm;
685692 $retval->{ lc $header } = $response->header($header);
686 } ## end foreach my $header ( $response...)
693 }
687694
688695 return $retval;
689 } ## end sub get_key
696 }
690697
691698 ########################################################################
692699 sub get_key_filename {
695702
696703 if ( !defined $filename ) {
697704 $filename = $key;
698 } ## end if ( !defined $filename)
705 }
699706
700707 return $self->get_key( $key, $method, \$filename );
701 } ## end sub get_key_filename
708 }
702709
703710 ########################################################################
704711 # See: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
758765 }
759766
760767 return $acct->_xpc_of_content( $response->content );
761 } ## end sub copy_key
768 }
762769
763770 ########################################################################
764771 sub delete_key {
774781 { method => 'DELETE',
775782 region => $self->region,
776783 path => $self->_uri($key),
777 headers => {}
778 }
784 headers => {},
785 },
779786 );
780 } ## end sub delete_key
787 }
781788
782789 ########################################################################
783790 sub _format_delete_keys {
796803 Key => [ $key->{Key} ],
797804 defined $key->{VersionId}
798805 ? ( VersionId => [ $key->{VersionId} ] )
799 : ()
806 : (),
800807 };
801808 }
802809 else { # array of keys
803 push @keys, { Key => [$key] };
810 push @keys, { Key => [$key], };
804811 }
805812 }
806813 }
811818 push @keys,
812819 {
813820 Key => [ $object[0] ],
814 defined $object[1] ? ( VersionId => [ $object[1] ] ) : ()
821 defined $object[1] ? ( VersionId => [ $object[1] ] ) : (),
815822 };
816823 }
817824 }
864871 my $content = {
865872 xmlns => $S3_XMLNS,
866873 Quiet => [$quiet_mode],
867 Object => $keys
874 Object => $keys,
868875 };
869876
870877 my $xml_content = XMLout(
871878 $content,
872879 RootName => 'Delete',
873 XMLDecl => $XMLDECL
880 XMLDecl => $XMLDECL,
874881 );
875882
876883 my $conf = {};
889896 path => $self->_uri() . '?delete',
890897 headers => $conf,
891898 data => $xml_content,
892 }
899 },
893900 );
894 } ## end sub delete_keys
901 }
895902
896903 ########################################################################
897904 sub delete_bucket {
902909 if @_ > 1;
903910
904911 return $self->account->delete_bucket($self);
905 } ## end sub delete_bucket
912 }
906913
907914 ########################################################################
908915 sub list_v2 {
916923
917924 if ( $conf->{'marker'} ) {
918925 $conf->{'continuation-token'} = delete $conf->{'marker'};
919 } ## end if ( $conf->{'marker'})
926 }
920927
921928 return $self->list($conf);
922 } ## end sub list_v2
929 }
923930
924931 ########################################################################
925932 sub list {
931938 $conf->{bucket} = $self->bucket;
932939
933940 return $self->account->list_bucket($conf);
934 } ## end sub list
941 }
935942
936943 ########################################################################
937944 sub list_all_v2 {
943950 $conf->{bucket} = $self->bucket;
944951
945952 return $self->account->list_bucket_all_v2($conf);
946 } ## end sub list_all_v2
953 }
947954
948955 ########################################################################
949956 sub list_all {
955962 $conf->{bucket} = $self->bucket;
956963
957964 return $self->account->list_bucket_all($conf);
958 } ## end sub list_all
965 }
959966
960967 ########################################################################
961968 sub get_acl {
968975 { region => $self->region,
969976 method => 'GET',
970977 path => $self->_uri($key) . '?acl=',
971 headers => {}
972 }
978 headers => {},
979 },
973980 );
974981
975982 my $old_redirectable = $account->ua->requests_redirectable;
984991 my $old_host = $account->host;
985992 $account->host( $uri->host );
986993
987 my $request = $account->_make_request(
994 $request = $account->_make_request(
988995 { region => $self->region,
989996 method => 'GET',
990997 path => $uri->path,
991 headers => {}
992 }
998 headers => {},
999 },
9931000 );
9941001
9951002 $response = $account->_do_http($request);
9961003
9971004 $account->ua->requests_redirectable($old_redirectable);
9981005 $account->host($old_host);
999 } ## end if ( $response->code =~...)
1006 }
10001007
10011008 my $content;
10021009
10031010 # do we test for NOT FOUND, returning undef?
1004 if ( $response->code ne '404' ) {
1011 if ( $response->code ne $HTTP_NOT_FOUND ) {
10051012 $account->_croak_if_response_error($response);
10061013 $content = $response->content;
10071014 }
10081015
10091016 return $content;
1010 } ## end sub get_acl
1017 }
10111018
10121019 ########################################################################
10131020 sub set_acl {
10241031
10251032 my $path = $self->_uri( $conf->{key} ) . '?acl=';
10261033
1027 my $hash_ref
1028 = ( $conf->{acl_short} )
1029 ? { 'x-amz-acl' => $conf->{acl_short} }
1030 : {};
1034 my $headers = {};
1035
1036 if ( $conf->{acl_short} ) {
1037 $headers->{'x-amz-acl'} = $conf->{acl_short};
1038 }
10311039
10321040 my $xml = $conf->{acl_xml} || $EMPTY;
10331041
10341042 my $account = $self->account;
1043
1044 $headers->{'Content-Length'} = length $xml;
10351045
10361046 return $account->_send_request_expect_nothing(
10371047 { method => 'PUT',
10381048 path => $path,
1039 headers => $hash_ref,
1049 headers => $headers,
10401050 data => $xml,
1041 region => $self->region
1042 }
1051 region => $self->region,
1052 },
10431053 );
1044 } ## end sub set_acl
1054 }
10451055
10461056 ########################################################################
10471057 sub get_location_constraint {
10531063 my $xpc = $account->_send_request(
10541064 { region => $self->region,
10551065 method => 'GET',
1056 path => $self->bucket . '/?location='
1057 }
1066 path => $self->bucket . '/?location=',
1067 },
10581068 );
10591069
10601070 my $lc;
10641074 if $account->_remember_errors($xpc);
10651075
10661076 return $lc;
1067 } ## end if ( !$xpc )
1077 }
10681078
10691079 $lc = $xpc->{content};
10701080
10711081 if ( defined $lc && $lc eq $EMPTY ) {
10721082 $lc = undef;
1073 } ## end if ( defined $lc && $lc...)
1083 }
10741084
10751085 return $lc;
1076 } ## end sub get_location_constraint
1086 }
10771087
10781088 # proxy up the err requests
10791089
10911101 my ($self) = @_;
10921102
10931103 return $self->account->err;
1094 } ## end sub err
1104 }
10951105
10961106 ########################################################################
10971107 sub errstr {
10991109 my ($self) = @_;
11001110
11011111 return $self->account->errstr;
1102 } ## end sub errstr
1112 }
11031113
11041114 ########################################################################
11051115 sub error {
11071117 my ($self) = @_;
11081118
11091119 return $self->account->error;
1110 } ## end sub err
1120 }
11111121
11121122 ########################################################################
11131123 sub _content_sub {
11381148 $fh->binmode;
11391149
11401150 $remaining = $stat->size;
1141 } ## end if ( !$fh->opened )
1151 }
11421152
11431153 my $read = $fh->read( $buffer, $blksize );
11441154
11511161 or croak "close of upload content $filename failed: $OS_ERROR";
11521162
11531163 $buffer ||= $EMPTY; # LWP expects an empty string on finish, read returns 0
1154 } ## end if ( !$read )
1164 }
11551165
11561166 $remaining -= length $buffer;
11571167
11581168 return $buffer;
11591169 };
1160 } ## end sub _content_sub
1170 }
11611171
11621172 1;
11631173
12381248
12391249 =item logger
12401250
1241 Sets the logger object (should be an object capable of providing at
1242 least a C<debug> and C<trace> method for recording log messages. If no
1243 logger object is passed the C<account> object's logger object will be used.
1251 Sets the logger. The logger should be a blessed reference capable of
1252 providing at least a C<debug> and C<trace> method for recording log
1253 messages. If no logger object is passed the C<account> object's logger
1254 object will be used.
12441255
12451256 =item verify_region
12461257
12741285
12751286 =item value
12761287
1277 A SCALAR string representing the contents of the object..
1288 A SCALAR string representing the contents of the object.
12781289
12791290 =item configuration
12801291
22 use strict;
33 use warnings;
44
5 use parent qw{Exporter};
5 use parent qw(Exporter);
66
77 use Readonly;
88
2020 Readonly our $MIN_MULTIPART_UPLOAD_CHUNK_SIZE => 5 * 1024 * 1024;
2121 Readonly our $DEFAULT_LOG_LEVEL => 'error';
2222 Readonly our $MAX_DELETE_KEYS => 1000;
23 Readonly our $MAX_RETRIES => 5;
24 Readonly our $DEFAULT_REGION => 'us-east-1';
2325
2426 Readonly our $XMLDECL => '<?xml version="1.0" encoding="UTF-8"?>';
2527 Readonly our $S3_XMLNS => 'http://s3.amazonaws.com/doc/2006-03-01/';
3133 warn => 2,
3234 error => 1,
3335 fatal => 0,
36 );
37
38 Readonly::Hash our %LIST_OBJECT_MARKERS => (
39 '2' => [qw(ContinuationToken NextContinuationToken continuation-token)],
40 '1' => [qw(Marker NextMarker marker)],
3441 );
3542
3643 # booleans
4855 Readonly our $AMPERSAND => q{&};
4956 Readonly our $EQUAL_SIGN => q{=};
5057
58 # HTTP codes
59
60 Readonly our $HTTP_BAD_REQUEST => 400;
61 Readonly our $HTTP_UNAUTHORIZED => 401;
62 Readonly our $HTTP_PAYMENT_RQUIRED => 402;
63 Readonly our $HTTP_FORBIDDEN => 403;
64 Readonly our $HTTP_NOT_FOUND => 404;
65 Readonly our $HTTP_CONFLICT => 409;
66 Readonly our $HTTP_MOVED_PERMANENTLY => 301;
67 Readonly our $HTTP_FOUND => 302;
68 Readonly our $HTTP_SEE_OTHER => 303;
69 Readonly our $HTTP_NOT_MODIFIED => 304;
70
5171 our %EXPORT_TAGS = (
5272 chars => [
53 qw{
73 qw(
5474 $AMPERSAND
5575 $COLON
5676 $DOUBLE_COLON
6080 $EQUAL_SIGN
6181 $QUESTION_MARK
6282 $SLASH
63 }
83 )
6484 ],
6585 booleans => [
66 qw{
86 qw(
6787 $TRUE
6888 $FALSE
69 }
89 )
7090 ],
7191 defaults => [
72 qw{
92 qw(
7393 $AMAZON_HEADER_PREFIX
7494 $METADATA_PREFIX
7595 $KEEP_ALIVE_CACHESIZE
7696 $DEFAULT_TIMEOUT
7797 $DEFAULT_BUFFER_SIZE
7898 $DEFAULT_LOG_LEVEL
79 %LOG_LEVELS
8099 $DEFAULT_HOST
100 $DEFAULT_REGION
81101 $MAX_BUCKET_NAME_LENGTH
82102 $MAX_DELETE_KEYS
83103 $MIN_BUCKET_NAME_LENGTH
84104 $MIN_MULTIPART_UPLOAD_CHUNK_SIZE
105 $MAX_RETRIES
106 )
107 ],
108 misc => [
109 qw(
85110 $S3_XMLNS
86111 $XMLDECL
87 }
112 %LIST_OBJECT_MARKERS
113 %LOG_LEVELS
114 $NOT_FOUND
115 )
116 ],
117 http => [
118 qw(
119 $HTTP_BAD_REQUEST
120 $HTTP_CONFLICT
121 $HTTP_UNAUTHORIZED
122 $HTTP_PAYMENT_RQUIRED
123 $HTTP_FORBIDDEN
124 $HTTP_NOT_FOUND
125 $HTTP_MOVED_PERMANENTLY
126 $HTTP_FOUND
127 $HTTP_SEE_OTHER
128 $HTTP_NOT_MODIFIED
129 )
130
88131 ],
89132 );
90133
33 use warnings;
44
55 use Amazon::S3::Bucket;
6 use Amazon::S3::Constants qw{:all};
6 use Amazon::S3::Constants qw(:all);
77 use Amazon::S3::Logger;
88 use Amazon::S3::Signature::V4;
99
1010 use Carp;
1111 use Data::Dumper;
1212 use Digest::HMAC_SHA1;
13 use Digest::MD5 qw{md5_hex};
14 use English qw{-no_match_vars};
13 use Digest::MD5 qw(md5_hex);
14 use English qw(-no_match_vars);
1515 use HTTP::Date;
1616 use URI;
1717 use LWP::UserAgent::Determined;
18 use MIME::Base64 qw{encode_base64 decode_base64};
19 use Scalar::Util qw{ reftype blessed };
20 use List::Util qw{ any pairs };
21 use URI::Escape qw{uri_escape_utf8};
22 use XML::Simple qw{XMLin}; ## no critic (Community::DiscouragedModules)
23
24 use parent qw{Class::Accessor::Fast};
18 use MIME::Base64 qw(encode_base64 decode_base64);
19 use Scalar::Util qw( reftype blessed );
20 use List::Util qw( any pairs );
21 use URI::Escape qw(uri_escape_utf8);
22 use XML::Simple qw(XMLin); ## no critic (Community::DiscouragedModules)
23
24 use parent qw(Class::Accessor::Fast);
2525
2626 __PACKAGE__->mk_accessors(
27 qw{
27 qw(
2828 aws_access_key_id
2929 aws_secret_access_key
3030 token
4747 _signer
4848 timeout
4949 ua
50 }
50 ),
5151 );
5252
53 our $VERSION = '@PACKAGE_VERSION@'; ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
53 our $VERSION = '@PACKAGE_VERSION@'; ## no critic (RequireInterpolation)
5454
5555 ########################################################################
5656 sub new {
9999 $safe_options{aws_access_key_id} = '****';
100100 }
101101
102 return Dumper( [ 'options: ', \%safe_options ] );
103 }
102 return Dumper( [ options => \%safe_options ] );
103 },
104104 );
105105
106106 if ( !$self->credentials ) {
125125 requests_redirectable => [qw(GET HEAD DELETE)],
126126 );
127127
128 $ua->timing( join $COMMA, map { 2**$_ } 0 .. 5 );
128 $ua->timing( join $COMMA, map { 2**$_ } 0 .. $MAX_RETRIES );
129129 }
130130 else {
131131 $ua = LWP::UserAgent->new(
138138 $ua->env_proxy;
139139 $self->ua($ua);
140140
141 $self->region( $self->_region // 'us-east-1' );
141 $self->region( $self->_region // $DEFAULT_REGION );
142142
143143 if ( !$self->_signer && $self->cache_signer ) {
144144 $self->_signer( $self->signer );
161161 return $text if !$text;
162162
163163 if ( !defined $encryption_key ) {
164 eval {
165 require Crypt::Blowfish;
166 require Crypt::CBC;
164 $encryption_key = eval {
165 if ( !defined $encryption_key ) {
166 require Crypt::Blowfish;
167 require Crypt::CBC;
168
169 return md5_hex( rand $PID );
170 }
167171 };
168
169 if ($EVAL_ERROR) {
170 $encryption_key = $EMPTY;
171 }
172 else {
173 $encryption_key = md5_hex( rand $PID );
174 }
175172 }
176173
177 return $text
178 if !$encryption_key;
174 if ( !$encryption_key || $EVAL_ERROR ) {
175 return $text;
176 }
179177
180178 my $cipher = Crypt::CBC->new(
181179 -pass => $encryption_key,
197195
198196 my $cipher = Crypt::CBC->new(
199197 -pass => $encryption_key,
200 -cipher => 'Crypt::Blowfish'
198 -cipher => 'Crypt::Blowfish',
201199 );
202200
203201 return $cipher->decrypt($secret);
216214 $bucket = Amazon::S3::Bucket->new( bucket => $bucket, account => $self );
217215 }
218216
219 return $bucket->get_location_constraint // 'us-east-1';
217 return $bucket->get_location_constraint // $DEFAULT_REGION;
220218 }
221219
222220 ########################################################################
225223 my ($self) = @_;
226224
227225 my $region = $ENV{AWS_REGION} || $ENV{AWS_DEFAULT_REGION};
226
228227 return $region
229228 if $region;
230229
244243 }
245244 }
246245
247 return $region || 'us-east-1';
246 return $region || $DEFAULT_REGION;
248247 }
249248
250249 # Amazon::Credentials compatibility methods
285284 # /Backups<path>?partNumber=27&uploadId=<id> - HTTP/1.1" 400
286285 # RequestTimeout 360 20971520 20478 - "-" "libwww-perl/6.15"
287286 my $http_codes_hr = $self->ua->codes_to_determinate();
288 $http_codes_hr->{400} = 1;
287 $http_codes_hr->{$HTTP_BAD_REQUEST} = $TRUE;
289288 }
290289
291290 return;
304303 # /Backups<path>?partNumber=27&uploadId=<id> - HTTP/1.1" 400
305304 # RequestTimeout 360 20971520 20478 - "-" "libwww-perl/6.15"
306305 my $http_codes_hr = $self->ua->codes_to_determinate();
307 delete $http_codes_hr->{400};
306 delete $http_codes_hr->{$HTTP_BAD_REQUEST};
308307 }
309308
310309 return;
347346 my $region = $self->_region;
348347 my $bucket_list;
349348
350 $self->reset_signer_region('us-east-1'); # default region for buckets op
349 $self->reset_signer_region($DEFAULT_REGION); # default region for buckets op
351350
352351 my $r = $self->_send_request(
353352 { method => 'GET',
354353 path => $EMPTY,
355354 headers => {},
356 region => 'us-east-1',
357 }
355 region => $DEFAULT_REGION,
356 },
358357 );
359358
360359 return $bucket_list
380379 account => $self,
381380 buffer_size => $self->buffer_size,
382381 verify_region => $verify_region // $FALSE,
383 }
382 },
384383 );
385384
386385 }
406405 # is probably not needed anymore since bucket operations now send
407406 # the region of the bucket to the signer
408407 if ( $self->cache_signer ) {
409 if ( $self->region && $self->region ne 'us-east-1' ) {
408 if ( $self->region && $self->region ne $DEFAULT_REGION ) {
410409 if ( $self->signer->can('region') ) {
411410 $self->signer->region($region);
412411 }
427426 my $region = $conf->{location_constraint} // $conf->{region}
428427 // $self->region;
429428
430 if ( $region && $region eq 'us-east-1' ) {
429 if ( $region && $region eq $DEFAULT_REGION ) {
431430 undef $region;
432431 }
433432
458457 headers => { %header_ref, 'Content-Length' => length $data },
459458 data => $data,
460459 region => $region,
461 }
460 },
462461 );
463462
464463 my $bucket_obj = $retval ? $self->bucket($bucket) : undef;
475474
476475 if ( ref $args[0] && reftype( $args[0] ) eq 'HASH' ) {
477476 ( $bucketname, $region, $verify_region )
478 = @{ $args[0] }{qw{bucket region verify_region}};
477 = @{ $args[0] }{qw(bucket region verify_region)};
479478 }
480479 else {
481480 ( $bucketname, $region ) = @args;
492491 account => $self,
493492 region => $region,
494493 verify_region => $verify_region,
495 }
494 },
496495 );
497496 }
498497
521520 path => $bucket . $SLASH,
522521 headers => {},
523522 region => $region,
524 }
523 },
525524 );
526525 }
527526
549548 my $bucket_list; # return this
550549 my $path = $bucket . $SLASH;
551550
551 my $list_type = $conf->{'list-type'} // '1';
552
553 my ( $marker, $next_marker, $query_next )
554 = @{ $LIST_OBJECT_MARKERS{$list_type} };
555
556 if ( $conf->{marker} ) {
557 $conf->{$query_next} = delete $conf->{marker};
558 }
559
552560 if ( %{$conf} ) {
561
553562 my @vars = keys %{$conf};
554563
555564 # remove undefined elements
574583 path => $path,
575584 headers => {}, # { 'Content-Length' => 0 },
576585 region => $self->region,
577 }
586 },
578587 );
579588
580589 return $bucket_list
581590 if $self->_remember_errors($r);
582591
583 $self->get_logger->debug( sub { return Dumper($r); } );
584
585 my ( $marker, $next_marker ) = qw{ Marker NextMarker };
586
587 if ( $conf->{'list-type'} && $conf->{'list-type'} eq '2' ) {
588 $marker = 'ContinuationToken';
589 $next_marker = 'NextContinuationToken';
590 }
592 $self->get_logger->debug(
593 sub {
594 return Dumper(
595 [ marker => $marker,
596 next_marker => $next_marker,
597 response => $r,
598 ],
599 );
600 },
601 );
591602
592603 $bucket_list = {
593604 bucket => $r->{Name},
622633 owner_displayname => $node->{Owner}{DisplayName},
623634 };
624635 }
636
625637 $bucket_list->{keys} = \@keys;
626638
627639 if ( $conf->{delimiter} ) {
645657 push @common_prefixes, $prefix;
646658 }
647659 }
660
648661 $bucket_list->{common_prefixes} = \@common_prefixes;
649662 }
650663
674687 if !$bucket;
675688
676689 my $response = $self->list_bucket($conf);
677 croak 'The server has stopped responding'
690
691 croak $EVAL_ERROR
678692 if !$response;
679693
680694 return $response
690704 $conf->{bucket} = $bucket;
691705
692706 $response = $self->list_bucket($conf);
693 croak 'The server has stopped responding'
707
708 croak $EVAL_ERROR
694709 if !$response;
695710
696711 push @{ $all->{keys} }, @{ $response->{keys} };
766781 region => $self->region || $self->get_default_region,
767782 service => 's3',
768783 $self->get_token ? ( security_token => $creds->get_token ) : (),
769 }
784 },
770785 );
771786
772787 if ( $self->cache_signer ) {
823838
824839 if ( ref $args[0] && reftype( $args[0] ) eq 'HASH' ) {
825840 ( $method, $path, $headers, $data, $metadata, $region )
826 = @{ $args[0] }{qw{method path headers data metadata region}};
841 = @{ $args[0] }{qw(method path headers data metadata region)};
827842 }
828843 else {
829844 ( $method, $path, $headers, $data, $metadata, $region ) = @args;
863878
864879 if ( $host =~ /([^:]+):([^:]\d+)$/xsm ) {
865880
866 eval {
881 $url = eval {
867882 my $port = $2;
868883 $host = $1;
869884
870 my $uri = URI->new("http://$bucket.host");
885 my $uri = URI->new;
886
871887 $uri->scheme('http');
872888 $uri->host("$bucket.$host");
873889 $uri->port($port);
874890 $uri->path($path);
875 $url = $uri . $query_string;
876
891
892 return $uri . $query_string;
877893 };
878894
895 die "could not a uri for bucket: $bucket, host: $host, path: $path\n"
896 if !$url || $EVAL_ERROR;
879897 }
880898 else {
881899 $url = "$protocol://$bucket.$host$path$query_string";
909927 $self->get_logger->trace(
910928 sub {
911929 return Dumper( [ 'REQUEST' => \@args ] );
912 }
930 },
913931 );
914932
915933 my $request;
934 my $keep_root = $FALSE;
916935
917936 if ( @args == 1 && ref( $args[0] ) =~ /HTTP::Request/xsm ) {
918937 $request = $args[0];
919938 }
920939 else {
940 if ( ref $args[0] ) {
941 $keep_root = delete $args[0]->{keep_root};
942 }
943
921944 $request = $self->_make_request(@args);
922945 }
923946
934957 $content = undef;
935958 }
936959 elsif ( $content && $response->content_type eq 'application/xml' ) {
937 $content = $self->_xpc_of_content($content);
960 $content = $self->_xpc_of_content( $content, $keep_root );
938961 }
939962
940963 return $content;
11081131 my $request = $self->_make_request(@args);
11091132
11101133 my $response = $self->_do_http($request);
1134
11111135 $self->get_logger->debug( Dumper( [$response] ) );
11121136
11131137 my $content = $response->content;
11291153 # first time we used it. Thus, we need to probe first to find out what's going on,
11301154 # before we start sending any actual data.
11311155 ########################################################################
1132 sub _send_request_expect_nothing_probed {
1156 sub _send_request_expect_nothing_probed { ## no critic (ProhibitUnusedPrivateSubroutines)
11331157 ########################################################################
11341158 my ( $self, @args ) = @_;
11351159
11371161
11381162 if ( @args == 1 && ref $args[0] ) {
11391163 ( $method, $path, $conf, $value, $region )
1140 = @{ $args[0] }{qw{method path headers data region}};
1164 = @{ $args[0] }{qw(method path headers data region)};
11411165 }
11421166 else {
11431167 ( $method, $path, $conf, $value, $region )
1144 = @{ $args[0] }{qw{method path headers data region}};
1168 = @{ $args[0] }{qw(method path headers data region)};
11451169 }
11461170
11471171 $region = $region // $self->region;
11491173 my $request = $self->_make_request(
11501174 { method => 'HEAD',
11511175 path => $path,
1152 region => $region
1153 }
1176 region => $region,
1177 },
11541178 );
11551179
11561180 my $override_uri = undef;
11761200 path => $path,
11771201 headers => $conf,
11781202 data => $value,
1179 region => $region
1180 }
1203 region => $region,
1204 },
11811205 );
11821206
11831207 if ( defined $override_uri ) {
12211245 ########################################################################
12221246 my ( $self, $src, $keep_root ) = @_;
12231247
1224 my $xml_hr;
1225
1226 eval {
1227 $xml_hr = XMLin(
1248 my $xml_hr = eval {
1249 XMLin(
12281250 $src,
1229 'SuppressEmpty' => $EMPTY,
1230 'ForceArray' => ['Contents'],
1231 'KeepRoot' => $keep_root
1251 SuppressEmpty => $EMPTY,
1252 ForceArray => ['Contents'],
1253 KeepRoot => $keep_root,
1254 NoAttr => $TRUE,
12321255 );
12331256 };
12341257
1235 if ($EVAL_ERROR) {
1258 if ( !$xml_hr && $EVAL_ERROR ) {
12361259 confess "Error parsing $src: $EVAL_ERROR";
12371260 }
12381261
12451268 ########################################################################
12461269 my ( $self, $src, $keep_root ) = @_;
12471270
1248 return $TRUE if !$src; # this should not happen
1271 return $src
1272 if !$src;
12491273
12501274 if ( !ref $src && $src !~ /^[[:space:]]*</xsm ) { # if not xml
12511275 ( my $code = $src ) =~ s/^[[:space:]]*[(][\d]*[)].*$/$1/xsm;
12791303 # Deprecated - this adds a header for the old V2 auth signatures
12801304 #
12811305 ########################################################################
1282 sub _add_auth_header {
1306 sub _add_auth_header { ## no critic (ProhibitUnusedPrivateSubroutines)
12831307 ########################################################################
12841308 my ( $self, $headers, $method, $path ) = @_;
12851309
13891413 $buf .= "/$1";
13901414
13911415 # ...unless there any parameters we're interested in...
1392 if ( $path =~ /[&?](acl|torrent|location|uploads|delete)($|=|&)/xsm ) {
1416 if ( $path =~ /[&?](acl|torrent|location|uploads|delete)([=&]|$)/xsm ) {
13931417 # if ( $path =~ /[&?](acl|torrent|location|uploads|delete)([=&])?/xsm ) {
13941418 $buf .= "?$1";
13951419 }
14441468 ########################################################################
14451469 my ( $self, $unencoded ) = @_;
14461470
1447 return uri_escape_utf8( $unencoded, '^A-Za-z0-9\-\._~\x2f' );
1471 return uri_escape_utf8( $unencoded, '^A-Za-z0-9\-\._~\x2f' ); ## no critic (RequireInterpolation)
14481472 }
14491473
14501474 1;
14661490
14671491 =head1 SYNOPSIS
14681492
1469 #!/usr/bin/perl
1470 use warnings;
1471 use strict;
1472
14731493 use Amazon::S3;
1474
1475 use vars qw/$OWNER_ID $OWNER_DISPLAYNAME/;
14761494
14771495 my $aws_access_key_id = "Fill me in!";
14781496 my $aws_secret_access_key = "Fill me in too!";
14881506
14891507 # create a bucket
14901508 my $bucket_name = $aws_access_key_id . '-net-amazon-s3-test';
1509
14911510 my $bucket = $s3->add_bucket( { bucket => $bucket_name } )
14921511 or die $s3->err . ": " . $s3->errstr;
14931512
14941513 # store a key with a content-type and some optional metadata
14951514 my $keyname = 'testing.txt';
1515
14961516 my $value = 'T';
1517
14971518 $bucket->add_key(
14981519 $keyname, $value,
14991520 { content_type => 'text/plain',
15101531 # list keys in the bucket
15111532 $response = $bucket->list
15121533 or die $s3->err . ": " . $s3->errstr;
1534
15131535 print $response->{bucket}."\n";
1536
15141537 for my $key (@{ $response->{keys} }) {
15151538 print "\t".$key->{key}."\n";
15161539 }
15171540
15181541 # delete key from bucket
15191542 $bucket->delete_key($keyname);
1543
1544 # delete multiple keys from bucket
1545 $bucket->delete_keys([$key1, $key2, $key3]);
15201546
15211547 # delete bucket
15221548 $bucket->delete_bucket;
15231549
15241550 =head1 DESCRIPTION
15251551
1552 This documentation refers to version @PACKAGE_VERSION@.
1553
15261554 C<Amazon::S3> provides a portable client interface to Amazon Simple
15271555 Storage System (S3).
15281556
1529 I<This module is rather dated. For a much more robust and modern
1530 implementation of an S3 interface try C<Net::Amazon::S3>.
1531 C<Amazon::S3> ostensibly was intended to be a drop-in replacement for
1532 C<Net:Amazon::S3> that "traded some performance in return for
1533 portability". That statement is no longer accurate as
1534 C<Net::Amazon::S3> implements much more of the S3 API and may have
1535 changed the interface in ways that might break your
1536 applications. However, C<Net::Amazon::S3> is today dependent on
1557 This module is rather dated, however with some help from a few
1558 contributors it has had some recent updates. Recent changes include
1559 implementations of:
1560
1561 =over 5
1562
1563 =item ListObjectsV2
1564
1565 =item CopyObject
1566
1567 =item DeleteObjects
1568
1569 =back
1570
1571 Additionally, this module now implements Signature Version 4 signing,
1572 unit tests have been updated and more documentation has been added or
1573 corrected. Credentials are encrypted if you have encryption modules installed.
1574
1575 =head2 Comparison to Other Perl S3 Modules
1576
1577 Other implementations for accessing Amazon's S3 service include
1578 C<Net::Amazon::S3> and the C<Paws> project. C<Amazon::S3> ostensibly
1579 was intended to be a drop-in replacement for C<Net:Amazon::S3> that
1580 "traded some performance in return for portability". That statement is
1581 no longer accurate as C<Amazon::S3> may have changed the interface in
1582 ways that might break your applications if you are relying on
1583 compatibility with C<Net::Amazon::S3>.
1584
1585 However, C<Net::Amazon::S3> and C<Paws::S3> today, are dependent on
15371586 C<Moose> which may in fact level the playing field in terms of
15381587 performance penalties that may have been introduced by recent updates
1539 to C<Amazon::S3>. YMMV, however, this module may still appeal to some
1540 that favor simplicity of the interface and a lower number of
1541 dependencies. Below is the original description of the module.>
1588 to C<Amazon::S3>. Changes to C<Amazon::S3> include the use of more
1589 Perl modules in lieu of raw Perl code to increase maintainability and
1590 stability as well as some refactoring. C<Amazon::S3> also strives now
1591 to adhere to best practices as much as possible.
1592
1593 C<Paws::S3> is a much more robust implementation of
1594 a Perl S3 interface, however this module may still appeal to
1595 those that favor simplicity of the interface and a lower number of
1596 dependencies. Below is the original description of the module.
15421597
15431598 =over 10
15441599
15771632 =head1 LIMITATIONS AND DIFFERENCES WITH EARLIER VERSIONS
15781633
15791634 As noted, this module is no longer a I<drop-in> replacement for
1580 C<Net::Amazon::S3> and has limitations and differences that may make
1581 the use of this module in your applications
1582 questionable. Additionally, one of the original intents of this fork
1583 of C<Net::Amazon::S3> was to reduce the dependencies and make it
1584 I<easy to install>. Recent changes to this module have introduced new
1585 dependencies in order to improve the maintainability and provide
1586 additional features. Installing CPAN modules is never easy, especially
1587 when the dependencies of the dependencies are impossible to control
1588 and include XS modules.
1635 C<Net::Amazon::S3> and has limitations and differences that may impact
1636 the use of this module in your applications. Additionally, one of the
1637 original intents of this fork of C<Net::Amazon::S3> was to reduce the
1638 number of dependencies and make it I<easy to install>. Recent changes
1639 to this module have introduced new dependencies in order to improve
1640 the maintainability and provide additional features. Installing CPAN
1641 modules is never easy, especially when the dependencies of the
1642 dependencies are impossible to control and include XS modules.
15891643
15901644 =over 5
15911645
16121666
16131667 HTML::HeadParser 2.14
16141668 LWP 6.13
1615 Amazon::S3 0.55
1669 Amazon::S3
16161670
16171671 ...other versions I<may> work...YMMV.
16181672
16391693 parameter. This implies that you need to supply the bucket's region
16401694 when signing requests for any API call that involves a specific
16411695 bucket. Starting with version 0.55 of this module,
1642 C<Amazon::S3::Bucket> provides a new method (C<region()> and accepts
1696 C<Amazon::S3::Bucket> provides a new method (C<region()>) and accepts
16431697 in the constructor a C<region> parameter. If a region is not
16441698 supplied, the region for the bucket will be set to the region set in
16451699 the C<account> object (C<Amazon::S3>) that you passed to the bucket's
16641718
16651719 =item Multipart Upload Support
16661720
1667 There is limited testing for multipart uploads.
1721 There is some limited testing for multipart uploads.
16681722
16691723 For more information regarding multi-part uploads visit the link below.
16701724
18131867
18141868 Unfortunately, while this will prevent L<Net::Amazon::Signature::V4>
18151869 from hanging on to your credentials, you credentials will be stored in
1816 the L<Amazon::S3> object.
1870 the C<Amazon::S3> object.
18171871
18181872 Starting with version 0.55 of this module, if you have installed
18191873 L<Crypt::CBC> and L<Crypt::Blowfish>, your credentials will be
18311885
18321886 =item 5. Do nothing...send the credentials, use the default signer.
18331887
1834 In this case, both the L<Amazon::S3> class and the
1888 In this case, both the C<Amazon::S3> class and the
18351889 L<Net::Amazon::Signature::V4> have your credentials. Caveat Emptor.
18361890
18371891 See Also L<Amazon::Credentials> for more information about safely
22302284
22312285 Your AWS access key
22322286
2233 =item AWS_ACCESS_KEY_SECRET
2287 =item AWS_SECRET_ACCESS_KEY
22342288
22352289 Your AWS sekkr1t passkey. Be forewarned that setting this environment variable
22362290 on a shared system might leak that information to another user. Be careful.
22402294 Doesn't matter what you set it to. Just has to be set if you want
22412295 to skip ACLs tests.
22422296
2297 =item AMAZON_S3_SKIP_PERMISSIONS
2298
2299 Skip tests that check for enforcement of ACLs...as of this version,
2300 LocalStack for example does not support enforcement of ACLs.
2301
22432302 =item AMAZON_S3_SKIP_REGION_CONSTRAINT_TEST
22442303
22452304 Doesn't matter what you set it to. Just has to be set if you want
22642323
22652324 I<Consider using an S3 mocking service like C<minio> or C<LocalStack>
22662325 if you want to create real tests for your applications or this module.>
2326
2327 Here's bash script for testing using LocalStack
2328
2329 #!/bin/bash
2330 # -*- mode: sh; -*-
2331
2332 BUCKET=net-amazon-s3-test-test
2333 ENDPOINT_URL=s3.localhost.localstack.cloud:4566
2334
2335 AMAZON_S3_EXPENSIVE_TESTS=1 \
2336 AMAZON_S3_HOST=$ENDPOINT_URL \
2337 AMAZON_S3_LOCALSTACK=1 \
2338 AWS_ACCESS_KEY_ID=test \
2339 AWS_ACCESS_SECRET_KEY=test \
2340 AMAZON_S3_DOMAIN_BUCKET_NAMES=1 make test 2>&1 | tee test.log
2341
2342 To run the tests...clone the project and build the software.
2343
2344 cd src/main/perl
2345 ./test.localstack
22672346
22682347 =head1 ADDITIONAL INFORMATION
22692348
23572436 http://www.perl.com/language/misc/Artistic.html. Except
23582437 where otherwise noted, C<Amazon::S3> is Copyright 2008, Timothy
23592438 Appnel, tima@cpan.org. All rights reserved.
2439
2440 =cut
00 #!/usr/bin/env perl -w
1
2 ## no critic
31
42 use warnings;
53 use strict;
64
7 use lib 'lib';
5 use lib qw( . lib);
86
97 use Data::Dumper;
108 use Digest::MD5::File qw(file_md5_hex);
11 use English qw{-no_match_vars};
12 use File::Temp qw{ tempfile };
9 use English qw{-no_match_vars};
10 use File::Temp qw{ tempfile };
11 use List::Util qw(any);
1312 use Test::More;
1413
15 our $OWNER_ID;
16 our $OWNER_DISPLAYNAME;
14 use S3TestUtils qw(:constants :subs);
15
1716 our @REGIONS = (undef);
1817
1918 if ( $ENV{AMAZON_S3_REGIONS} ) {
2019 push @REGIONS, split /\s*,\s*/xsm, $ENV{AMAZON_S3_REGIONS};
2120 }
2221
23 my $host;
24
25 my $skip_owner_id;
26 my $skip_permissions;
27 my $skip_acls;
28
29 if ( exists $ENV{AMAZON_S3_LOCALSTACK} ) {
30 $host = 'localhost:4566';
31
32 $ENV{'AWS_ACCESS_KEY_ID'} = 'test';
33 $ENV{'AWS_ACCESS_KEY_SECRET'} = 'test';
34
35 $ENV{'AMAZON_S3_EXPENSIVE_TESTS'} = 1;
36
37 $skip_owner_id = 1;
38 $skip_permissions = 1;
39 $skip_acls = 1;
40 }
41 else {
42 $host = $ENV{AMAZON_S3_HOST};
43 }
44
45 my $secure = $host ? 0 : 1;
46
47 # - do not use DNS bucket names for testing if a mocking service is used
48 # - override this by setting AMAZON_S3_DNS_BUCKET_NAMES to any value
49 # - your tests may fail unless you have DNS entry for the bucket name
50 # e.g 127.0.0.1 net-amazon-s3-test-test.localhost
51
52 my $dns_bucket_names
53 = ( $host && !exists $ENV{AMAZON_S3_DNS_BUCKET_NAMES} ) ? 0 : 1;
54
55 $skip_acls //= exists $ENV{AMAZON_S3_MINIO}
56 || exists $ENV{AMAZON_S3_SKIP_ACL_TESTS};
57
58 my $no_region_constraint //= exists $ENV{AMAZON_S3_MINIO}
59 || exists $ENV{AMAZON_S3_SKIP_REGION_CONSTRAINT_TEST};
60
61 my $aws_access_key_id = $ENV{'AWS_ACCESS_KEY_ID'};
62 my $aws_secret_access_key = $ENV{'AWS_ACCESS_KEY_SECRET'};
63 my $token = $ENV{'AWS_SESSION_TOKEN'};
64
65 if ( !$ENV{'AMAZON_S3_EXPENSIVE_TESTS'} ) {
22 my $host = set_s3_host();
23
24 my $bucket_name = make_bucket_name();
25
26 if ( !$ENV{AMAZON_S3_EXPENSIVE_TESTS} ) {
6627 plan skip_all => 'Testing this module for real costs money.';
6728 }
6829 else {
7637 use_ok('Amazon::S3');
7738 use_ok('Amazon::S3::Bucket');
7839
79 my $s3;
80
81 if ( $ENV{AMAZON_S3_CREDENTIALS} ) {
82 require Amazon::Credentials;
83
84 $s3 = Amazon::S3->new(
85 { credentials => Amazon::Credentials->new,
86 host => $host,
87 secure => $secure,
88 dns_bucket_names => $dns_bucket_names,
89 level => $ENV{DEBUG} ? 'trace' : 'error',
90 }
91 );
92 ( $aws_access_key_id, $aws_secret_access_key, $token )
93 = $s3->get_credentials;
94 }
95 else {
96 $s3 = Amazon::S3->new(
97 { aws_access_key_id => $aws_access_key_id,
98 aws_secret_access_key => $aws_secret_access_key,
99 token => $token,
100 host => $host,
101 secure => $secure,
102 dns_bucket_names => $dns_bucket_names,
103 level => $ENV{DEBUG} ? 'trace' : 'error',
104 }
105 );
106 }
107
108 # list all buckets that i own
109 my $response = eval { return $s3->buckets; };
110
111 if ( $EVAL_ERROR || !$response ) {
112 BAIL_OUT($EVAL_ERROR);
113 }
114
115 $OWNER_ID = $response->{owner_id};
116 $OWNER_DISPLAYNAME = $response->{owner_displayname};
40 my $s3 = get_s3_service($host);
41
42 if ( !$s3 || $EVAL_ERROR ) {
43 BAIL_OUT( 'could not initialize s3 object: ' . $EVAL_ERROR );
44 }
45
46 # bail if test bucket already exists
47 our ( $OWNER_ID, $OWNER_DISPLAYNAME ) = check_test_bucket($s3);
11748
11849 for my $location (@REGIONS) {
11950 # this test formerly used the same bucket name for both regions,
12253 # To test the bucket constraint policy below then we need to use a
12354 # different bucket name. The old comment here was...
12455 #
125 # create a bucket
126 # make sure it's a valid hostname for EU testing
127 # we use the same bucket name for both in order to force one or the
128 # other to have stale DNS
56 # > create a bucket
57 # > make sure it's a valid hostname for EU testing
58 # > we use the same bucket name for both in order to force one or the
59 # > other to have stale DNS
12960
13061 $s3->region($location);
13162 $host = $s3->host;
13263
133 my $bucketname_raw;
134 my $bucketname;
64 my $bucket_name_raw;
65 my $bucket_name;
13566 my $bucket_obj;
13667 my $bucket_suffix;
13768
138 while (1) {
139
140 $bucketname_raw = sprintf 'net-amazon-s3-test-%s%s',
141 lc($aws_access_key_id), $bucket_suffix // '';
142
143 $bucketname = '/' . $bucketname_raw;
69 while ($TRUE) {
70
71 $bucket_name_raw = make_bucket_name();
72 $bucket_name = $SLASH . $bucket_name_raw;
14473
14574 $bucket_obj = eval {
14675 $s3->add_bucket(
147 { bucket => $bucketname,
76 { bucket => $bucket_name,
14877 acl_short => 'public-read',
14978 location_constraint => $location
15079 }
15887 last if $bucket_obj;
15988
16089 # 409 indicates bucket name not yet available...
161 if ( $s3->last_response->code ne '409' ) {
162 BAIL_OUT("could not create $bucketname");
90 if ( $s3->last_response->code ne $HTTP_CONFLICT ) {
91 BAIL_OUT("could not create $bucket_name");
16392 }
16493
16594 $bucket_suffix = '-2';
16695 }
16796
168 is( ref $bucket_obj,
169 'Amazon::S3::Bucket',
170 'create bucket in ' . ( $location // 'DEFAULT_REGION' ) )
171 or BAIL_OUT("could not create bucket $bucketname");
97 is(
98 ref $bucket_obj,
99 'Amazon::S3::Bucket', sprintf 'create bucket (%s) in %s ',
100 $bucket_name, $location // 'DEFAULT_REGION'
101 ) or BAIL_OUT("could not create bucket $bucket_name");
172102
173103 SKIP: {
174 if ($no_region_constraint) {
175 skip "No region constraints", 1;
104 if ( $ENV{AMAZON_S3_SKIP_REGION_CONSTRAINT_TEST} ) {
105 skip 'No region constraints', 1;
176106 }
177107
178108 is( $bucket_obj->get_location_constraint, $location );
180110
181111 SKIP: {
182112
183 if ( $skip_acls || !$bucket_obj ) {
184 skip "ACLs only for Amazon S3", 3;
113 if ( $ENV{AMAZON_S3_SKIP_ACLS} || !$bucket_obj ) {
114 skip 'ACLs only for Amazon S3', 3;
185115 }
186116
187117 like_acl_allusers_read($bucket_obj);
188118
189 ok( $bucket_obj->set_acl( { acl_short => 'private' } ) );
119 my $rsp = $bucket_obj->set_acl( { acl_short => 'private' } );
120
121 ok( $rsp, 'set_acl - private' )
122 or diag(
123 Dumper( [ response => $rsp, $s3->err, $s3->errstr, $s3->error ] ) );
124
190125 unlike_acl_allusers_read($bucket_obj);
191
192126 }
193127
194128 # another way to get a bucket object (does no network I/O,
195129 # assumes it already exists). Read Amazon::S3::Bucket.
196 $bucket_obj = $s3->bucket($bucketname);
197 is( ref $bucket_obj, "Amazon::S3::Bucket" );
130 $bucket_obj = $s3->bucket($bucket_name);
131 is( ref $bucket_obj, 'Amazon::S3::Bucket' );
198132
199133 # fetch contents of the bucket
200134 # note prefix, marker, max_keys options can be passed in
201135
202 $response = $bucket_obj->list
203 or BAIL_OUT( $s3->err . ": " . $s3->errstr );
136 my $response = $bucket_obj->list
137 or BAIL_OUT( $s3->err . ': ' . $s3->errstr );
204138
205139 SKIP: {
206 skip "invalid response to 'list'"
207 if !$response;
208
209 is( $response->{bucket}, $bucketname_raw )
140 if ( !$response ) {
141 skip 'invalid response to "list"';
142 }
143
144 is( $response->{bucket}, $bucket_name_raw )
210145 or BAIL_OUT( Dumper [$response] );
211146
212147 ok( !$response->{prefix} );
220155 is_deeply( $response->{keys}, [] )
221156 or diag( Dumper( [$response] ) );
222157
223 is( undef, $bucket_obj->get_key("non-existing-key") );
158 is( undef, $bucket_obj->get_key('non-existing-key') );
224159 }
225160
226161 my $keyname = 'testing.txt';
230165 # Create a publicly readable key, then turn it private with a short acl.
231166 # This key will persist past the end of the block.
232167 my $value = 'T';
168
233169 $bucket_obj->add_key(
234170 $keyname, $value,
235171 { content_type => 'text/plain',
240176
241177 my $url
242178 = $s3->dns_bucket_names
243 ? "http://$bucketname_raw.$host/$keyname"
244 : "http://$host/$bucketname/$keyname";
179 ? "http://$bucket_name_raw.$host/$keyname"
180 : "http://$host/$bucket_name/$keyname";
245181
246182 SKIP: {
247 if ($skip_acls) {
248 skip "ACLs only for Amazon S3", 3;
249 }
250
251 is_request_response_code( $url, 200,
252 "can access the publicly readable key" );
183 if ( $ENV{AMAZON_S3_SKIP_ACLS} ) {
184 skip 'ACLs only for Amazon S3', 3;
185 }
186
187 is_request_response_code( $url, $HTTP_OK,
188 'can access the publicly readable key' );
253189
254190 like_acl_allusers_read( $bucket_obj, $keyname );
255191
256192 ok(
257 $bucket_obj->set_acl( { key => $keyname, acl_short => 'private' } ) );
193 $bucket_obj->set_acl(
194 { key => $keyname,
195 acl_short => 'private'
196 }
197 )
198 );
258199 }
259200
260201 SKIP: {
261 if ($skip_acls) {
262 skip 'ACLs only for Amazon S3', 1;
263 }
264
265 is_request_response_code( $url, 403, "cannot access the private key" );
202 if ( $ENV{AMAZON_S3_SKIP_PERMISSIONS} ) {
203 skip 'Mocking service does not enforce ACLs', 1;
204 }
205
206 is_request_response_code( $url, $HTTP_FORBIDDEN,
207 'cannot access the private key' );
266208 }
267209
268210 SKIP: {
269 if ($skip_acls) {
211 if ( $ENV{AMAZON_S3_SKIP_ACLS} ) {
270212 skip 'ACLs only for Amazon S3', 5;
271213 }
272214
281223 );
282224
283225 is_request_response_code( $url,
284 200, "can access the publicly readable key after acl_xml set" );
226 $HTTP_OK, 'can access the publicly readable key after acl_xml set' );
285227
286228 like_acl_allusers_read( $bucket_obj, $keyname );
287229
295237 }
296238
297239 SKIP: {
298 if ( $skip_acls || $ENV{LOCALSTACK} ) {
299 skip 'LocalStack does not enforce ACLs', 2;
240 if ( $ENV{AMAZON_S3_SKIP_PERMISSIONS} ) {
241 skip 'Mocking service does not enforce ACLs', 2;
300242 }
301243
302244 is_request_response_code( $url,
303 403, 'cannot access the private key after acl_xml set' );
245 $HTTP_FORBIDDEN, 'cannot access the private key after acl_xml set' );
304246
305247 unlike_acl_allusers_read( $bucket_obj, $keyname );
306248 }
325267
326268 my $url
327269 = $s3->dns_bucket_names
328 ? "http://$bucketname_raw.$host/$keyname2"
329 : "http://$host/$bucketname/$keyname2";
270 ? "http://$bucket_name_raw.$host/$keyname2"
271 : "http://$host/$bucket_name/$keyname2";
330272
331273 SKIP: {
332 skip 'LocalStack does not enforce ACLs', 1
333 if $skip_permissions || $skip_acls;
334
335 is_request_response_code( $url, 403, "cannot access the private key" );
274 if ( $ENV{AMAZON_S3_SKIP_PERMISSIONS} ) {
275 skip 'Mocking service does not enforce ACLs', 1;
276 }
277
278 is_request_response_code( $url, $HTTP_FORBIDDEN,
279 'cannot access the private key' );
336280 }
337281
338282 SKIP: {
339 skip 'ACLs only for Amazon S3', 4 if $skip_acls;
283 if ( $ENV{AMAZON_S3_SKIP_ACLS} ) {
284 skip 'ACLs only for Amazon S3', 4;
285 }
340286
341287 unlike_acl_allusers_read( $bucket_obj, $keyname2 );
342288
349295 );
350296
351297 is_request_response_code( $url,
352 200, "can access the publicly readable key" );
298 $HTTP_OK, 'can access the publicly readable key' );
353299
354300 like_acl_allusers_read( $bucket_obj, $keyname2 );
355301
369315 }
370316
371317 if ( !$response ) {
372 BAIL_OUT( $s3->err . ": " . $s3->errstr );
373 }
374
375 is( $response->{bucket}, $bucketname_raw, "list($v) - bucketname " );
318 BAIL_OUT( $s3->err . ': ' . $s3->errstr );
319 }
320
321 is( $response->{bucket}, $bucket_name_raw, sprintf 'list(%s) - %s',
322 $v, $bucket_name );
376323
377324 ok( !$response->{prefix}, "list($v) - prefix empty" )
378325 or diag( Dumper [$response] );
396343 is( $key->{size}, 1, "list($v) - size == 1" );
397344
398345 SKIP: {
399 skip 'LocalStack has different owner for bucket', 1 if $skip_owner_id;
346 if ( $ENV{AMAZON_S3_SKIP_OWNER_ID_TEST} ) {
347 skip 'mocking service has different owner for bucket', 1;
348 }
349
400350 is( $key->{owner_id}, $OWNER_ID, "list($v) - owner id " )
401351 or diag( Dumper [$key] );
402352 }
412362
413363 # now play with the file methods
414364 my ( $fh, $lorem_ipsum ) = tempfile();
365
415366 print {$fh} <<'EOT';
416367 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
417368 eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
427378 my $lorem_ipsum_md5 = file_md5_hex($lorem_ipsum);
428379 my $lorem_ipsum_size = -s $lorem_ipsum;
429380
430 $keyname .= "2";
381 $keyname .= '2';
431382
432383 $bucket_obj->add_key_filename(
433384 $keyname,
440391 $response = $bucket_obj->get_key($keyname);
441392
442393 is( $response->{content_type}, 'text/plain', 'get_key - content_type' );
443 like( $response->{value}, qr/Lorem ipsum/, 'get_key - Lorem ipsum' );
394 like( $response->{value}, qr/Lorem\sipsum/xsm, 'get_key - Lorem ipsum' );
444395
445396 is( $response->{etag}, $lorem_ipsum_md5, 'get_key - etag' )
446397 or diag( Dumper [$response] );
456407 is( $response->{content_type},
457408 'text/plain', 'get_key_filename - content_type' );
458409
459 is( $response->{value}, '', 'get_key_filename - value empty' );
410 is( $response->{value}, $EMPTY, 'get_key_filename - value empty' );
460411
461412 is( $response->{etag}, $lorem_ipsum_md5, 'get_key_filename - etag == md5' );
462413
478429
479430 isa_ok( $copy_result, 'HASH', 'copy_object returns a hash reference' );
480431
481 $bucket_obj->delete_key($keyname);
482432 $response = $bucket_obj->list;
483433
484434 ok( ( grep {"$keyname.bak"} @{ $response->{keys} } ), 'found the copy' );
485435
486 $bucket_obj->delete_key($keyname);
487 $bucket_obj->delete_key("$keyname.bak");
436 if ( !$ENV{AMAZON_S3_KEEP_BUCKET} ) {
437 $bucket_obj->delete_key($keyname);
438 $bucket_obj->delete_key("$keyname.bak");
439 }
488440
489441 # try empty files
490442 $keyname .= '3';
491 $bucket_obj->add_key( $keyname, '' );
443 $bucket_obj->add_key( $keyname, $EMPTY );
492444 $response = $bucket_obj->get_key($keyname);
493445
494 is( $response->{value}, '', 'empty object - value empty' );
446 is( $response->{value}, $EMPTY, 'empty object - value empty' );
495447
496448 is(
497449 $response->{etag},
509461 # fetch contents of the bucket
510462 # note prefix, marker, max_keys options can be passed in
511463 $response = $bucket_obj->list
512 or die $s3->err . ": " . $s3->errstr;
513
514 $bucketname =~ s/^\///;
515
516 is( $response->{bucket}, $bucketname,
517 'delete key from bucket - bucketname' );
464 or die $s3->err . ': ' . $s3->errstr;
465
466 $bucket_name =~ s/^\///xsm;
467
468 is( $response->{bucket}, $bucket_name,
469 'delete key from bucket - ' . $bucket_name );
518470
519471 ok( !$response->{prefix}, 'delete key from bucket - prefix empty' );
520472
533485 # delete multiple keys from bucket
534486 # TODO: test deleting specific versions
535487 #
536 $keyname = 'foo-';
537
538 for ( 1 .. 8 ) {
539 $bucket_obj->add_key( "$keyname$_", q{} );
540 }
541
542 $response = $bucket_obj->list
543 or die $s3->err . ": " . $s3->errstr;
544
545 my @key_list = @{ $response->{keys} };
546
547 is( 8, scalar @key_list, 'wrote 8 keys for delete_keys() test' );
548
549 ######################################################################
550 # quietly delete version keys - first two
551 ######################################################################
552 my $delete_rsp = $bucket_obj->delete_keys(
553 { quiet => 1,
554 keys => [ map { $_->{key} } @key_list[ ( 0, 1 ) ] ]
555 }
556 );
557
558 # diag( Dumper( [ QUIET => $delete_rsp ] ) );
559
560 ok( !$delete_rsp, 'delete_keys() quiet response' );
561
562 $response = $bucket_obj->list
563 or die $s3->err . ": " . $s3->errstr;
564
565 is( scalar @{ $response->{keys} }, 6, 'delete versioned keys' );
566
567 shift @key_list;
568 shift @key_list;
569
570 ######################################################################
571 # delete list of keys - next two keys
572 ######################################################################
573 $delete_rsp
574 = $bucket_obj->delete_keys( map { $_->{key} } @key_list[ ( 0, 1 ) ] );
575
576 ok( $delete_rsp, 'delete_keys() response' );
577
578 $response = $bucket_obj->list
579 or die $s3->err . ": " . $s3->errstr;
580
581 is( scalar @{ $response->{keys} }, 4, 'delete list of keys' );
582
583 shift @key_list;
584 shift @key_list;
585
586 ######################################################################
587 # delete array of keys - last two keys
588 #####################################################################
589 $delete_rsp
590 = $bucket_obj->delete_keys( map { $_->{key} } @key_list[ ( 0, 1 ) ] );
591
592 ok( $delete_rsp, 'delete_keys() response' );
593
594 $response = $bucket_obj->list
595 or die $s3->err . ": " . $s3->errstr;
596
597 is( scalar @{ $response->{keys} }, 2, 'delete array of keys' );
598
599 ######################################################################
600 # callback
601 ######################################################################
602 $delete_rsp = $bucket_obj->delete_keys(
603 sub {
604 my $key = shift @key_list;
605 return ( $key->{key}, '1' );
606 }
607 );
608
609 ok( $delete_rsp, 'delete_keys() response' );
610
611 $response = $bucket_obj->list
612 or die $s3->err . ": " . $s3->errstr;
613
614 ok( !scalar @{ $response->{keys} }, 'delete keys from callback' );
615
616 #
617 # delete multiple keys from bucket
618 ######################################################################
619
620 ok( $bucket_obj->delete_bucket(), 'delete bucket' );
488
489 SKIP: {
490 if ( $ENV{AMAZON_S3_KEEP_BUCKET} ) {
491 skip 'keeping bucket', 9;
492 }
493
494 $keyname = 'foo-';
495
496 for ( 1 .. 8 ) {
497 $bucket_obj->add_key( "$keyname$_", $EMPTY );
498 }
499
500 $response = $bucket_obj->list
501 or die $s3->err . ': ' . $s3->errstr;
502
503 my @key_list = @{ $response->{keys} };
504
505 is( 8, scalar @key_list, 'wrote 8 keys for delete_keys() test' );
506
507 ######################################################################
508 # quietly delete version keys - first two
509 ######################################################################
510 my $delete_rsp = $bucket_obj->delete_keys(
511 { quiet => 1,
512 keys => [ map { $_->{key} } @key_list[ ( 0, 1 ) ] ]
513 }
514 );
515
516 ok( !$delete_rsp, 'delete_keys() quiet response - empty' )
517 or BAIL_OUT(
518 'could not delete quietly '
519 . Dumper(
520 [ response => $delete_rsp,
521 last_request => $s3->get_last_request,
522 last_response => $s3->get_last_response,
523 ]
524 )
525 );
526
527 $response = $bucket_obj->list
528 or die $s3->err . ': ' . $s3->errstr;
529
530 is(
531 scalar @{ $response->{keys} },
532 -2 + scalar(@key_list),
533 'delete versioned keys'
534 );
535
536 shift @key_list;
537 shift @key_list;
538
539 ######################################################################
540 # delete list of keys - next two keys
541 ######################################################################
542 $delete_rsp
543 = $bucket_obj->delete_keys( map { $_->{key} } @key_list[ ( 0, 1 ) ] );
544
545 ok( $delete_rsp, 'delete_keys() response' );
546
547 $response = $bucket_obj->list
548 or die $s3->err . ': ' . $s3->errstr;
549
550 is(
551 scalar @{ $response->{keys} },
552 -2 + scalar(@key_list),
553 'delete list of keys'
554 );
555
556 shift @key_list;
557 shift @key_list;
558
559 ######################################################################
560 # delete array of keys - next two keys
561 #####################################################################
562 $delete_rsp
563 = $bucket_obj->delete_keys( map { $_->{key} } @key_list[ ( 0, 1 ) ] );
564
565 ok( $delete_rsp, 'delete_keys() response' );
566
567 $response = $bucket_obj->list
568 or die $s3->err . ': ' . $s3->errstr;
569
570 is(
571 scalar @{ $response->{keys} },
572 -2 + scalar(@key_list),
573 'delete array of keys'
574 );
575
576 shift @key_list;
577 shift @key_list;
578
579 ######################################################################
580 # callback - last two keys
581 ######################################################################
582 $delete_rsp = $bucket_obj->delete_keys(
583 sub {
584 my $key = shift @key_list;
585 return ( $key->{key} );
586 }
587 );
588
589 ok( $delete_rsp, 'delete_keys() response' );
590
591 $response = $bucket_obj->list
592 or die $s3->err . ': ' . $s3->errstr;
593
594 is( scalar @{ $response->{keys} }, 0, 'delete keys from callback' )
595 or diag( Dumper( [ response => $response, key_list => \@key_list ] ) );
596
597 #
598 # delete multiple keys from bucket
599 ######################################################################
600 }
601
602 SKIP: {
603 if ( $ENV{AMAZON_S3_KEEP_BUCKET} ) {
604 skip 'keeping bucket', 1;
605 }
606
607 ok( $bucket_obj->delete_bucket(), 'delete bucket' );
608 }
621609 }
622610
623611 # see more docs in Amazon::S3::Bucket
624612
625613 # local test methods
614 ########################################################################
626615 sub is_request_response_code {
616 ########################################################################
627617 my ( $url, $code, $message ) = @_;
618
628619 my $request = HTTP::Request->new( 'GET', $url );
629620
630 #warn $request->as_string();
631621 my $response = $s3->ua->request($request);
632622
633623 is( $response->code, $code, $message )
634 or diag( Dumper($response) );
635 }
636
624 or diag( Dumper( [ response_code => $response ] ) );
625
626 return;
627 }
628
629 ########################################################################
637630 sub like_acl_allusers_read {
638 my ( $bucketobj, $keyname ) = @_;
639
640 my $message = acl_allusers_read_message( 'like', @_ );
641
642 my $acl = $bucketobj->get_acl($keyname);
643
644 like( $acl, qr(AllUsers.+READ), $message )
645 or diag( Dumper [$acl] );
646
647 }
648
631 ########################################################################
632 my ( $bucket_obj, $keyname ) = @_;
633
634 my $message = acl_allusers_read_message( 'like', $bucket_obj, $keyname );
635
636 my $acl = $bucket_obj->get_acl($keyname);
637
638 like( $acl, qr/AllUsers.+READ/xsm, $message )
639 or diag( Dumper( [ acl => $acl ] ) );
640
641 return;
642 }
643
644 ########################################################################
649645 sub unlike_acl_allusers_read {
650 my ( $bucketobj, $keyname ) = @_;
651 my $message = acl_allusers_read_message( 'unlike', @_ );
652 unlike( $bucketobj->get_acl($keyname), qr(AllUsers.+READ), $message );
653 }
654
646 ########################################################################
647 my ( $bucket_obj, $keyname ) = @_;
648
649 my $message = acl_allusers_read_message( 'unlike', $bucket_obj, $keyname );
650
651 my $acl = $bucket_obj->get_acl($keyname);
652
653 unlike( $bucket_obj->get_acl($keyname), qr/AllUsers.+READ/xsm, $message )
654 or diag( Dumper( [ acl => $acl ] ) );
655
656 return;
657 }
658
659 ########################################################################
655660 sub acl_allusers_read_message {
656 my ( $like_or_unlike, $bucketobj, $keyname ) = @_;
657 my $message = $like_or_unlike . "_acl_allusers_read: " . $bucketobj->bucket;
658 $message .= " - $keyname" if $keyname;
661 ########################################################################
662 my ( $like_or_unlike, $bucket_obj, $keyname ) = @_;
663
664 my $message = sprintf '%s_acl_allusers_read: %s', $like_or_unlike,
665 $bucket_obj->bucket;
666
667 if ($keyname) {
668 $message .= " - $keyname";
669 }
670
659671 return $message;
660672 }
661673
674 ########################################################################
662675 sub acl_xml_from_acl_short {
663 my $acl_short = shift || 'private';
664
665 my $public_read = '';
666 if ( $acl_short eq 'public-read' ) {
667 $public_read = qq~
668 <Grant>
669 <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
670 xsi:type="Group">
671 <URI>http://acs.amazonaws.com/groups/global/AllUsers</URI>
672 </Grantee>
673 <Permission>READ</Permission>
674 </Grant>
675 ~;
676 }
677
678 return qq~<?xml version="1.0" encoding="UTF-8"?>
679 <AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
680 <Owner>
681 <ID>$OWNER_ID</ID>
682 <DisplayName>$OWNER_DISPLAYNAME</DisplayName>
683 </Owner>
684 <AccessControlList>
685 <Grant>
686 <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
687 xsi:type="CanonicalUser">
688 <ID>$OWNER_ID</ID>
689 <DisplayName>$OWNER_DISPLAYNAME</DisplayName>
690 </Grantee>
691 <Permission>FULL_CONTROL</Permission>
692 </Grant>
693 $public_read
694 </AccessControlList>
695 </AccessControlPolicy>~;
696 }
697
676 ########################################################################
677 my ($acl_short) = @_;
678
679 $acl_short //= 'private';
680
681 my $public_read
682 = $acl_short eq 'public-read' ? $PUBLIC_READ_POLICY : $EMPTY;
683
684 my $policy = <<"END_OF_POLICY";
685 <?xml version="1.0" encoding="UTF-8"?>
686 <AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
687 <Owner>
688 <ID>$OWNER_ID</ID>
689 <DisplayName>$OWNER_DISPLAYNAME</DisplayName>
690 </Owner>
691 <AccessControlList>
692 <Grant>
693 <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
694 xsi:type="CanonicalUser">
695 <ID>$OWNER_ID</ID>
696 <DisplayName>$OWNER_DISPLAYNAME</DisplayName>
697 </Grantee>
698 <Permission>FULL_CONTROL</Permission>
699 </Grant>
700 $public_read
701 </AccessControlList>
702 </AccessControlPolicy>
703 END_OF_POLICY
704
705 return $policy;
706 }
707
708 1;
44 use warnings;
55 use strict;
66
7 use lib 'lib';
7 use lib qw(lib);
88
99 use English qw{-no_match_vars};
1010
1515
1616 use_ok('Amazon::S3');
1717
18 ########################################################################
1819 sub test_levels {
20 ########################################################################
1921 my ($s3) = @_;
2022
2123 print {*STDERR} "\n---[" . $s3->level . "]---\n";
3032 return;
3133 } ## end sub test_levels
3234
35 ########################################################################
3336 sub test_all_levels {
37 ########################################################################
3438 my ($s3) = @_;
3539
3640 $s3->level('trace');
3741 stderr_like( sub { test_levels($s3); },
38 qr/trace\n.*debug\n.*info\n.*warn\n.*error\n.*fatal\n/xsm );
42 qr/trace\n.*debug\n.*info\n.*warn\n.*error\n.*fatal\n/xsm, 'trace' );
3943
4044 $s3->level('debug');
4145 stderr_like( sub { test_levels($s3); },
42 qr/debug\n.*info\n.*warn\n.*error\n.*fatal\n/xsm );
43 stderr_unlike( sub { test_levels($s3); }, qr/trace/ );
46 qr/debug\n.*info\n.*warn\n.*error\n.*fatal\n/xsm, 'debug' );
47 stderr_unlike( sub { test_levels($s3); },
48 qr/trace/, 'debug - not like trace' );
4449
4550 $s3->level('info');
4651 stderr_like( sub { test_levels($s3); },
47 qr/info\n.*warn\n.*error\n.*fatal\n/xsm );
48 stderr_unlike( sub { test_levels($s3); }, qr/trace|debug/ );
52 qr/info\n.*warn\n.*error\n.*fatal\n/xsm, 'info' );
53 stderr_unlike( sub { test_levels($s3); },
54 qr/trace|debug/, 'info - not like trace, debug' );
4955
5056 $s3->level('warn');
51 stderr_like( sub { test_levels($s3); }, qr/warn\n.*error\n.*fatal\n/xsm );
52 stderr_unlike( sub { test_levels($s3); }, qr/trace|debug|info/ );
57 stderr_like( sub { test_levels($s3); },
58 qr/warn\n.*error\n.*fatal\n/xsm, 'warn' );
59 stderr_unlike( sub { test_levels($s3); },
60 qr/trace|debug|info/, 'warn - not like trace, debug, info' );
5361
5462 $s3->level('error');
55 stderr_like( sub { test_levels($s3); }, qr/error\n.*fatal\n/xsm );
56 stderr_unlike( sub { test_levels($s3); }, qr/trace|debug|info|warn/ );
63 stderr_like( sub { test_levels($s3); }, qr/error\n.*fatal\n/xsm, 'error' );
64 stderr_unlike( sub { test_levels($s3); },
65 qr/trace|debug|info|warn/, 'error - not like trace, debug, info, warn' );
5766
5867 $s3->level('fatal');
59 stderr_like( sub { test_levels($s3); }, qr/fatal\n/xsm );
60 stderr_unlike( sub { test_levels($s3); }, qr/trace|debug|info|warn|error/ );
68 stderr_like( sub { test_levels($s3); }, qr/fatal\n/xsm, 'fatal' );
69 stderr_unlike(
70 sub { test_levels($s3); },
71 qr/trace|debug|info|warn|error/,
72 'fatal - not like trace, debug, info, warn, error'
73 );
6174
6275 } ## end sub test_all_levels
76
77 ########################################################################
6378
6479 my $s3 = Amazon::S3->new(
6580 { aws_access_key_id => 'test',
44 use warnings;
55 use strict;
66
7 use lib 'lib';
7 use lib qw(lib);
88
99 use English qw{-no_match_vars};
1010
1111 use Test::More;
12
1213 plan tests => 7;
1314
1415 use_ok('Amazon::S3');
2122 );
2223
2324 ok( $s3->region, 'us-east-1' );
25
2426 is( $s3->host, 's3.us-east-1.amazonaws.com',
2527 'default host is s3.us-east-1.amazonaws.com' );
2628
3335 );
3436
3537 is( $s3->region, 'us-west-2', 'region is set' );
38
3639 is( $s3->host, 's3.us-west-2.amazonaws.com',
3740 'host is modified during creation' );
3841
3942 $s3->region('us-east-1');
4043
4144 is( $s3->region, 'us-east-1', 'region is set' );
45
4246 is( $s3->host, 's3.us-east-1.amazonaws.com',
4347 'host is modified when region changes' );
4448
44 use warnings;
55 use strict;
66
7 use lib 'lib';
7 use lib qw(. lib);
88
99 use English qw{-no_match_vars};
10
11 use S3TestUtils qw(:constants :subs);
1012
1113 use Test::More;
1214 use Data::Dumper;
1315
14 my $aws_access_key_id = $ENV{'AWS_ACCESS_KEY_ID'} // 'foo';
15 my $aws_secret_access_key = $ENV{'AWS_ACCESS_KEY_SECRET'} // 'foo';
16 my $token = $ENV{'AWS_SESSION_TOKEN'};
17
18 my $host = $ENV{AMAZON_S3_HOST};
16 my $host = set_s3_host();
1917
2018 if ( !$ENV{'AMAZON_S3_EXPENSIVE_TESTS'} ) {
2119 plan skip_all => 'Testing this module for real costs money.';
22 } ## end if ( !$ENV{'AMAZON_S3_EXPENSIVE_TESTS'...})
20 }
2321 else {
24 plan tests => 16;
25 } ## end else [ if ( !$ENV{'AMAZON_S3_EXPENSIVE_TESTS'...})]
22 plan tests => 9;
23 }
2624
2725 ########################################################################
2826 # BEGIN TESTS
3129 use_ok('Amazon::S3');
3230 use_ok('Amazon::S3::Bucket');
3331
34 my $s3;
35
36 if ( $ENV{AMAZON_S3_CREDENTIALS} ) {
37 require Amazon::Credentials;
38
39 $s3 = Amazon::S3->new(
40 { credentials => Amazon::Credentials->new,
41 host => $host,
42 log_level => $ENV{DEBUG} ? 'debug' : undef,
43 }
44 );
45 ( $aws_access_key_id, $aws_secret_access_key, $token )
46 = $s3->get_credentials;
47 } ## end if ( $ENV{AMAZON_S3_CREDENTIALS...})
48 else {
49 $s3 = Amazon::S3->new(
50 { aws_access_key_id => $aws_access_key_id,
51 aws_secret_access_key => $aws_secret_access_key,
52 token => $token,
53 debug => $ENV{DEBUG},
54 host => $host,
55 secure => $host ? 0 : 1, # if host then probably container
56 }
57 );
58 } ## end else [ if ( $ENV{AMAZON_S3_CREDENTIALS...})]
59
60 my $bucketname_raw = sprintf 'net-amazon-s3-test-%s', lc $aws_access_key_id;
61
62 my $bucketname = '/' . $bucketname_raw;
63
64 my $bucket_obj = eval { $s3->add_bucket( { bucket => $bucketname } ); };
32 my $s3 = get_s3_service($host);
33
34 my $bucket_name = make_bucket_name();
35
36 my $bucket_obj = create_bucket( $s3, $bucket_name );
37
38 ok( ref $bucket_obj, 'created bucket - ' . $bucket_name );
6539
6640 if ( $EVAL_ERROR || !$bucket_obj ) {
6741 BAIL_OUT( $s3->err . ": " . $s3->errstr );
68 } ## end if ( $EVAL_ERROR || !$bucket_obj)
69
70 is( ref $bucket_obj, 'Amazon::S3::Bucket', 'created bucket' . $bucketname )
71 or BAIL_OUT("could not create bucket $bucketname");
72
73 my $response = $bucket_obj->list
74 or BAIL_OUT( $s3->err . ": " . $s3->errstr );
75
76 is( $response->{bucket}, $bucketname_raw, 'no bucket name in list response' )
77 or do {
78 diag( Dumper( [$response] ) );
79 BAIL_OUT( Dumper [$response] );
80 };
81
82 ok( !$response->{prefix}, 'no prefix in list response' );
83 ok( !$response->{marker}, 'no marker in list response' );
84
85 is( $response->{max_keys}, 1_000, 'max keys default = 1000' )
86 or BAIL_OUT( Dumper [$response] );
87
88 is( $response->{is_truncated}, 0, 'is_truncated 0' );
89
90 is_deeply( $response->{keys}, [], 'no keys in bucket yet' )
91 or BAIL_OUT( Dumper( [$response] ) );
92
93 foreach my $key ( 0 .. 9 ) {
94 my $keyname = sprintf 'testing-%02d.txt', $key;
95 my $value = 'T';
96
97 $bucket_obj->add_key( $keyname, $value );
98 } ## end foreach my $key ( 0 .. 9 )
99
42 }
43
44 my $max_keys = 25;
45
46 ########################################################################
47 subtest 'list (check response elements)' => sub {
48 ########################################################################
49 my $response = $bucket_obj->list
50 or BAIL_OUT( $s3->err . ": " . $s3->errstr );
51
52 is( $response->{bucket}, $bucket_name, 'no bucket name in list response' )
53 or do {
54 diag( Dumper( [$response] ) );
55 BAIL_OUT( Dumper [$response] );
56 };
57
58 ok( !$response->{prefix}, 'no prefix in list response' );
59 ok( !$response->{marker}, 'no marker in list response' );
60
61 is( $response->{max_keys}, 1_000, 'max keys default = 1000' )
62 or BAIL_OUT( Dumper [$response] );
63
64 is( $response->{is_truncated}, 0, 'is_truncated 0' );
65
66 is_deeply( $response->{keys}, [], 'no keys in bucket yet' )
67 or BAIL_OUT( Dumper( [$response] ) );
68 };
69
70 ########################################################################
10071 subtest 'list_all' => sub {
72 ########################################################################
73
74 add_keys( $bucket_obj, $max_keys );
75
10176 my $response = $bucket_obj->list_all;
10277
10378 is( ref $response, 'HASH', 'response isa HASH' )
10681 is( ref $response->{keys}, 'ARRAY', 'keys element is an ARRAY' )
10782 or diag( Dumper( [$response] ) );
10883
109 is( @{ $response->{keys} }, 10, '10 keys returned' )
84 is( @{ $response->{keys} }, $max_keys, $max_keys . ' keys returned' )
11085 or diag( Dumper( [$response] ) );
11186
11287 foreach my $key ( @{ $response->{keys} } ) {
11388 is( ref $key, 'HASH', 'array element isa HASH' )
11489 or diag( Dumper( [$key] ) );
11590
116 like( $key->{key}, qr/testing-\d{2}.txt/, 'keyname' )
91 like( $key->{key}, qr/testing-\d{2}[.]txt/xsm, 'keyname' )
11792 or diag( Dumper( [$key] ) );
11893
119 } ## end foreach my $key ( @{ $response...})
120 };
121
94 }
95 };
96
97 ########################################################################
12298 subtest 'list' => sub {
99 ########################################################################
123100
124101 my $marker = '';
125102 my $iter = 0; # so we don't loop forever if this is busted
126103
127104 my @key_list;
105 my $page_size = int $max_keys / 2;
128106
129107 while ( $marker || !$iter ) {
130 last if $iter++ > 5;
131
132 $response = $bucket_obj->list(
133 { 'max-keys' => 3,
108 last if $iter++ > $max_keys;
109
110 my $response = $bucket_obj->list(
111 { 'max-keys' => $page_size,
134112 marker => $marker,
135 delimiter => '/'
113 delimiter => '/',
136114 }
137115 );
138116
139117 if ( !$response ) {
140118 BAIL_OUT( $s3->err . ": " . $s3->errstr );
141 } ## end if ( !$response )
142
143 is( $response->{bucket}, $bucketname_raw, 'no bucket name' );
119 }
120
121 is( $response->{bucket}, $bucket_name, 'no bucket name' );
144122
145123 ok( !$response->{prefix}, 'no prefix' )
146124 or diag( Dumper [$response] );
147125
148 is( $response->{max_keys}, 3, 'max-keys 3' );
126 is( $response->{max_keys}, $page_size, 'max-keys ' . $page_size );
149127
150128 is( ref $response->{keys}, 'ARRAY' )
151129 or BAIL_OUT( Dumper( [$response] ) );
153131 push @key_list, @{ $response->{keys} };
154132
155133 $marker = $response->{next_marker};
156 } ## end while ( $marker || !$iter)
157
158 is( @key_list, 10, 'got 10 keys' )
159 or diag( Dumper( \@key_list ) );
160 };
161
162 subtest 'list-v2' => sub {
134
135 last if !$marker;
136 }
137
138 is( @key_list, $max_keys, $max_keys . ' returned' )
139 or diag( Dumper( [ key_list => \@key_list ] ) );
140 };
141
142 ########################################################################
143 subtest 'list_v2' => sub {
144 ########################################################################
163145
164146 my $marker = '';
165147 my $iter = 0; # so we don't loop forever if this is busted
166148
167149 my @key_list;
150 my $page_size = int $max_keys / 2;
168151
169152 while ( $marker || !$iter ) {
170 last if $iter++ > 5;
171
172 $response = $bucket_obj->list_v2(
173 { 'max-keys' => 3,
153 last if $iter++ > $max_keys;
154
155 my $response = $bucket_obj->list_v2(
156 { 'max-keys' => $page_size,
174157 $marker ? ( 'marker' => $marker ) : (),
175158 delimiter => '/',
176159 }
178161
179162 if ( !$response ) {
180163 BAIL_OUT( $s3->err . ": " . $s3->errstr );
181 } ## end if ( !$response )
182
183 is( $response->{bucket}, $bucketname_raw, 'no bucket name' );
164 }
165
166 is( $response->{bucket}, $bucket_name, 'no bucket name' );
184167
185168 ok( !$response->{prefix}, 'no prefix' )
186169 or diag( Dumper [$response] );
187170
188 is( $response->{max_keys}, 3, 'max-keys 3' );
171 is( $response->{max_keys}, $page_size, 'max-keys ' . $page_size );
189172
190173 is( ref $response->{keys}, 'ARRAY' )
191174 or BAIL_OUT( Dumper( [$response] ) );
193176 push @key_list, @{ $response->{keys} };
194177
195178 $marker = $response->{next_marker};
196 } ## end while ( $marker || !$iter)
197
198 is( @key_list, 10, 'got 10 keys' )
179
180 last if !$marker;
181 }
182
183 is( @key_list, $max_keys, $max_keys . ' returned' )
199184 or diag( Dumper( \@key_list ) );
200185 };
201186
202 $response = $s3->list_bucket_all( { bucket => $bucketname } );
203
204 is( ref $response, 'HASH', 'list_bucket_all response is a HASH' );
205 is( @{ $response->{keys} }, 10, 'got all 10 keys' );
206
207 $response = $s3->list_bucket_all_v2( { bucket => $bucketname } );
208 is( ref $response, 'HASH', 'list_bucket_all_v2 response is a HASH' );
209 is( @{ $response->{keys} }, 10, 'got all 10 keys' );
210
211 foreach my $key ( 0 .. 9 ) {
212 my $keyname = sprintf 'testing-%02d.txt', $key;
213
214 $bucket_obj->delete_key($keyname);
215 } ## end foreach my $key ( 0 .. 9 )
187 ########################################################################
188 subtest 'list_bucket_all' => sub {
189 ########################################################################
190
191 $max_keys += add_keys( $bucket_obj, $max_keys, 'foo/' );
192
193 my $response = $s3->list_bucket_all( { bucket => $bucket_name } );
194
195 is( ref $response, 'HASH', 'list_bucket_all response is a HASH' );
196
197 is( @{ $response->{keys} }, $max_keys, $max_keys . ' returned' );
198 };
199
200 ########################################################################
201 subtest 'list_bucket_all_v2' => sub {
202 ########################################################################
203
204 my $response = $s3->list_bucket_all_v2( { bucket => $bucket_name } );
205
206 is( ref $response, 'HASH', 'list_bucket_all_v2 response is a HASH' );
207
208 is( @{ $response->{keys} }, $max_keys, $max_keys . ' returned' );
209
210 foreach ( @{ $response->{keys} } ) {
211 $bucket_obj->delete_key( $_->{key} );
212 }
213 };
216214
217215 $bucket_obj->delete_bucket;
218216
44 use warnings;
55 use strict;
66
7 use lib 'lib';
7 use lib qw( . lib);
88
99 use Carp;
1010
1111 use Data::Dumper;
1212 use Digest::MD5::File qw(file_md5_hex);
13 use English qw{-no_match_vars};
14 use File::Temp qw{ tempfile };
13 use English qw{-no_match_vars};
14 use File::Temp qw{ tempfile };
1515 use Test::More;
1616
17 my $host;
17 use S3TestUtils qw(:constants :subs);
1818
19 if ( exists $ENV{AMAZON_S3_LOCALSTACK} ) {
20 $host = 'localhost:4566';
21
22 $ENV{'AWS_ACCESS_KEY_ID'} = 'test';
23 $ENV{'AWS_ACCESS_KEY_SECRET'} = 'test';
24
25 $ENV{'AMAZON_S3_EXPENSIVE_TESTS'} = 1;
26
27 } ## end if ( exists $ENV{AMAZON_S3_LOCALSTACK...})
28 else {
29 $host = $ENV{AMAZON_S3_HOST};
30 } ## end else [ if ( exists $ENV{AMAZON_S3_LOCALSTACK...})]
31
32 my $secure = $host ? 0 : 1;
33
34 # do not use DNS bucket names for testing if a mocking service is used
35 # override this by setting AMAZON_S3_DNS_BUCKET_NAMES to any value
36 # your tests may fail unless you have DNS entry for the bucket name
37 # e.g 127.0.0.1 net-amazon-s3-test-test.localhost
38
39 my $dns_bucket_names
40 = ( $host && !exists $ENV{AMAZON_S3_DNS_BUCKET_NAMES} ) ? 0 : 1;
41
42 my $aws_access_key_id = $ENV{'AWS_ACCESS_KEY_ID'};
43 my $aws_secret_access_key = $ENV{'AWS_ACCESS_KEY_SECRET'};
44 my $token = $ENV{'AWS_SESSION_TOKEN'};
19 my $host = set_s3_host();
4520
4621 if ( !$ENV{'AMAZON_S3_EXPENSIVE_TESTS'} ) {
4722 plan skip_all => 'Testing this module for real costs money.';
48 } ## end if ( !$ENV{'AMAZON_S3_EXPENSIVE_TESTS'...})
23 }
4924 else {
5025 plan tests => 7;
5126 }
5328 use_ok('Amazon::S3');
5429 use_ok('Amazon::S3::Bucket');
5530
56 my $s3;
31 my $s3 = get_s3_service($host);
5732
58 if ( $ENV{AMAZON_S3_CREDENTIALS} ) {
59 require Amazon::Credentials;
60
61 $s3 = Amazon::S3->new(
62 { credentials => Amazon::Credentials->new,
63 host => $host,
64 secure => $secure,
65 dns_bucket_names => $dns_bucket_names,
66 level => $ENV{DEBUG} ? 'trace' : 'error',
67 }
68 );
69 ( $aws_access_key_id, $aws_secret_access_key, $token )
70 = $s3->get_credentials;
71 } ## end if ( $ENV{AMAZON_S3_CREDENTIALS...})
72 else {
73 $s3 = Amazon::S3->new(
74 { aws_access_key_id => $aws_access_key_id,
75 aws_secret_access_key => $aws_secret_access_key,
76 token => $token,
77 host => $host,
78 secure => $secure,
79 dns_bucket_names => $dns_bucket_names,
80 level => $ENV{DEBUG} ? 'trace' : 'error',
81 }
82 );
83 } ## end else [ if ( $ENV{AMAZON_S3_CREDENTIALS...})]
84
85 sub create_bucket {
86 my ($bucket_name) = @_;
87
88 $bucket_name = '/' . $bucket_name;
89 my $bucket_obj
90 = eval { return $s3->add_bucket( { bucket => $bucket_name } ); };
91
92 return $bucket_obj;
33 if ( !$s3 ) {
34 BAIL_OUT('could not initialize s3 object');
9335 }
9436
95 my $bucket_obj = create_bucket sprintf 'net-amazon-s3-test-%s',
96 lc $aws_access_key_id;
37 my $bucket_name = make_bucket_name();
38 my $bucket_obj = create_bucket( $s3, $bucket_name );
9739
98 ok( ref $bucket_obj, 'created bucket' );
40 ok( ref $bucket_obj, 'created bucket - ' . $bucket_name );
9941
10042 if ( $EVAL_ERROR || !$bucket_obj ) {
10143 BAIL_OUT( $s3->err . ": " . $s3->errstr );
10244 } ## end if ( $EVAL_ERROR || !$bucket_obj)
10345
46 ########################################################################
10447 subtest 'multipart-manual' => sub {
48 ########################################################################
10549 my $key = 'big-object-1';
10650
10751 my $id = $bucket_obj->initiate_multipart_upload($key);
12973 ok( $bucket_obj->delete_key($key) );
13074 };
13175
76 ########################################################################
13277 subtest 'multipart-file' => sub {
78 ########################################################################
13379 my ( $fh, $file ) = tempfile();
13480
13581 my $buffer = 'x' x ( 1024 * 1024 );
166112 unlink $file;
167113 };
168114
115 ########################################################################
169116 subtest 'multipart-2-parts' => sub {
117 ########################################################################
170118 my $length = 1024 * 1024 * 7;
171119
172120 my $data = 'x' x $length;
189137 $bucket_obj->delete_key($key);
190138 };
191139
140 ########################################################################
192141 subtest 'multipart-callback' => sub {
142 ########################################################################
193143 my $key = 'big-object-4';
194144
195145 my @part = ( 5, 5, 5, 1 );
222172 $bucket_obj->delete_key($key);
223173 };
224174
175 ########################################################################
176
225177 $bucket_obj->delete_bucket()
226178 or diag( $s3->errstr );
227179
180 1;
44 use warnings;
55 use strict;
66
7 use lib 'lib';
7 use lib qw(. lib);
88
99 use Carp;
1010
1111 use Data::Dumper;
1212 use Digest::MD5::File qw(file_md5_hex);
13 use English qw{-no_match_vars};
14 use File::Temp qw{ tempfile };
13 use English qw(-no_match_vars);
14 use File::Temp qw( tempfile );
15 use S3TestUtils qw(:constants :subs);
1516 use Test::More;
1617 use XML::Simple qw{XMLin};
1718
18 my $host;
19
20 if ( exists $ENV{AMAZON_S3_LOCALSTACK} ) {
21 $host = 'localhost:4566';
22
23 $ENV{'AWS_ACCESS_KEY_ID'} = 'test';
24 $ENV{'AWS_ACCESS_KEY_SECRET'} = 'test';
25
26 $ENV{'AMAZON_S3_EXPENSIVE_TESTS'} = 1;
27
28 } ## end if ( exists $ENV{AMAZON_S3_LOCALSTACK...})
29 else {
30 $host = $ENV{AMAZON_S3_HOST};
31 } ## end else [ if ( exists $ENV{AMAZON_S3_LOCALSTACK...})]
32
33 my $secure = $host ? 0 : 1;
34
35 # do not use DNS bucket names for testing if a mocking service is used
36 # override this by setting AMAZON_S3_DNS_BUCKET_NAMES to any value
37 # your tests may fail unless you have DNS entry for the bucket name
38 # e.g 127.0.0.1 net-amazon-s3-test-test.localhost
39
40 my $dns_bucket_names
41 = ( $host && !exists $ENV{AMAZON_S3_DNS_BUCKET_NAMES} ) ? 0 : 1;
42
43 my $aws_access_key_id = $ENV{'AWS_ACCESS_KEY_ID'};
44 my $aws_secret_access_key = $ENV{'AWS_ACCESS_KEY_SECRET'};
45 my $token = $ENV{'AWS_SESSION_TOKEN'};
19 my $host = set_s3_host();
4620
4721 if ( !$ENV{'AMAZON_S3_EXPENSIVE_TESTS'} ) {
4822 plan skip_all => 'Testing this module for real costs money.';
5428 use_ok('Amazon::S3');
5529 use_ok('Amazon::S3::Bucket');
5630
57 my $s3;
31 my $s3 = get_s3_service($host);
5832
59 if ( $ENV{AMAZON_S3_CREDENTIALS} ) {
60 require Amazon::Credentials;
33 my $bucket_name = make_bucket_name();
6134
62 $s3 = Amazon::S3->new(
63 { credentials => Amazon::Credentials->new,
64 host => $host,
65 secure => $secure,
66 dns_bucket_names => $dns_bucket_names,
67 level => $ENV{DEBUG} ? 'trace' : 'error',
68 }
69 );
70 ( $aws_access_key_id, $aws_secret_access_key, $token )
71 = $s3->get_credentials;
72 } ## end if ( $ENV{AMAZON_S3_CREDENTIALS...})
73 else {
74 $s3 = Amazon::S3->new(
75 { aws_access_key_id => $aws_access_key_id,
76 aws_secret_access_key => $aws_secret_access_key,
77 token => $token,
78 host => $host,
79 secure => $secure,
80 dns_bucket_names => $dns_bucket_names,
81 level => $ENV{DEBUG} ? 'trace' : 'error',
82 }
83 );
84 } ## end else [ if ( $ENV{AMAZON_S3_CREDENTIALS...})]
85
86 ########################################################################
87 sub list_multipart_uploads {
88 ########################################################################
89 my ($bucket_obj) = @_;
90
91 my $xml = $bucket_obj->list_multipart_uploads;
92
93 ok( $xml =~ /^</xms, 'is xml result' );
94
95 my $uploads = XMLin( $xml, KeepRoot => 1 );
96
97 isa_ok( $uploads, 'HASH', 'made a hash object' )
98 or diag($uploads);
99
100 ok( defined $uploads->{ListMultipartUploadsResult},
101 'looks like a results object' )
102 or diag($xml);
103
104 my $upload_list = $uploads->{ListMultipartUploadsResult}->{Upload};
105
106 return $upload_list;
107 }
108
109 ########################################################################
110 sub partial_upload {
111 ########################################################################
112 my ( $key, $bucket_obj, $size_in_mb ) = @_;
113
114 my $id = $bucket_obj->initiate_multipart_upload($key);
115 my $length = ( $size_in_mb || 5 ) * 1024 * 1024;
116
117 my $data = 'x' x $length;
118
119 my $etag
120 = $bucket_obj->upload_part_of_multipart_upload( $key, $id, 1, $data,
121 $length );
122
123 return $id;
124 }
125
126 ########################################################################
127 sub create_bucket {
128 ########################################################################
129 my ($bucket_name) = @_;
130
131 $bucket_name = '/' . $bucket_name;
132 my $bucket_obj
133 = eval { return $s3->add_bucket( { bucket => $bucket_name } ); };
134
135 return $bucket_obj;
136 }
137
138 my $bucket_name = sprintf 'net-amazon-s3-test-%s', lc $aws_access_key_id;
139 my $bucket_obj = create_bucket $bucket_name;
35 my $bucket_obj = create_bucket( $s3, $bucket_name );
14036
14137 ok( ref $bucket_obj, 'created bucket - ' . $bucket_name );
14238
14743 my $id;
14844 my $key = 'big-object-1';
14945
46 ########################################################################
15047 subtest 'list-multipart-uploads' => sub {
48 ########################################################################
15149
15250 my $upload_list = list_multipart_uploads($bucket_obj);
51
15352 ok( !defined $upload_list, 'no in-progress uploads' )
15453 or diag( Dumper( [$upload_list] ) );
15554
16059 ok( $upload_list->{UploadId} eq $id, 'UploadId eq $id' );
16160 };
16261
62 ########################################################################
16363 subtest 'abort-multipart-upload' => sub {
64 ########################################################################
16465
16566 $bucket_obj->abort_multipart_upload( $key, $id );
16667
16970 ok( !defined $upload_list, 'aborted upload' );
17071 };
17172
73 ########################################################################
17274 subtest 'abort-on-error' => sub {
75 ########################################################################
17376 my $id = $bucket_obj->initiate_multipart_upload($key);
17477
17578 my $part_list = {};
19497 $bucket_obj->abort_multipart_upload( $key, $id );
19598 };
19699
100 ########################################################################
101
197102 $bucket_obj->delete_bucket()
198103 or diag( $s3->errstr );
199104
105 ########################################################################
106 sub partial_upload {
107 ########################################################################
108 my ( $key, $bucket_obj, $size_in_mb ) = @_;
109
110 my $id = $bucket_obj->initiate_multipart_upload($key);
111 my $length = ( $size_in_mb || 5 ) * 1024 * 1024;
112
113 my $data = 'x' x $length;
114
115 my $etag
116 = $bucket_obj->upload_part_of_multipart_upload( $key, $id, 1, $data,
117 $length );
118
119 return $id;
120 }
121
122 ########################################################################
123 sub list_multipart_uploads {
124 ########################################################################
125 my ($bucket_obj) = @_;
126
127 my $xml = $bucket_obj->list_multipart_uploads;
128
129 ok( $xml =~ /^</xms, 'is xml result' );
130
131 my $uploads = XMLin( $xml, KeepRoot => $TRUE );
132
133 isa_ok( $uploads, 'HASH', 'made a hash object' )
134 or diag($uploads);
135
136 ok( defined $uploads->{ListMultipartUploadsResult},
137 'looks like a results object' )
138 or diag($xml);
139
140 my $upload_list = $uploads->{ListMultipartUploadsResult}->{Upload};
141
142 return $upload_list;
143 }
144
145 1;
0 AMAZON_S3_EXPENSIVE_TESTS=1 \
1 AMAZON_S3_HOST=s3.localhost.localstack.cloud:4566 \
2 AMAZON_S3_LOCALSTACK=1 \
3 AWS_ACCESS_KEY_ID=test \
4 AWS_ACCESS_SECRET_KEY=test \
5 AMAZON_S3_DOMAIN_BUCKET_NAMES=1 make test >test.log 2>&1
0 #!/bin/bash
1 # -*- mode: sh; -*-
62
3 BUCKET=net-amazon-s3-test-test
4 ENDPOINT_URL=s3.localhost.localstack.cloud:4566
5
6 AMAZON_S3_EXPENSIVE_TESTS=1 \
7 AMAZON_S3_HOST=$ENDPOINT_URL \
8 AMAZON_S3_LOCALSTACK=1 \
9 AWS_ACCESS_KEY_ID=test \
10 AWS_ACCESS_SECRET_KEY=test \
11 AMAZON_S3_DOMAIN_BUCKET_NAMES=1 make test 2>&1 | tee test.log
12