Amazon-S3-Lite

 view release on metacpan or  search on metacpan

lib/Amazon/S3/Lite.pm  view on Meta::CPAN


  $self->logger->debug( sprintf 'Response: %s %s', $response->{status}, $response->{reason} );

  return $response;
}

########################################################################
# head_object( $bucket, $key )
#
# Fetches metadata for an object without retrieving the body.
# Returns undef if the key does not exist (404).
# Returns a hashref with content_type, content_length, etag,
# last_modified, and metadata (x-amz-meta-* headers).
########################################################################
sub head_object {
########################################################################
  my ( $self, $bucket, $key ) = @_;

  croak 'bucket is required' if !defined $bucket || !length $bucket;
  croak 'key is required'    if !defined $key    || !length $key;

lib/Amazon/S3/Lite.pm  view on Meta::CPAN

  };
}

########################################################################
# get_object( $bucket, $key, %options )
#
# Fetches an object from S3. Options:
#   range    => 'bytes=0-1023'   partial fetch
#   filename => '/tmp/foo'       stream body to disk; omits content key
#
# Returns undef on 404.
# Returns a hashref with content_type, content_length, etag,
# last_modified, metadata, and content (unless filename is used).
########################################################################
sub get_object {
########################################################################
  my ( $self, $bucket, $key, %options ) = @_;

  croak 'bucket is required' if !defined $bucket || !length $bucket;
  croak 'key is required'    if !defined $key    || !length $key;

lib/Amazon/S3/Lite.pm  view on Meta::CPAN

  my @all_objects;
  my $continuation_token;

  while ($TRUE) {
    if ( defined $continuation_token ) {
      $options{continuation_token} = $continuation_token;
    }

    my $result = $self->list_objects_v2( $bucket, %options );

    last if !$result;  # 404 / empty bucket

    push @all_objects, @{ $result->{objects} };

    last if !$result->{is_truncated};

    $continuation_token = $result->{next_continuation_token};
  }

  return @all_objects;
}

lib/Amazon/S3/Lite.pm  view on Meta::CPAN

# Error checking helpers
########################################################################
sub _is_success {
########################################################################
  return $_[0]->{status} =~ /\A2\d{2}\z/;
}

########################################################################
sub _is_not_found {
########################################################################
  return $_[0]->{status} == 404;
}

########################################################################
sub _croak_on_error {
########################################################################
  my ( $self, $response, $context ) = @_;

  return if _is_success($response);

  my ( $status, $reason ) = @{$response}{qw(status reason)};

lib/Amazon/S3/Lite.pm  view on Meta::CPAN


=item 4.

If none of the above yield credentials, the constructor croaks.

=back

=head1 METHODS

All methods croak on unrecoverable errors (network failure, HTTP 5xx).
HTTP 404 is not an exception - methods that can meaningfully return
C<undef> for a missing resource do so.

=head2 list_objects_v2

  my $result = $s3->list_objects_v2($bucket, %options);

Lists objects in C<$bucket> using the S3 ListObjectsV2 API.

Options:

lib/Amazon/S3/Lite.pm  view on Meta::CPAN

when no C<logger> object is supplied and Log::Log4perl is not available
or not yet initialized.

=head2 get_object

  my $obj = $s3->get_object($bucket, $key);
  my $obj = $s3->get_object($bucket, $key, %options);

Fetches the object at C<$key> in C<$bucket>.

Returns C<undef> if the key does not exist (HTTP 404).

Returns a hashref on success:

  {
    content        => '...',          # raw bytes; absent when filename is used
    content_type   => 'application/json',
    content_length => 1024,
    etag           => 'abc123',
    last_modified  => 'Tue, 01 Jan 2024 00:00:00 GMT',
    metadata       => {               # x-amz-meta-* headers, lowercased

lib/Amazon/S3/Lite.pm  view on Meta::CPAN

=back

=head2 head_object

  my $meta = $s3->head_object($bucket, $key);

Fetches metadata for C<$key> without retrieving the object body.
Useful for existence checks and reading C<x-amz-meta-*> headers
cheaply.

Returns C<undef> if the key does not exist (HTTP 404).

Returns a hashref on success with the same fields as C<get_object>
except C<content>, which is always absent.

=head2 put_object

  $s3->put_object($bucket, $key, $data, %options);

Stores C<$data> at C<$key> in C<$bucket>. C<$data> may be:

lib/Amazon/S3/Lite.pm  view on Meta::CPAN

=item * HTTP 5xx responses from S3

=item * Unexpected HTTP 3xx responses that could not be resolved

=back

Methods return C<undef> on:

=over 4

=item * HTTP 404 (key or bucket not found), where the return type allows it

=back

All other HTTP error codes (400, 403, 409, etc.) cause a croak with a
message containing the HTTP status line and the S3 error body where
available.

=head1 DEPENDENCIES

=over 4

share/README.md  view on Meta::CPAN

2. Environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`,
and optionally `AWS_SESSION_TOKEN`.
3. [Amazon::Credentials](https://metacpan.org/pod/Amazon%3A%3ACredentials), if installed. This covers IAM instance roles,
Lambda execution roles, ECS task roles, and `~/.aws/credentials`
profiles.
4. If none of the above yield credentials, the constructor croaks.

# METHODS

All methods croak on unrecoverable errors (network failure, HTTP 5xx).
HTTP 404 is not an exception - methods that can meaningfully return
`undef` for a missing resource do so.

## list\_objects\_v2

    my $result = $s3->list_objects_v2($bucket, %options);

Lists objects in `$bucket` using the S3 ListObjectsV2 API.

Options:

share/README.md  view on Meta::CPAN

    when no `logger` object is supplied and Log::Log4perl is not available
    or not yet initialized.

## get\_object

    my $obj = $s3->get_object($bucket, $key);
    my $obj = $s3->get_object($bucket, $key, %options);

Fetches the object at `$key` in `$bucket`.

Returns `undef` if the key does not exist (HTTP 404).

Returns a hashref on success:

    {
      content        => '...',          # raw bytes; absent when filename is used
      content_type   => 'application/json',
      content_length => 1024,
      etag           => 'abc123',
      last_modified  => 'Tue, 01 Jan 2024 00:00:00 GMT',
      metadata       => {               # x-amz-meta-* headers, lowercased

share/README.md  view on Meta::CPAN

    holding the full body in memory is undesirable.

## head\_object

    my $meta = $s3->head_object($bucket, $key);

Fetches metadata for `$key` without retrieving the object body.
Useful for existence checks and reading `x-amz-meta-*` headers
cheaply.

Returns `undef` if the key does not exist (HTTP 404).

Returns a hashref on success with the same fields as `get_object`
except `content`, which is always absent.

## put\_object

    $s3->put_object($bucket, $key, $data, %options);

Stores `$data` at `$key` in `$bucket`. `$data` may be:

share/README.md  view on Meta::CPAN

# ERROR HANDLING

Methods croak on:

- Network-level failures (connection refused, timeout, DNS failure)
- HTTP 5xx responses from S3
- Unexpected HTTP 3xx responses that could not be resolved

Methods return `undef` on:

- HTTP 404 (key or bucket not found), where the return type allows it

All other HTTP error codes (400, 403, 409, etc.) cause a croak with a
message containing the HTTP status line and the S3 error body where
available.

# DEPENDENCIES

- [HTTP::Tiny](https://metacpan.org/pod/HTTP%3A%3ATiny) (core since Perl 5.14)
- [Amazon::Signature4::Lite](https://metacpan.org/pod/Amazon%3A%3ASignature4%3A%3ALite)
- [XML::Twig](https://metacpan.org/pod/XML%3A%3ATwig) (for parsing list and copy responses)

t/01-s3-lite.t  view on Meta::CPAN


  my $obj = $r->{objects}[0];
  is $obj->{key},  'logs/2024-01-01.gz', 'key';
  is $obj->{size}, 1024,                 'size is integer';
  is $obj->{etag}, 'abc123',             'etag stripped of quotes';

  # missing bucket croaks
  eval { $s3->list_objects_v2() };
  like $@, qr/bucket is required/, 'croaks without bucket';

  # 404 returns undef
  local *Amazon::S3::Lite::_request = mock_request( status => 404 );
  my $not_found = $s3->list_objects_v2('no-such-bucket');
  ok !defined $not_found, '404 returns undef';
};

subtest 'list_all_objects_v2 pagination' => sub {
  my $s3   = new_s3();
  my $page = 0;

  no warnings 'redefine';
  local *Amazon::S3::Lite::list_objects_v2 = sub {
    my ( $self, $bucket, %opts ) = @_;
    $page++;

t/01-s3-lite.t  view on Meta::CPAN

    return { objects => [], is_truncated => 0 };
  };
  $s3->list_all_objects_v2( 'test-bucket', delimiter => '/' );
};

subtest 'head_object' => sub {
  my $s3 = new_s3();

  no warnings 'redefine';

  # 404 returns undef
  local *Amazon::S3::Lite::_request = mock_request( status => 404 );
  ok !defined $s3->head_object( 'b', 'k' ), '404 returns undef';

  # success
  local *Amazon::S3::Lite::_request = mock_request(
    headers => {
      'content-type'      => 'text/plain',
      'content-length'    => '42',
      'etag'              => '"abc123"',
      'last-modified'     => 'Wed, 01 Jan 2025 00:00:00 GMT',
      'x-amz-meta-source' => 'lambda',
    },

t/01-s3-lite.t  view on Meta::CPAN

  like $@, qr/bucket is required/, 'croaks without bucket';
  eval { $s3->head_object('b') };
  like $@, qr/key is required/, 'croaks without key';
};

subtest 'get_object' => sub {
  my $s3 = new_s3();

  no warnings 'redefine';

  # 404 returns undef
  local *Amazon::S3::Lite::_request = mock_request( status => 404 );
  ok !defined $s3->get_object( 'b', 'k' ), '404 returns undef';

  # in-memory success
  local *Amazon::S3::Lite::_request = mock_request(
    content => 'hello world',
    headers => {
      'content-type'   => 'text/plain',
      'content-length' => '11',
      'etag'           => '"abc123"',
      'last-modified'  => 'Wed, 01 Jan 2025 00:00:00 GMT',
    },

t/01-s3-lite.t  view on Meta::CPAN


    # get to file
    my ( $fh, $fname ) = tempfile( UNLINK => 1 );
    close $fh;
    my $file_meta = $s3->get_object( 'test-bucket', $key, filename => $fname );
    ok defined $file_meta,            'get_object with filename returns meta';
    ok !exists $file_meta->{content}, 'no content key';
    open my $in, '<', $fname or die $!;
    is do { local $/; <$in> }, $content, 'file content correct';

    # 404
    my $missing = $s3->get_object( 'test-bucket', 'no/such/key.txt' );
    ok !defined $missing, '404 returns undef';

    # delete
    ok $s3->delete_object( 'test-bucket', $key ), 'delete_object returns true';

    # confirm gone
    my $gone = $s3->head_object( 'test-bucket', $key );
    ok !defined $gone, 'object gone after delete';
  };

  subtest 'LocalStack - list_objects_v2' => sub {



( run in 2.284 seconds using v1.01-cache-2.11-cpan-788537b7465 )