# Table of Contents * [NAME](#name) * [SYNOPSIS](#synopsis) * [DESCRIPTION](#description) * [CONSTRUCTOR](#constructor) * [new](#new) * [Credential resolution order](#credential-resolution-order) * [METHODS](#methods) * [list\_objects\_v2](#list\objects\v2) * [list\_all\_objects\_v2](#list\all\objects\v2) * [get\_object](#get\object) * [head\_object](#head\object) * [put\_object](#put\object) * [copy\_object](#copy\object) * [delete\_object](#delete\object) * [list\_buckets](#list\buckets) * [create\_bucket](#create\bucket) * [delete\_bucket](#delete\bucket) * [put\_public\_access\_block](#put\public\access\block) * [put\_bucket\_website](#put\bucket\website) * [get\_bucket\_website](#get\bucket\website) * [delete\_bucket\_website](#delete\bucket\website) * [put\_bucket\_policy](#put\bucket\policy) * [get\_bucket\_policy](#get\bucket\policy) * [put\_bucket\_notification\_configuration](#put\bucket\notification\configuration) * [get\_bucket\_notification\_configuration](#get\bucket\notification\configuration) * [remove\_bucket\_notification\_configuration](#remove\bucket\notification\configuration) * [ERROR HANDLING](#error-handling) * [DEPENDENCIES](#dependencies) * [LAMBDA USAGE NOTES](#lambda-usage-notes) * [TESTING](#testing) * [SEE ALSO](#see-also) * [AUTHOR](#author) * [LICENSE](#license) # 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"; } # Create a bucket $s3->create_bucket('my-bucket'); $s3->create_bucket('my-bucket', region => 'eu-west-1'); # Configure a Lambda notification trigger $s3->put_bucket_notification_configuration('my-bucket', type => 'lambda', lambda_arn => $function_arn, events => 's3:ObjectCreated:*', filters => { prefix => 'uploads/' }, ); # Configure an SQS notification trigger $s3->put_bucket_notification_configuration('my-bucket', type => 'sqs', queue_arn => $queue_arn, events => 's3:ObjectCreated:*', ); # Retrieve notification configuration my $configs = $s3->get_bucket_notification_configuration('my-bucket'); for my $cfg ( @{$configs} ) { printf "id=%s lambda=%s queue=%s\n", $cfg->{id}, $cfg->{lambda_arn} // '', $cfg->{queue_arn} // ''; } # 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](https://metacpan.org/pod/HTTP%3A%3ATiny) (core since Perl 5.14) and [Amazon::Signature4::Lite](https://metacpan.org/pod/Amazon%3A%3ASignature4%3A%3ALite), 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](https://metacpan.org/pod/Amazon%3A%3AS3) or [Net::Amazon::S3](https://metacpan.org/pod/Net%3A%3AAmazon%3A%3AS3), 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](https://metacpan.org/pod/Amazon%3A%3AS3%3A%3AThin) 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](https://metacpan.org/pod/HTTP%3A%3AResponse) 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](https://metacpan.org/pod/LWP%3A%3AUserAgent)), 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 (options, default: us-east-1) 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](https://metacpan.org/pod/Amazon%3A%3ACredentials), [Paws::Credential::\*](https://metacpan.org/pod/Paws%3A%3ACredential%3A%3A%2A), 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](https://metacpan.org/pod/Log%3A%3ALog4perl). If available, it calls `Log::Log4perl::easy_init` with the configure log level (or WARN) and logs to STDERR. If Log::Log4perl is not installed, a minimal internal logger. - 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](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: - 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"](#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"](#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"](#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. - log\_level Log level for the internal logger. Accepted values: `trace`, `debug`, `info`, `warn`, `error`, `fatal`. Default is `warn`. Only consulted 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 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) - A seekable open filehandle or [IO::File](https://metacpan.org/pod/IO%3A%3AFile) object When passing a filehandle, the handle must be seekable. The method computes the SHA-256 hash required for AWS Signature Version 4 from the current file position through EOF, then restores the original position before streaming the content to S3. _Note: The effective uploaded payload is from the current position to EOF._ `content_length` is required unless the size can be determined from the handle (for example, when the handle is backed by a real file). For in-memory handles such as `IO::Scalar`, supply `content_length` explicitly. The filehandle is not closed by `put_object`. # 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 for filehandles whose size cannot be determined automatically. For handles backed by regular files, the size is determined from the file. Ignored for scalar data, where the 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"](#lambda-usage-notes). ## create\_bucket $s3->create_bucket($bucket); $s3->create_bucket($bucket, region => 'eu-west-1', acl => 'private'); Creates a new S3 bucket. Options: - region The region in which to create the bucket. Defaults to the region the object was constructed with. **Note:** `us-east-1` is S3's implicit default - the `CreateBucketConfiguration` body is intentionally omitted for that region as including it causes a `InvalidLocationConstraint` error. For all other regions the `LocationConstraint` element is sent automatically. - acl Canned ACL string, e.g. `private` (the S3 default), `public-read`. Returns true on success. Croaks on failure. ## delete\_bucket $s3->delete_bucket($bucket); $s3->delete_bucket($bucket, region => 'us-west-2'); Deletes an empty bucket. Returns a true value on success. S3 refuses to delete a bucket that still contains objects, so callers must empty the bucket first (for example by iterating ["list\_all\_objects\_v2"](#list_all_objects_v2) and calling ["delete\_object"](#delete_object) on each key). - region Override the region the delete is signed against. Defaults to the region the object was constructed with. ## put\_public\_access\_block $s3->put_public_access_block($bucket); $s3->put_public_access_block( $bucket, block_public_acls => 1, ignore_public_acls => 1, block_public_policy => 0, restrict_public_buckets => 0, ); Sets the Block Public Access configuration on a bucket. Each of the four settings is a boolean; any setting not supplied **defaults to true** (fully locked down), so an argument-less call blocks all public access. Returns a true value on success. - block\_public\_acls - ignore\_public\_acls - block\_public\_policy - restrict\_public\_buckets Each accepts a true/false value. Omitted settings default to true. Note that hosting content publicly (for example a static website, or a policy-scoped private bucket that grants anonymous `GetObject` from a VPC endpoint) requires the relevant setting to be false so the bucket policy is not rejected. ## put\_bucket\_website $s3->put_bucket_website($bucket); $s3->put_bucket_website($bucket, index => 'index.html', error => 'error.html'); Configures the bucket as an S3 static website endpoint. Returns a true value on success. - index The index document suffix. Defaults to `index.html`. - error The error document key. Optional; when omitted, no error document is configured. ## get\_bucket\_website my $config = $s3->get_bucket_website($bucket); Returns the bucket's website configuration as a hashref, or `undef` if the bucket has no website configuration. The hashref contains whichever of the following are set: - index\_document The index document suffix. - error\_document The error document key. - redirect\_all\_requests\_to The hostname all requests are redirected to, if the bucket is configured as a redirect. ## delete\_bucket\_website $s3->delete_bucket_website($bucket); Removes the website configuration from a bucket. Returns a true value on success. ## put\_bucket\_policy $s3->put_bucket_policy($bucket, \%policy); $s3->put_bucket_policy($bucket, $json_string); Attaches a bucket policy. The policy may be given either as a Perl data structure (a hashref, which is encoded to canonical JSON) or as a pre-encoded JSON string. Returns a true value on success. ## get\_bucket\_policy my $policy = $s3->get_bucket_policy($bucket); my $json = $s3->get_bucket_policy($bucket, raw => 1); Returns the bucket's policy, or `undef` if the bucket has no policy (S3 returns `NoSuchBucketPolicy`). By default the policy is decoded and returned as a hashref; pass `raw => 1` to get the raw JSON string instead. - raw When true, return the policy as its raw JSON string rather than a decoded hashref. ## put\_bucket\_notification\_configuration # Lambda trigger $s3->put_bucket_notification_configuration($bucket, type => 'lambda', lambda_arn => $function_arn, events => 's3:ObjectCreated:*', ); # SQS trigger $s3->put_bucket_notification_configuration($bucket, type => 'sqs', queue_arn => $queue_arn, events => [qw(s3:ObjectCreated:* s3:ObjectRemoved:*)], filters => { prefix => 'uploads/', suffix => '.csv' }, ); Sets the bucket notification configuration for `$bucket`, routing S3 events to a Lambda function or SQS queue. Options: - type (required) The notification target type. Must be `lambda` or `sqs`. - lambda\_arn (required when type is `lambda`) The ARN of the Lambda function to invoke. - queue\_arn (required when type is `sqs`) The ARN of the SQS queue to deliver messages to. - events (required) A scalar event name or an arrayref of event names. Common values: `s3:ObjectCreated:*`, `s3:ObjectRemoved:*`. - filters A hashref of S3 key filter rules. Supported keys are `prefix` and `suffix`. - id An identifier for the configuration entry. Defaults to `notification-1`. Returns true on success. Croaks on failure. ## get\_bucket\_notification\_configuration my $configs = $s3->get_bucket_notification_configuration($bucket); for my $cfg ( @{$configs} ) { if ( $cfg->{lambda_arn} ) { printf "Lambda: id=%s arn=%s\n", $cfg->{id}, $cfg->{lambda_arn}; } elsif ( $cfg->{queue_arn} ) { printf "SQS: id=%s arn=%s\n", $cfg->{id}, $cfg->{queue_arn}; } print " events: ", join(', ', @{ $cfg->{events} }), "\n"; } Retrieves the current notification configuration for `$bucket`. Handles both Lambda (`CloudFunctionConfiguration`) and SQS (`QueueConfiguration`) entries, which are the XML element names the S3 API returns regardless of how the configuration was created. Returns an arrayref of configuration hashrefs, each containing: - id The configuration entry identifier. - lambda\_arn The Lambda function ARN. Present for Lambda notification entries; `undef` for SQS entries. - queue\_arn The SQS queue ARN. Present for SQS notification entries; `undef` for Lambda entries. - events Arrayref of event type strings. - filters Arrayref of hashrefs, each with `name` (`prefix` or `suffix`) and `value`. Returns an empty arrayref if no notification configuration is set. Croaks on failure. ## remove\_bucket\_notification\_configuration $s3->remove_bucket_notification_configuration($bucket); Removes all notification configurations from `$bucket` by sending an empty `NotificationConfiguration` document to S3. After this call S3 will no longer deliver any events for the bucket. Returns true on success. Croaks on failure. # 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) - [Digest::MD5](https://metacpan.org/pod/Digest%3A%3AMD5) (core, for Content-MD5 headers) - [MIME::Base64](https://metacpan.org/pod/MIME%3A%3ABase64) (core) - [URI::Escape](https://metacpan.org/pod/URI%3A%3AEscape) - [Carp](https://metacpan.org/pod/Carp) (core) Optional: - [Amazon::Credentials](https://metacpan.org/pod/Amazon%3A%3ACredentials) - automatic credential discovery from IAM roles, ECS task roles, ~/.aws/credentials, and environment. - [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl) - 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](https://metacpan.org/pod/Amazon%3A%3ACredentials) 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](https://metacpan.org/pod/HTTP%3A%3ATiny) (Perl core), [XML::Twig](https://metacpan.org/pod/XML%3A%3ATwig), [AWS::Signature4](https://metacpan.org/pod/AWS%3A%3ASignature4), and [URI::Escape](https://metacpan.org/pod/URI%3A%3AEscape), 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](https://metacpan.org/pod/Amazon%3A%3AS3) - the full-featured S3 client this module draws from [Amazon::S3::Thin](https://metacpan.org/pod/Amazon%3A%3AS3%3A%3AThin) - 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](https://metacpan.org/pod/HTTP%3A%3AResponse) objects. See ["DESCRIPTION"](#description) for a detailed comparison. [Net::Amazon::S3](https://metacpan.org/pod/Net%3A%3AAmazon%3A%3AS3) - a Moose-based full-featured alternative [Amazon::Signature4::Lite](https://metacpan.org/pod/Amazon%3A%3ASignature4%3A%3ALite) - the signing module used internally [Amazon::Credentials](https://metacpan.org/pod/Amazon%3A%3ACredentials) - credential provider with IAM role and profile support # AUTHOR Rob Lauer # LICENSE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.