s3

package
v1.152.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package s3 provides lightweight helpers built on the AWS SDK v2 S3 client for common bucket object operations.

The package focuses on pragmatic day-to-day workflows:

  • 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 while hiding repetitive client setup and request boilerplate behind a compact API.

Problem

Using the raw S3 SDK directly in every service often leads to duplicated code for configuration loading, client initialization, endpoint overrides (for local testing), and basic object operations. Over time, these duplicated snippets can drift and make maintenance harder.

This package centralizes that integration into a small reusable client.

What It Provides

Configuration & Extensibility

The client configuration composes with github.com/tecnickcom/nurago/pkg/awsopt and exposes option hooks for advanced scenarios:

Benefits

  • Consistent S3 integration patterns across services.
  • Less boilerplate for the most common object operations.
  • Easier local testing and custom endpoint routing.

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

Examples

Constants

This section is empty.

Variables

View Source
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

func New(ctx context.Context, bucketName string, opts ...Option) (*Client, error)

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

func (c *Client) Delete(ctx context.Context, key string) error

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

func (c *Client) Get(ctx context.Context, key string) (*Object, error)

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

func (c *Client) HealthCheck(ctx context.Context) error

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

func (c *Client) ListKeys(ctx context.Context, prefix string) ([]string, error)

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

func (c *Client) ListObjects(ctx context.Context, prefix string) ([]ObjectInfo, error)

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).

func (*Client) Put

func (c *Client) Put(ctx context.Context, key string, reader io.Reader) error

Put uploads reader content to key in the configured bucket.

It returns ErrEmptyKey when key is empty, before any upstream call is made. A nil reader uploads an empty (zero-byte) object.

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) Bucket

func (o *Object) Bucket() string

Bucket returns the name of the bucket this object was fetched from.

func (*Object) Close

func (o *Object) Close() error

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

func (o *Object) ContentLength() int64

ContentLength returns the object's size in bytes. GetObject responses always carry Content-Length, so 0 denotes a genuinely empty object.

func (*Object) ContentType

func (o *Object) ContentType() string

ContentType returns the object's Content-Type, or "" when the response did not carry one.

func (*Object) ETag

func (o *Object) ETag() string

ETag returns the object's entity tag, or "" when the response did not carry one.

func (*Object) Key

func (o *Object) Key() string

Key returns the object key this object was fetched with.

func (*Object) LastModified

func (o *Object) LastModified() time.Time

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

func WithAWSOptions(opt awsopt.Options) Option

WithAWSOptions appends awsopt options used to build aws.Config.

func WithEndpointImmutable

func WithEndpointImmutable(url string) Option

WithEndpointImmutable installs a fixed EndpointResolverV2 for deterministic endpoint routing.

func WithEndpointMutable

func WithEndpointMutable(url string) Option

WithEndpointMutable sets BaseEndpoint while allowing SDK endpoint behavior to remain mutable.

func WithS3Client

func WithS3Client(client S3) Option

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

type SrvOptionFunc = func(*s3.Options)

SrvOptionFunc aliases an AWS SDK S3 service option mutator.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL