Amazon-S3
view release on metacpan or search on metacpan
lib/Amazon/S3.pm view on Meta::CPAN
use HTTP::Date;
use LWP::UserAgent::Determined;
use List::Util qw( any pairs none );
use MIME::Base64 qw(encode_base64 decode_base64);
use Scalar::Util qw( reftype blessed );
use URI;
use XML::Simple;
use parent qw(Class::Accessor::Fast Exporter);
__PACKAGE__->mk_accessors(
qw(
aws_access_key_id
aws_secret_access_key
token
buffer_size
cache_signer
credentials
dns_bucket_names
digest
err
errstr
error
express
host
last_request
last_response
logger
log_level
retry
_region
secure
_signer
timeout
ua
),
);
our $VERSION = '2.0.2'; ## no critic (RequireInterpolation)
our @EXPORT_OK = qw(is_domain_bucket);
########################################################################
sub new {
########################################################################
my ( $class, @args ) = @_;
my %options = ref $args[0] ? %{ $args[0] } : @args;
$options{timeout} //= $DEFAULT_TIMEOUT;
$options{secure} //= $TRUE;
$options{host} //= $DEFAULT_HOST;
$options{dns_bucket_names} //= $TRUE;
$options{cache_signer} //= $FALSE;
$options{retry} //= $FALSE;
$options{express} //= $FALSE;
$options{_region} = delete $options{region};
$options{_signer} = delete $options{signer};
# convenience for level => 'debug' & for consistency with
# Amazon::Credentials only do this if we are using internal logger,
# call should NOT use debug flag but rather use their own logger's
# level to turn on higher levels of logging...
if ( !$options{logger} ) {
if ( delete $options{debug} ) {
$options{level} = 'debug';
}
$options{log_level} = delete $options{level};
$options{log_level} //= $DEFAULT_LOG_LEVEL;
$options{logger}
= Amazon::S3::Logger->new( log_level => $options{log_level} );
}
my $self = $class->SUPER::new( \%options );
# setup logger internal logging
$self->get_logger->debug(
sub {
my %safe_options = %options;
if ( $safe_options{aws_secret_access_key} ) {
$safe_options{aws_secret_access_key} = '****';
$safe_options{aws_access_key_id} = '****';
}
return Dumper( [ options => \%safe_options ] );
},
);
if ( !$self->credentials ) {
croak 'No aws_access_key_id'
if !$self->aws_access_key_id;
croak 'No aws_secret_access_key'
if !$self->aws_secret_access_key;
# encrypt credentials
$self->aws_access_key_id( _encrypt( $self->aws_access_key_id ) );
$self->aws_secret_access_key( _encrypt( $self->aws_secret_access_key ) );
$self->token( _encrypt( $self->token ) );
}
my $ua;
if ( $self->retry ) {
$ua = LWP::UserAgent::Determined->new(
keep_alive => $KEEP_ALIVE_CACHESIZE,
requests_redirectable => [qw(GET HEAD DELETE)],
);
$ua->timing( join $COMMA, map { 2**$_ } 0 .. $MAX_RETRIES );
}
else {
$ua = LWP::UserAgent->new(
keep_alive => $KEEP_ALIVE_CACHESIZE,
requests_redirectable => [qw(GET HEAD DELETE)],
);
}
$ua->timeout( $self->timeout );
$ua->env_proxy;
$self->ua($ua);
$self->region( $self->_region // $DEFAULT_REGION );
if ( !$self->_signer && $self->cache_signer ) {
$self->_signer( $self->signer );
}
if ( $self->express ) {
$self->use_express_one_zone();
}
$self->turn_on_special_retry();
return $self;
lib/Amazon/S3.pm view on Meta::CPAN
my ($self) = @_;
return _decrypt( $self->aws_secret_access_key );
}
########################################################################
sub get_token {
########################################################################
my ($self) = @_;
return _decrypt( $self->token );
}
########################################################################
sub turn_on_special_retry {
########################################################################
my ($self) = @_;
return
if !$self->retry;
# In the field we are seeing issue of Amazon returning with a 400
# code in the case of timeout. From AWS S3 logs: REST.PUT.PART
# Backups/2017-05-04/<account>.tar.gz "PUT
# /Backups<path>?partNumber=27&uploadId=<id> - HTTP/1.1" 400
# RequestTimeout 360 20971520 20478 - "-" "libwww-perl/6.15"
my $http_codes_hr = $self->ua->codes_to_determinate();
$http_codes_hr->{$HTTP_BAD_REQUEST} = $TRUE;
return;
}
########################################################################
sub turn_off_special_retry {
########################################################################
my ($self) = @_;
return
if !$self->retry;
# In the field we are seeing issue with Amazon returning a 400
# code in the case of timeout. From AWS S3 logs: REST.PUT.PART
# Backups/2017-05-04/<account>.tar.gz "PUT
# /Backups<path>?partNumber=27&uploadId=<id> - HTTP/1.1" 400
# RequestTimeout 360 20971520 20478 - "-" "libwww-perl/6.15"
my $http_codes_hr = $self->ua->codes_to_determinate();
delete $http_codes_hr->{$HTTP_BAD_REQUEST};
return;
}
########################################################################
sub region {
########################################################################
my ( $self, @args ) = @_;
if (@args) {
$self->_region( $args[0] );
}
$self->get_logger->debug(
sub { return 'region: ' . ( $self->_region // $EMPTY ) } );
if ( $self->_region ) {
my $host = $self->host;
$self->get_logger->debug( sub { return 'host: ' . $self->host } );
if ( $host =~ /\As3[.](.*)?amazonaws/xsm ) {
$self->host( sprintf 's3.%s.amazonaws.com', $self->_region );
}
}
return $self->_region;
}
########################################################################
sub buckets {
########################################################################
my ( $self, $verify_region ) = @_;
# The "default" region for Amazon is us-east-1
# This is the region to set it to for listing buckets
# You may need to reset the signer's endpoint to 'us-east-1'
# temporarily cache signer
my $region = $self->_region;
my $bucket_list;
$self->reset_signer_region($DEFAULT_REGION); # default region for buckets op
my $r = $self->_send_request(
{ method => 'GET',
path => $EMPTY,
headers => {},
region => $DEFAULT_REGION,
},
);
return $bucket_list
if !$r || $self->errstr;
my $owner_id = $r->{Owner}{ID};
my $owner_displayname = $r->{Owner}{DisplayName};
my @buckets;
if ( ref $r->{Buckets} ) {
my $buckets = $r->{Buckets}{Bucket};
if ( !ref $buckets || reftype($buckets) ne 'ARRAY' ) {
$buckets = [$buckets];
}
foreach my $node ( @{$buckets} ) {
push @buckets,
Amazon::S3::Bucket->new(
{ bucket => $node->{Name},
creation_date => $node->{CreationDate},
account => $self,
buffer_size => $self->buffer_size,
verify_region => $verify_region // $FALSE,
},
);
}
}
lib/Amazon/S3.pm view on Meta::CPAN
$self->express($express);
return $result;
}
########################################################################
sub list_bucket_v2 {
########################################################################
my ( $self, $conf ) = @_;
$conf->{'list-type'} = '2';
goto &list_bucket;
}
########################################################################
sub list_bucket {
########################################################################
my ( $self, $conf ) = @_;
my $bucket = delete $conf->{bucket};
croak 'must specify bucket'
if !$bucket;
$conf //= {};
my $bucket_list; # return this
my $path = $bucket . $SLASH;
my $headers = delete $conf->{headers};
my $list_type = $conf->{'list-type'} // '1';
my ( $marker, $next_marker, $query_next )
= @{ $LIST_OBJECT_MARKERS{$list_type} };
if ( $conf->{marker} ) {
$conf->{$query_next} = delete $conf->{marker};
}
if ( %{$conf} ) {
my @vars = keys %{$conf};
# remove undefined elements
foreach (@vars) {
next if defined $conf->{$_};
delete $conf->{$_};
}
my $query_string = $QUESTION_MARK . join $AMPERSAND,
map { $_ . $EQUAL_SIGN . urlencode( $conf->{$_} ) }
keys %{$conf};
$path .= $query_string;
}
$self->get_logger->debug( sprintf 'PATH: %s', $path );
my $r = $self->_send_request(
{ method => 'GET',
path => $path,
headers => $headers // {}, # { 'Content-Length' => 0 },
region => $self->region,
},
);
$self->get_logger->trace(
Dumper(
[ r => $r,
errstr => $self->errstr,
]
)
);
return $bucket_list
if !$r || $self->errstr;
$self->get_logger->trace(
sub {
return Dumper(
[ marker => $marker,
next_marker => $next_marker,
response => $r,
],
);
},
);
$bucket_list = {
bucket => $r->{Name},
prefix => $r->{Prefix} // $EMPTY,
marker => $r->{$marker} // $EMPTY,
next_marker => $r->{$next_marker} // $EMPTY,
max_keys => $r->{MaxKeys},
is_truncated => (
( defined $r->{IsTruncated} && scalar $r->{IsTruncated} eq 'true' )
? $TRUE
: $FALSE
),
};
my @keys;
foreach my $node ( @{ $r->{Contents} } ) {
my $etag = $node->{ETag};
if ( defined $etag ) {
$etag =~ s{(^"|"$)}{}gxsm;
}
push @keys,
{
key => $node->{Key},
last_modified => $node->{LastModified},
etag => $etag,
size => $node->{Size},
storage_class => $node->{StorageClass},
lib/Amazon/S3.pm view on Meta::CPAN
# x-amz-optional-object-attributes: OptionalObjectAtttributes
#
# Parameters:
# delimiter => Delimiter
# encoding-type => EncodingType
# key-marker => KeyMarker
# max-keys => MaxKeys
# prefix => Prefix
# version-id-marker => VersionIdMarker
#
# Response
########################################################################
sub list_object_versions {
########################################################################
my ( $self, $conf ) = @_;
my $bucket = delete $conf->{bucket};
die 'no bucket'
if !$bucket;
my $headers = delete $conf->{headers};
croak 'must specify bucket'
if !$bucket;
$conf ||= {};
my ( $marker, $next_marker, $query_next )
= @{ $LIST_OBJECT_MARKERS{'3'} };
if ( $conf->{'key-marker'} ) {
$conf->{$query_next} = delete $conf->{'key-marker'};
}
if ( %{$conf} ) {
# remove undefined elements
foreach ( keys %{$conf} ) {
next if defined $conf->{$_};
delete $conf->{$_};
}
}
my $path
= create_api_uri( path => "$bucket/", api => 'versions', %{$conf} );
my $r = $self->_send_request(
{ method => 'GET',
path => $path,
headers => $headers // {},
region => $self->region,
},
);
return
if !$r || $self->errstr;
$self->get_logger->debug(
sub {
return Dumper(
[ marker => $marker,
next_marker => $next_marker,
response => $r,
],
);
},
);
return $r;
}
########################################################################
sub get_credentials {
########################################################################
my ($self) = @_;
my $aws_access_key_id;
my $aws_secret_access_key;
my $token;
if ( $self->credentials ) {
$aws_access_key_id = $self->credentials->get_aws_access_key_id;
$aws_secret_access_key = $self->credentials->get_aws_secret_access_key;
$token = $self->credentials->get_token;
}
else {
$aws_access_key_id = $self->aws_access_key_id;
$aws_secret_access_key = $self->aws_secret_access_key;
$token = $self->token;
}
return ( $aws_access_key_id, $aws_secret_access_key, $token );
}
# Log::Log4perl compatibility routines
########################################################################
sub get_logger {
########################################################################
my ($self) = @_;
return $self->logger;
}
########################################################################
sub level {
########################################################################
my ( $self, @args ) = @_;
if (@args) {
$self->log_level( $args[0] );
$self->get_logger->level( uc $args[0] );
}
return $self->get_logger->level;
}
########################################################################
lib/Amazon/S3.pm view on Meta::CPAN
return $FALSE
if length $bucketname > $MAX_BUCKET_NAME_LENGTH - 1;
return $FALSE
if length $bucketname < $MIN_BUCKET_NAME_LENGTH;
return $FALSE
if $bucketname !~ m{\A[[:lower:]][[:lower:]\d-]*\z}xsm;
return $FALSE
if $bucketname !~ m{[[:lower:]\d]\z}xsm;
return $TRUE;
}
########################################################################
sub _make_request {
########################################################################
my ( $self, @args ) = @_;
my $parameters = get_parameters(@args);
my ( $method, $path, $headers, $data, $metadata, $region )
= @{$parameters}{qw(method path headers data metadata region)};
# reset region on every call...every bucket can have it's own region
$self->region( $region // $self->_region );
croak 'must specify method'
if !$method;
croak 'must specify path'
if !defined $path;
$headers //= {};
$metadata //= {};
$data //= $EMPTY;
$headers->{'Content-Length'} //= length $data;
my $http_headers = $self->_merge_meta( $headers, $metadata );
my $protocol = $self->secure ? 'https' : 'http';
my $host = $self->host;
$path =~ s/\A\///xsm;
my $url = sprintf '%s://%s/%s', $protocol, $host, $path;
# if ( $path =~ m{\A([^/?]+)([^?]+)(.*)}xsm
if ( $path =~ /\A([^\/?]+)([^?]+)(.*)/xsm
&& $self->dns_bucket_names
&& is_domain_bucket($1) ) {
my $bucket = $1;
$path = $2;
my $query_string = $3;
$self->logger->debug(
sub {
return Dumper(
[ bucket => $bucket,
path => $path,
query_string => $query_string,
]
);
}
);
if ( $host =~ /([^:]+):([^:]\d+)$/xsm ) {
my $port;
$url = eval {
$port = $2;
$host = $1;
my $uri = URI->new;
$uri->scheme('http');
$uri->host("$bucket.$host");
$uri->port($port);
$uri->path($path);
return $uri . $query_string;
};
die sprintf
"error creating uri for bucket: [%s], host: [%s], path: [%s], port: [%s]\n%s",
$bucket, $host, $path, $port, $EVAL_ERROR
if !$url || $EVAL_ERROR;
}
else {
$url = sprintf '%s://%s.%s%s%s', $protocol, $bucket, $host, $path,
$query_string;
}
}
my $request = HTTP::Request->new( $method, $url, $http_headers );
$self->last_request($request);
if ($data) {
$request->content($data);
}
$self->signer->region($region); # always set regional endpoint for signing
$self->signer->sign($request);
return $request;
}
# $self->_send_request($HTTP::Request)
# $self->_send_request(@params_to_make_request)
# $self->_send_request($params_to_make_request)
########################################################################
sub _send_request {
########################################################################
my ( $self, @args ) = @_;
my $logger = $self->get_logger;
$logger->trace(
sub {
return Dumper( [ args => \@args ] );
},
);
my $keep_root = $FALSE;
my $request = eval {
return $args[0]
if ref( $args[0] ) =~ /HTTP::Request/xsm;
return {@args}
if @args > 1 && !@args % 2;
return $args[0]
if ref $args[0];
croak 'invalid argument to _send_request';
};
if ( ref($request) !~ /HTTP::Request/xsm ) {
$keep_root = delete $request->{keep_root};
$request = $self->_make_request($request);
}
my $response = $self->_do_http($request);
$self->last_response($response);
$logger->debug(
sub {
return Dumper( [ response => $response ] );
}
);
return $self->_decode_response( $response, $keep_root );
}
########################################################################
sub _decode_response {
########################################################################
my ( $self, $response, $keep_root ) = @_;
my $content;
if ( $response->code !~ /\A2\d{2}\z/xsm ) {
$self->_remember_errors( $response->content, 1 );
$content = undef;
}
elsif ( is_xml_response($response) ) {
$content = $self->_xpc_of_content( $response->content, $keep_root );
}
return $content;
}
########################################################################
sub is_xml_response {
########################################################################
my ($rsp) = @_;
return $FALSE
if !$rsp->content;
return $TRUE
if $rsp->content_type eq 'application/xml';
return $TRUE
if $rsp->content =~ /\A\s*<[?]xml/xsm;
return $FALSE;
}
#
# This is the necessary to find the region for a specific bucket
# and set the signer object to use that region when signing requests
########################################################################
sub adjust_region {
########################################################################
my ( $self, $bucket, $called_from_redirect ) = @_;
my $url = sprintf 'https://%s.%s', $bucket, $self->host;
my $request = HTTP::Request->new( GET => $url );
$self->{'signer'}->sign($request);
# We have to turn off our special retry since this will deliberately
# trigger that code
$self->turn_off_special_retry();
lib/Amazon/S3.pm view on Meta::CPAN
# where an exact host has been given
if ( !$called_from_redirect ) {
$self->host( sprintf 's3-%s-amazonaws.com', $region );
}
return $TRUE;
},
IllegalLocationConstraintException => sub {
# This is hackish; but in this case the region name only appears in the message
if ( $message =~ /The (\S+) location/xsm ) {
my $new_region = $1;
# Correct the region for the signer
$self->{signer}->{endpoint} = $new_region;
# Set the proper host for the region
$self->host( sprintf 's3.%s.amazonaws.com', $new_region );
return $TRUE;
}
},
'Other' => sub {
# Some other error
$self->_remember_errors( $response->content, 1 );
return $FALSE;
},
);
return $error_handlers{$condition}->();
}
########################################################################
sub reset_errors {
########################################################################
my ($self) = @_;
$self->err(undef);
$self->errstr(undef);
$self->error(undef);
return $self;
}
########################################################################
sub _do_http {
########################################################################
my ( $self, $request, $filename ) = @_;
# convenient time to reset any error conditions
$self->reset_errors;
my $response = $self->ua->request( $request, $filename );
# For new buckets at non-standard locations, amazon will sometimes
# respond with a temporary redirect. In this case it is necessary
# to try again with the new URL
my $location = $response->header('Location');
if ( $response->code =~ /\A3/xsm and defined $location ) {
$self->get_logger->debug(
sub {
return { sprintf 'Redirecting to: %s', $location };
}
);
$request->uri($location);
$response = $self->ua->request( $request, $filename );
}
$self->get_logger->debug( sub { return Dumper( [$response] ) } );
$self->last_response($response);
return $response;
}
# Call this if handling any temporary redirect issues
# (Like needing to probe with a HEAD request when file handle are involved)
########################################################################
sub _do_http_no_redirect {
########################################################################
my ( $self, $request, $filename ) = @_;
# convenient time to reset any error conditions
$self->reset_errors;
my $response = $self->ua->request( $request, $filename );
$self->get_logger->debug( sub { return Dumper( [$response] ) } );
$self->last_response($response);
return $response;
}
########################################################################
sub _send_request_expect_nothing {
########################################################################
my ( $self, @args ) = @_;
my $request = $self->_make_request(@args);
my $response = $self->_do_http($request);
my $content = $response->content;
return $TRUE
if $response->code =~ /^2\d\d$/xsm;
# anything else is a failure, and we save the parsed result
$self->_remember_errors( $response->content, $TRUE );
return $FALSE;
}
# Send a HEAD request first, to find out if we'll be hit with a 307 redirect.
# Since currently LWP does not have true support for 100 Continue, it simply
# slams the PUT body into the socket without waiting for any possible redirect.
# Thus when we're reading from a filehandle, when LWP goes to reissue the request
# having followed the redirect, the filehandle's already been closed from the
# first time we used it. Thus, we need to probe first to find out what's going on,
# before we start sending any actual data.
########################################################################
sub _send_request_expect_nothing_probed {
########################################################################
my ( $self, @args ) = @_;
my $parameters = get_parameters(@args);
my ( $method, $path, $conf, $value, $region )
= @{$parameters}{qw(method path headers data region)};
$region = $region // $self->region;
my $request = $self->_make_request(
{ method => 'HEAD',
path => $path,
region => $region,
},
);
my $override_uri;
my $old_redirectable = $self->ua->requests_redirectable;
$self->ua->requests_redirectable( [] );
my $response = $self->_do_http_no_redirect($request);
if ( $response->code =~ /^3/xsm ) {
if ( defined $response->header('Location') ) {
$override_uri = $response->header('Location');
}
else {
$self->_croak_if_response_error($response);
}
$self->get_logger->debug(
sub {
return sprintf 'setting override URI: [%s]', $override_uri;
}
);
}
$request = $self->_make_request(
{ method => $method,
path => $path,
headers => $conf,
data => $value,
region => $region,
},
);
if ( defined $override_uri ) {
$request->uri($override_uri);
}
$response = $self->_do_http_no_redirect($request);
$self->ua->requests_redirectable($old_redirectable);
my $content = $response->content;
return $TRUE
if $response->code =~ /^2\d\d$/xsm;
# anything else is a failure, and we save the parsed result
$self->_remember_errors( $response->content, $TRUE );
return $FALSE;
}
########################################################################
sub _croak_if_response_error {
########################################################################
my ( $self, $response ) = @_;
if ( $response->code !~ /^2\d{2}$/xsm ) {
$self->err('network_error');
$self->errstr( $response->status_line );
croak $response->status_line;
}
return;
}
########################################################################
sub _xpc_of_content {
########################################################################
my ( $self, $src, $keep_root ) = @_;
my $xml_hr = eval {
XMLin(
$src,
SuppressEmpty => $EMPTY,
ForceArray => ['Contents'],
lib/Amazon/S3.pm view on Meta::CPAN
=over
=item is_truncated
Boolean flag that indicates whether or not all results of your query were
returned in this response. If your results were truncated, you can
make a follow-up paginated request using the Marker parameter to
retrieve the rest of the results.
=item next_marker
A convenience element, useful when paginating with delimiters. The
value of C<next_marker>, if present, is the largest (alphabetically)
of all key names and all CommonPrefixes prefixes in the response.
If the C<is_truncated> flag is set, request the next page of results
by setting C<marker> to the value of C<next_marker>. This element
is only present in the response if the C<delimiter> parameter was
sent with the request.
=back
Each key is a reference to a hash that looks like this:
{
key => $key,
last_modified => $last_mod_date,
etag => $etag, # An MD5 sum of the stored content.
size => $size, # Bytes
storage_class => $storage_class # Doc?
owner_id => $owner_id,
owner_displayname => $owner_name
}
=head2 get_bucket_location
get_bucket_location(bucket-name)
get_bucket_locaiton(bucket-obj)
This is a convenience routines for the C<get_location_constraint()> of
the bucket object. This method will return the default
region of 'us-east-1' when C<get_location_constraint()> returns a null
value.
my $region = $s3->get_bucket_location('my-bucket');
Starting with version 0.55, C<Amazon::S3::Bucket> will call this
C<get_location_constraint()> to determine the region for the
bucket. You can get the region for the bucket by using the C<region()>
method of the bucket object.
my $bucket = $s3->bucket('my-bucket');
my $bucket_region = $bucket->region;
=head2 get_logger
Returns the logger object. If you did not set a logger when you
created the object then an instance of C<Amazon::S3::Logger> is
returned. You can log to STDERR using this logger. For example:
$s3->get_logger->debug('this is a debug message');
$s3->get_logger->trace(sub { return Dumper([$response]) });
=head2 list_bucket_all, list_bucket_all_v2
List all keys in this bucket without having to worry about
'marker'. This is a convenience method, but may make multiple requests
to S3 under the hood.
Takes the same arguments as C<list_bucket>.
I<You are encouraged to use the newer C<list_bucket_all_v2> method.>
=head2 list_object_versions
list_object_versions( args )
Returns metadata about all versions of the objects in a bucket. You
can also use request parameters as selection criteria to return
metadata about a subset of all the object versions.
This method will only return the raw result set and does not perform
pagination or unravel common prefixes as do other methods like
C<list_bucket>. This may change in the future.
See L<https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html>
for more information about the request parameters and the result body.
C<args> is hash reference containing the following parameters:
=over 5
=item bucket
Name of the bucket. This method is not vailable for directory buckets.
=item headers
Optional headers. See
L<https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html>
for more details regarding optional headers.
=item delimiter
A delimiter is a character that you specify to group keys. All keys
that contain the same string between the prefix and the first
occurrence of the delimiter are grouped under a single result element
in CommonPrefixes. These groups are counted as one result against the
max-keys limitation. These keys are not returned elsewhere in the
response.
=item encoding-type
Requests Amazon S3 to encode the object keys in the response and
specifies the encoding method to use.
=item key-marker
Specifies the key to start with when listing objects in a bucket.
lib/Amazon/S3.pm view on Meta::CPAN
=item AMAZON_S3_SKIP_ACL_TESTS
Doesn't matter what you set it to. Just has to be set if you want
to skip ACLs tests.
=item AMAZON_S3_SKIP_PERMISSIONS
Skip tests that check for enforcement of ACLs...as of this version,
LocalStack for example does not support enforcement of ACLs.
=item AMAZON_S3_SKIP_REGION_CONSTRAINT_TEST
Doesn't matter what you set it to. Just has to be set if you want
to skip region constraint test.
=item AMAZON_S3_MINIO
Doesn't matter what you set it to. Just has to be set if you want
to skip tests that would fail on minio.
=item AMAZON_S3_LOCALSTACK
Doesn't matter what you set it to. Just has to be set if you want
to skip tests that would fail on LocalStack.
=item AMAZON_S3_REGIONS
A comma delimited list of regions to use for testing. The default will
only test creating a bucket in the local region.
=back
I<Consider using an S3 mocking service like C<minio> or C<LocalStack>
if you want to create real tests for your applications or this module.>
Here's bash script for testing using LocalStack
#!/bin/bash
# -*- mode: sh; -*-
BUCKET=net-amazon-s3-test-test
ENDPOINT_URL=s3.localhost.localstack.cloud:4566
AMAZON_S3_EXPENSIVE_TESTS=1 \
AMAZON_S3_HOST=$ENDPOINT_URL \
AMAZON_S3_LOCALSTACK=1 \
AWS_ACCESS_KEY_ID=test \
AWS_ACCESS_SECRET_KEY=test \
AMAZON_S3_DOMAIN_BUCKET_NAMES=1 make test 2>&1 | tee test.log
To run the tests...clone the project and build the software.
cd src/main/perl
./test.localstack
=head1 ADDITIONAL INFORMATION
=head2 LOGGING AND DEBUGGING
Additional debugging information can be output to STDERR by setting
the C<level> option when you instantiate the C<Amazon::S3>
object. Levels are represented as a string. The valid levels are:
fatal
error
warn
info
debug
trace
You can set an optionally pass in a logger that implements a subset of
the C<Log::Log4perl> interface. Your logger should support at least
these method calls. If you do not supply a logger the default logger
(C<Amazon::S3::Logger>) will be used.
get_logger()
fatal()
error()
warn()
info()
debug()
trace()
level()
At the C<trace> level, every HTTP request and response will be output
to STDERR. At the C<debug> level information regarding the higher
level methods will be output to STDERR. There currently is no
additional information logged at lower levels.
=head2 S3 LINKS OF INTEREST
=over 5
=item L<Bucket restrictions and limitations|https://docs.aws.amazon.com/AmazonS3/latest/userguide/BucketRestrictions.html>
=item L<Bucket naming rules|https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html>
=item L<Amazon S3 REST API|https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html>
=item L<Authenticating Requests (AWS Signature Version 4)|https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html>
=item L<Authenticating Requests (AWS Signature Version 2)|https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html>
=item L<LocalStack|https://localstack.io>
=back
=head1 SUPPORT
Bugs should be reported via the CPAN bug tracker at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Amazon-S3>
For other issues, contact the author.
=head1 REPOSITORY
L<https://github.com/rlauer6/perl-amazon-s3|https://github.com/rlauer6/perl-amazon-s3>
=head1 AUTHOR
Original author: Timothy Appnel <tima@cpan.org>
Current maintainer: Rob Lauer <bigfoot@cpan.org>
=head1 SEE ALSO
L<Amazon::S3::Bucket>, L<Net::Amazon::S3>
=head1 COPYRIGHT AND LICENCE
This module was initially based on L<Net::Amazon::S3> 0.41, by
Leon Brocard. Net::Amazon::S3 was based on example code from
Amazon with this notice:
I<This software code is made available "AS IS" without warranties of any
kind. You may copy, display, modify and redistribute the software
code either by itself or as incorporated into your code; provided that
you do not remove any proprietary notices. Your use of this software
code is at your own risk and you waive any claim against Amazon
Digital Services, Inc. or its affiliates with respect to your use of
this software code. (c) 2006 Amazon Digital Services, Inc. or its
affiliates.>
The software is released under the Artistic License. The
terms of the Artistic License are described at
( run in 0.874 second using v1.01-cache-2.11-cpan-39bf76dae61 )