NAME

Amazon::S3::Lite - A lightweight Amazon S3 client for common operations

SYNOPSIS

use Amazon::S3::Lite;

# Credentials from environment or IAM role automatically
my $s3 = Amazon::S3::Lite->new({ region => 'us-east-1' });

# Explicit credentials
my $s3 = Amazon::S3::Lite->new({
  region                => 'us-east-1',
  aws_access_key_id     => $key,
  aws_secret_access_key => $secret,
  token                 => $session_token,  # optional, for STS/Lambda roles
});

# Pass any credentials object with standard getters
my $s3 = Amazon::S3::Lite->new({
  region      => 'us-east-1',
  credentials => $creds_obj,
});

# List objects in a bucket
my $result = $s3->list_objects_v2('my-bucket', prefix => 'logs/');

foreach my $obj ( @{ $result->{objects} } ) {
  printf "%s  %d bytes\n", $obj->{key}, $obj->{size};
}

# Paginate
while ( $result->{is_truncated} ) {
  $result = $s3->list_objects_v2('my-bucket',
    prefix             => 'logs/',
    continuation_token => $result->{next_continuation_token},
  );
  # ... process $result->{objects}
}

# Get an object
my $obj = $s3->get_object('my-bucket', 'path/to/key.json');
print $obj->{content};

# Head an object (existence check / metadata only)
my $meta = $s3->head_object('my-bucket', 'path/to/key.json');
if ($meta) {
  print $meta->{content_length};
}

# Put an object
$s3->put_object('my-bucket', 'path/to/key.json', $json_string,
  content_type => 'application/json',
  metadata     => { source => 'lambda' },
);

# Copy an object
$s3->copy_object(
  src_bucket => 'my-bucket', src_key => 'orig/file.json',
  dst_bucket => 'my-bucket', dst_key => 'archive/file.json',
);

# Delete an object
$s3->delete_object('my-bucket', 'path/to/key.json');

# List all buckets
my $result = $s3->list_buckets;
for my $bucket ( @{ $result->{buckets} } ) {
  print $bucket->{name}, "\n";
}

DESCRIPTION

Amazon::S3::Lite is a minimal Amazon S3 client covering the operations most commonly needed in AWS Lambda functions and lightweight scripts: listing buckets, listing objects, reading, writing, copying, and deleting.

It is built on HTTP::Tiny (core since Perl 5.14) and Amazon::Signature4::Lite, with no dependency on LWP or any part of the libwww-perl ecosystem. The dependency list is intentionally small, making it well-suited for Lambda container images where minimizing cold-start time and image size matters.

It is not a replacement for Amazon::S3 or Net::Amazon::S3, which support the full S3 API surface including multipart upload, bucket management, ACLs, versioning, and presigned URLs. If you need those features, use one of those distributions instead.

Amazon::S3::Thin is another excellent lightweight S3 client with a similar philosophy and a longer track record. It is more complete than this module - supporting presigned URLs, bulk delete, and virtual-hosted-style requests - and returns raw HTTP::Response objects so callers handle status codes and errors themselves. Amazon::S3::Lite differs in three ways: it has no dependency on LWP (Amazon::S3::Thin defaults to LWP::UserAgent), it returns parsed hashrefs rather than raw response objects, and it has first-class support for Lambda IAM role credential rotation. If you need the broader feature set or prefer direct HTTP access, Amazon::S3::Thin is a fine choice.

CONSTRUCTOR

new

my $s3 = Amazon::S3::Lite->new(\%options);

Returns a new Amazon::S3::Lite object. Options:

region (required)

The AWS region for your bucket, e.g. us-east-1.

aws_access_key_id / aws_secret_access_key

Static credentials. token may also be supplied for STS temporary credentials (as used by Lambda execution roles).

These are only consulted if no credentials object is provided.

token

Optional STS session token, used alongside static credentials for temporary credential sets.

credentials

An object providing credential getters. The object must respond to:

$creds->aws_access_key_id
$creds->aws_secret_access_key
$creds->token            # may return undef

Any object that satisfies this interface is accepted - Amazon::Credentials, Paws::Credential::*, or your own. The getters are called at request time, so objects that refresh expiring credentials transparently are supported.

logger

An object providing the standard log methods:

$logger->trace(...)
$logger->debug(...)
$logger->info(...)
$logger->warn(...)
$logger->error(...)

If not supplied, the module looks for Log::Log4perl. If available, it calls Log::Log4perl::easy_init with level WARN and logs to STDERR. If Log::Log4perl is not installed, a minimal internal logger is used that prints WARN and above to STDERR.

host

Override the S3 endpoint host. Defaults to s3.amazonaws.com. Useful for S3-compatible services (MinIO, Ceph, LocalStack).

secure

Use HTTPS. Default is 1 (true). Set to 0 only for testing against local S3-compatible endpoints.

timeout

HTTP request timeout in seconds. Default is 30.

Credential resolution order

When no credentials object is passed, credentials are resolved in this order:

  1. Constructor arguments aws_access_key_id and aws_secret_access_key.

  2. Environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN.

  3. Amazon::Credentials, 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:

prefix

Limit results to keys beginning with this string.

delimiter

Group keys sharing a common prefix up to this delimiter. Grouped prefixes are returned in common_prefixes.

max_keys

Maximum number of objects to return per call (1-1000, default 1000).

continuation_token

Resume a truncated listing from a prior call's next_continuation_token.

start_after

Return only keys lexicographically after this value.

Returns a hashref:

{
  bucket                 => 'my-bucket',
  prefix                 => 'logs/',
  is_truncated           => 0,
  next_continuation_token => undef,        # set when is_truncated is true
  key_count              => 42,
  objects                => [
    {
      key           => 'logs/2024-01-01.gz',
      size          => 102400,
      last_modified => '2024-01-01T00:00:00.000Z',
      etag          => 'abc123',
      storage_class => 'STANDARD',
    },
    ...
  ],
  common_prefixes        => [],            # populated when delimiter is set
}

list_all_objects_v2

my @objects = $s3->list_all_objects_v2($bucket, %options);

Convenience wrapper around "list_objects_v2" that automatically follows continuation tokens and returns a flat list of all matching object hashrefs in a single call.

Accepts the same options as list_objects_v2 except continuation_token (which is managed internally) and delimiter (which is silently ignored - see below).

my @logs = $s3->list_all_objects_v2('my-bucket', prefix => 'logs/');

foreach my $obj (@logs) {
  printf "%s  %d bytes\n", $obj->{key}, $obj->{size};
}

Be mindful of memory when listing buckets with large numbers of objects. For very large listings, use "list_objects_v2" directly and process each page as it arrives.

delimiter and common_prefixes are not supported by this method. The purpose of list_all_objects_v2 is a complete flat listing of all matching keys. Hierarchical directory-style traversal using delimiter is inherently page-by-page and should use "list_objects_v2" directly.

Returns a (possibly empty) list of object hashrefs, each with the same fields as the elements of objects in the list_objects_v2 response.

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
    source => 'lambda',
  },
}

Options:

range

An HTTP Range header value, e.g. bytes=0-1023, for partial fetches.

filename

Path to a local file where the object body should be written. When supplied, the response body is streamed directly to disk via HTTP::Tiny's :content_file mechanism and content is omitted from the returned hashref. The file is created or overwritten.

my $meta = $s3->get_object('my-bucket', 'data/dump.csv',
  filename => '/tmp/dump.csv',
);
# $meta->{content} is absent; file is on disk

This is the recommended approach for large objects in Lambda where 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:

  • A scalar string (the object body verbatim)

  • A reference to a scalar (avoids copying large strings)

  • An open filehandle or IO::File object (body is read to EOF)

When passing a filehandle, content_length becomes required unless HTTP::Tiny can determine the size from the handle (i.e. the handle is backed by a real file). For in-memory handles (IO::Scalar, etc.) you must supply content_length explicitly, or the method will croak.

# Scalar
$s3->put_object('my-bucket', 'hello.txt', 'Hello, world!',
  content_type => 'text/plain',
);

# Filehandle
open my $fh, '<', '/tmp/data.csv' or die $!;
$s3->put_object('my-bucket', 'data.csv', $fh,
  content_type => 'text/csv',
);

Options:

content_type

MIME type for the object. Defaults to application/octet-stream.

content_length

Required when $data is an in-memory filehandle. Optional (and ignored) for scalar data, where length is computed automatically.

metadata

Hashref of user-defined metadata. Keys should be bare names - the x-amz-meta- prefix is added automatically.

metadata => { source => 'lambda', job_id => '42' }
acl

Canned ACL string, e.g. private (default), public-read.

Returns the ETag of the stored object on success. Croaks on failure.

copy_object

$s3->copy_object(
  src_bucket => 'src-bucket',
  src_key    => 'original/key.json',
  dst_bucket => 'dst-bucket',
  dst_key    => 'copy/key.json',
);

Copies an object within or between buckets without transferring data through the client. The copy is performed entirely server-side by S3.

Returns a hashref on success:

{
  etag          => 'abc123',
  last_modified => '2024-01-01T00:00:00.000Z',
}

Croaks on failure.

delete_object

$s3->delete_object($bucket, $key);
$s3->delete_object($bucket, $key, version_id => $vid);

Deletes the object at $key in $bucket.

If version_id is provided, that specific version is deleted.

Returns true on success. Note that S3 returns HTTP 204 for both successful deletes and deletes of non-existent keys, so this method does not distinguish between the two - it succeeds silently in either case.

list_buckets

my $result = $s3->list_buckets;

Lists all S3 buckets owned by the authenticated account.

Returns a hashref:

{
  owner_id   => 'abc123...',
  owner_name => 'myaccount',
  buckets    => [
    { name => 'my-bucket',    creation_date => '2024-01-01T00:00:00.000Z' },
    { name => 'other-bucket', creation_date => '2024-06-01T00:00:00.000Z' },
    ...
  ],
}

Note that this operation is always signed against us-east-1 regardless of the region the object was constructed with. See "LAMBDA USAGE NOTES".

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

Optional:

  • Amazon::Credentials - automatic credential discovery from IAM roles, ECS task roles, ~/.aws/credentials, and environment.

  • Log::Log4perl - structured logging; if present, used in preference to the built-in minimal logger.

LAMBDA USAGE NOTES

In a Lambda container, credentials come from the execution role via the ECS credential provider endpoint (indicated by AWS_CONTAINER_CREDENTIALS_RELATIVE_URI in the environment). Amazon::Credentials handles this automatically when installed and is the recommended approach. If you prefer not to take that dependency, the Lambda runtime also populates AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN directly, which this module picks up automatically from the environment.

Region note: The list_buckets method is a global S3 operation and is always signed against us-east-1, regardless of the region supplied to the constructor. This is an S3 requirement, not a limitation of this module, and is handled transparently - your object's region is not changed.

Cold start: Because this module depends only on HTTP::Tiny (Perl core), XML::Twig, AWS::Signature4, and URI::Escape, it adds minimal overhead to Lambda container image builds compared to LWP-based S3 clients.

TESTING

When testing against LocalStack, be aware that LocalStack is more lenient than real S3 regarding SigV4 requirements. In particular, LocalStack may accept requests where the x-amz-content-sha256 header is missing or where session token handling is incorrect. Tests that pass against LocalStack should always be verified against real S3 before release.

SEE ALSO

Amazon::S3 - the full-featured S3 client this module draws from

Amazon::S3::Thin - another excellent lightweight S3 client with a similar philosophy, broader feature coverage, and a longer track record. Uses LWP by default and returns raw HTTP::Response objects. See "DESCRIPTION" for a detailed comparison.

Net::Amazon::S3 - a Moose-based full-featured alternative

Amazon::Signature4::Lite - the signing module used internally

Amazon::Credentials - credential provider with IAM role and profile support

AUTHOR

Rob Lauer <rlauer@treasurersbriefcase.com>

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.