Documentation
¶
Overview ¶
Package s3 provides helpers built on the AWS SDK v2 S3 client for common bucket object operations:
- upload object data,
- download object data,
- list object keys by prefix,
- delete objects.
It is built on github.com/aws/aws-sdk-go-v2/service/s3.
What It Provides ¶
- New to create a bucket-scoped Client.
- Client.Put to upload from an io.Reader.
- Client.Get to fetch an object and access its body stream.
- Client.ListKeys to list object keys, optionally filtered by prefix.
- Client.ListObjects to list objects with per-object metadata (size, last-modified, ETag), optionally filtered by prefix.
- Client.Delete to remove an object by key.
- Client.HealthCheck to verify bucket reachability and access permissions.
Configuration & Extensibility ¶
The client configuration composes with github.com/tecnickcom/nurago/pkg/awsopt and exposes option hooks:
- WithAWSOptions to pass generic AWS config options,
- WithSrvOptionFuncs to customize S3 service options,
- WithS3Client to inject a custom S3 implementation (tests and advanced integrations; skips AWS configuration loading),
- WithEndpointMutable and WithEndpointImmutable for endpoint overrides (useful for local S3-compatible environments and tests).
Usage ¶
c, err := s3.New(ctx, "my-bucket")
if err != nil {
return err
}
if err := c.Put(ctx, "reports/latest.json", reader); err != nil {
return err
}
obj, err := c.Get(ctx, "reports/latest.json")
if err != nil {
return err
}
_ = obj
keys, err := c.ListKeys(ctx, "reports/")
if err != nil {
return err
}
_ = keys
if err := c.Delete(ctx, "reports/old.json"); err != nil {
return err
}
if err := c.HealthCheck(ctx); err != nil {
return err
}
Example ¶
package main
import (
"context"
"fmt"
"io"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/tecnickcom/nurago/pkg/s3"
)
// exampleS3Client is a minimal in-memory stand-in for the AWS S3 client,
// implementing only the s3.S3 interface. Real code would let New build the
// client from aws.Config instead.
type exampleS3Client struct {
objects map[string][]byte
}
func (c *exampleS3Client) PutObject(
_ context.Context,
params *awss3.PutObjectInput,
_ ...func(*awss3.Options),
) (*awss3.PutObjectOutput, error) {
body, err := io.ReadAll(params.Body)
if err != nil {
return nil, fmt.Errorf("cannot read object body: %w", err)
}
c.objects[aws.ToString(params.Key)] = body
return &awss3.PutObjectOutput{}, nil
}
func (c *exampleS3Client) GetObject(
_ context.Context,
params *awss3.GetObjectInput,
_ ...func(*awss3.Options),
) (*awss3.GetObjectOutput, error) {
body := c.objects[aws.ToString(params.Key)]
return &awss3.GetObjectOutput{
Body: io.NopCloser(strings.NewReader(string(body))),
ContentType: aws.String("application/json"),
ContentLength: aws.Int64(int64(len(body))),
}, nil
}
func (c *exampleS3Client) ListObjectsV2(
_ context.Context,
_ *awss3.ListObjectsV2Input,
_ ...func(*awss3.Options),
) (*awss3.ListObjectsV2Output, error) {
contents := make([]types.Object, 0, len(c.objects))
for key := range c.objects {
contents = append(contents, types.Object{Key: aws.String(key)})
}
return &awss3.ListObjectsV2Output{Contents: contents}, nil
}
func (c *exampleS3Client) DeleteObject(
_ context.Context,
params *awss3.DeleteObjectInput,
_ ...func(*awss3.Options),
) (*awss3.DeleteObjectOutput, error) {
delete(c.objects, aws.ToString(params.Key))
return &awss3.DeleteObjectOutput{}, nil
}
func (c *exampleS3Client) HeadBucket(
_ context.Context,
_ *awss3.HeadBucketInput,
_ ...func(*awss3.Options),
) (*awss3.HeadBucketOutput, error) {
return &awss3.HeadBucketOutput{}, nil
}
func main() {
// A caller would normally configure a real client via options such as
// WithAWSOptions or WithEndpointMutable; here an injected client keeps the
// example self-contained, so no AWS configuration is loaded.
c, err := s3.New(
context.TODO(),
"my-bucket",
s3.WithS3Client(&exampleS3Client{objects: map[string][]byte{}}),
)
if err != nil {
fmt.Println("error:", err)
return
}
err = c.Put(context.TODO(), "reports/latest.json", strings.NewReader(`{"ok":true}`))
if err != nil {
fmt.Println("error:", err)
return
}
obj, err := c.Get(context.TODO(), "reports/latest.json")
if err != nil {
fmt.Println("error:", err)
return
}
data, err := io.ReadAll(obj.Body())
if err != nil {
fmt.Println("error:", err)
return
}
err = obj.Close()
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(obj.ContentType())
fmt.Println(obj.ContentLength())
fmt.Println(string(data))
keys, err := c.ListKeys(context.TODO(), "reports/")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(keys)
err = c.Delete(context.TODO(), "reports/latest.json")
if err != nil {
fmt.Println("error:", err)
return
}
}
Output: application/json 11 {"ok":true} [reports/latest.json]
Index ¶
- Variables
- type Client
- func (c *Client) Delete(ctx context.Context, key string) error
- func (c *Client) Get(ctx context.Context, key string) (*Object, error)
- func (c *Client) HealthCheck(ctx context.Context) error
- func (c *Client) ListKeys(ctx context.Context, prefix string) ([]string, error)
- func (c *Client) ListObjects(ctx context.Context, prefix string) ([]ObjectInfo, error)
- func (c *Client) Put(ctx context.Context, key string, reader io.Reader) error
- type Object
- type ObjectInfo
- type Option
- type S3
- type SrvOptionFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrEmptyBucketName is returned by New when bucketName is empty. ErrEmptyBucketName = errors.New("s3: bucket name must not be empty") // ErrEmptyKey is returned by Get, Put and Delete when key is empty, before // any upstream call is made. ErrEmptyKey = errors.New("s3: object key must not be empty") // ErrEmptyObjectBody is returned by Get when the response carries no body // stream (a nil response or a nil Body). ErrEmptyObjectBody = errors.New("s3: object response has no body") // ErrBucketNotResponding is returned by HealthCheck when the bucket probe // succeeds but returns a nil response. ErrBucketNotResponding = errors.New("s3: the bucket is not responding") )
Exported sentinel errors returned by this package. Match them with errors.Is.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client wraps AWS SDK S3 operations for a single target bucket.
func New ¶
New builds a client for bucketName using configured AWS credentials and S3 options.
bucketName must not be empty; a cheap check runs before any AWS configuration is loaded, so misconfiguration fails fast with ErrEmptyBucketName. A custom client can be supplied with WithS3Client, in which case no AWS configuration is loaded.
The returned Client is safe for concurrent use.
func (*Client) Delete ¶
Delete removes the object identified by key from the configured bucket.
It returns ErrEmptyKey when key is empty, before any upstream call is made.
func (*Client) Get ¶
Get fetches an object by key and returns an Object with its streaming body.
It returns ErrEmptyKey when key is empty, and ErrEmptyObjectBody when the response carries no body stream.
func (*Client) HealthCheck ¶
HealthCheck verifies bucket reachability and access permissions via HeadBucket.
The probe uses HeadBucket and therefore requires s3:ListBucket permission on the bucket; a permissions error surfaces as a wrapped AWS error, not as a bucket outage.
It returns ErrBucketNotResponding when the probe succeeds but returns a nil response; the underlying AWS error is wrapped otherwise.
func (*Client) ListKeys ¶
ListKeys returns object keys matching prefix; an empty prefix lists all keys in the bucket. It transparently paginates over ListObjectsV2 results, so all matching keys are returned even when they exceed the per-request AWS limit (1000 keys).
It is a thin projection over Client.ListObjects; use ListObjects when the per-object size, last-modified time, or ETag is also needed.
func (*Client) ListObjects ¶
ListObjects returns metadata for objects matching prefix; an empty prefix lists all objects in the bucket. Like Client.ListKeys, it transparently paginates over ListObjectsV2 results, so all matching objects are returned even when they exceed the per-request AWS limit (1000 objects).
type Object ¶
type Object struct {
// contains filtered or unexported fields
}
Object contains metadata and body stream for a downloaded S3 object.
The caller owns the underlying body stream and MUST call Object.Close when done to release the network connection and avoid leaking resources.
func (*Object) Body ¶
func (o *Object) Body() io.ReadCloser
Body returns the streaming body of the object.
The caller MUST call Object.Close (not the returned reader directly) once finished reading to release the underlying resources.
func (*Object) Close ¶
Close releases the underlying body stream of the object.
It MUST be called once the caller is done with the object to avoid leaking the underlying network connection.
func (*Object) ContentLength ¶
ContentLength returns the object's size in bytes. GetObject responses always carry Content-Length, so 0 denotes a genuinely empty object.
func (*Object) ContentType ¶
ContentType returns the object's Content-Type, or "" when the response did not carry one.
func (*Object) ETag ¶
ETag returns the object's entity tag, or "" when the response did not carry one.
func (*Object) LastModified ¶
LastModified returns the object's last-modified time, or the zero time when the response did not carry one.
type ObjectInfo ¶
type ObjectInfo struct {
// Key is the object key.
Key string
// Size is the object size in bytes.
Size int64
// LastModified is the object's last-modified time, or the zero time when unset.
LastModified time.Time
// ETag is the object's entity tag, or "" when unset.
ETag string
}
ObjectInfo describes a single object returned by Client.ListObjects.
type Option ¶
type Option func(*cfg)
Option applies a configuration change to the internal S3 client settings.
func WithAWSOptions ¶
WithAWSOptions appends awsopt options used to build aws.Config.
func WithEndpointImmutable ¶
WithEndpointImmutable installs a fixed EndpointResolverV2 for deterministic endpoint routing.
func WithEndpointMutable ¶
WithEndpointMutable sets BaseEndpoint while allowing SDK endpoint behavior to remain mutable.
func WithS3Client ¶
WithS3Client injects a custom S3 implementation.
This is primarily useful for tests and advanced integrations where the caller needs full control over request behavior without creating a real s3.Client from aws.Config. When set, the injected client is used as-is: the AWS configuration is not loaded and the AWS/service options (WithAWSOptions, WithSrvOptionFuncs, WithEndpointMutable, WithEndpointImmutable) are ignored.
func WithSrvOptionFuncs ¶
func WithSrvOptionFuncs(opt ...SrvOptionFunc) Option
WithSrvOptionFuncs appends service-specific s3.Options mutators.
type S3 ¶
type S3 interface {
DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error)
GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error)
HeadBucket(ctx context.Context, params *s3.HeadBucketInput, optFns ...func(*s3.Options)) (*s3.HeadBucketOutput, error)
ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error)
PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
}
S3 is the minimal AWS SDK S3 API surface required by Client.
type SrvOptionFunc ¶
SrvOptionFunc aliases an AWS SDK S3 service option mutator.