s3

package
v1.2.5 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

s3 — S3 client for TinyGo

The maintained Go clients do not build with TinyGo. aws-sdk-go-v2 reaches for the full net/http.Transport API, which TinyGo declares as an empty struct, and its transport layer imports net/http/httputil, which does not compile under TinyGo at all; minio-go fails even earlier, on net/http/cookiejar. This package therefore speaks the S3 REST API directly, over the SigV4 signer in cloud/aws, which nosql/dynamodb shares.

import "github.com/shibukawa/tinygodriver/storage/s3"

client, err := s3.New(
	s3.WithRegion("ap-northeast-1"),
	s3.WithCredentials(s3.Credentials{AccessKeyID: id, SecretAccessKey: secret}),
)

obj, err := client.Get(ctx, "bucket", "photos/cat.jpg")
defer obj.Body.Close()

Implementation selection

Signing, request building, and XML decoding are shared code. The builds differ only in how a request reaches the network:

Build HTTP stack (s3.Backend)
Standard Go net/http with crypto/tls
TinyGo, or -tags force_tinygo_logic https, TLS through the host OS

Neither build lets http.Client follow redirects, because a redirected request goes to a different host and its signature covers that host. Client follows them itself and signs each hop, so a bucket in another region works the same on both builds. TinyGo's http.Client never follows redirects anyway, which is what makes this the only correct design.

API

Method S3 operation
Get, GetRange GetObject, with Range
Put PutObject
Head HeadObject
Delete DeleteObject
List ListObjectsV2, one page
CreateBucket, DeleteBucket CreateBucket, DeleteBucket

Put takes WithContentType, WithContentEncoding, WithMetadata, and WithContentLength. List takes WithPrefix, WithDelimiter, WithMaxKeys, WithStartAfter, and WithContinuationToken.

List returns one page. A truncated page carries NextToken:

var token string
for {
	page, err := client.List(ctx, "bucket",
		s3.WithPrefix("photos/"), s3.WithContinuationToken(token))
	if err != nil {
		return err
	}
	for _, obj := range page.Objects {
		fmt.Println(obj.Key, obj.Size)
	}
	if !page.IsTruncated {
		break
	}
	token = page.NextToken
}

Configuration

New falls back to the environment, so a configured shell needs no options: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_REGION (or AWS_DEFAULT_REGION), and AWS_ENDPOINT_URL_S3 (or AWS_ENDPOINT_URL).

Option Effect
WithEndpoint endpoint URL, for S3-compatible servers
WithRegion signing region
WithCredentials static credentials
WithCredentialsFromEnv read credentials from the environment
WithPathStyle endpoint/bucket/key instead of bucket.endpoint/key
WithUnsignedPayload sign headers only, so large streams are not buffered
WithTimeout logical-operation timeout including redirects and response body, default 60s
WithHTTPClient supply the http.Client

Addressing defaults to virtual-host style for amazonaws.com endpoints and path style everywhere else, which is what S3-compatible servers expect.

client, err := s3.New(
	s3.WithEndpoint("http://127.0.0.1:9000"),
	s3.WithRegion("us-east-1"),
	s3.WithCredentials(s3.Credentials{AccessKeyID: "admin", SecretAccessKey: "admin"}),
)

Errors

S3 error codes map onto sentinels, so application code branches without matching strings. *s3.Error carries the status, code, message, and request ID.

obj, err := client.Get(ctx, "bucket", "missing")
if errors.Is(err, s3.ErrNoSuchKey) {
	// ...
}

var s3err *s3.Error
if errors.As(err, &s3err) {
	log.Println(s3err.StatusCode, s3err.Code, s3err.RequestID)
}

ErrNoSuchKey, ErrNoSuchBucket, ErrAccessDenied, ErrBucketExists, ErrBucketNotEmpty, ErrInvalidRange, ErrBadCredentials, ErrNoCredentials, ErrNoRegion, ErrTooManyRedirect.

Limitations

  • No multipart upload. Put sends one request, so the endpoint's single-request limit applies (5 GiB on AWS).
  • Put reads the body twice. SigV4 signs a hash of the payload. A body that implements io.Seeker is hashed and rewound; anything else is buffered in memory. WithUnsignedPayload streams instead, at the cost of the signature no longer covering the body — use it only over https. A stream that reports no length goes out chunked, which AWS rejects for PutObject, so pass s3.WithContentLength(n) with it.
  • No connection reuse on TinyGo. The https transport opens a connection per request, so every call pays a TLS handshake.
  • Credentials are static or environment values. No shared credentials file, no SSO, no IMDS.
  • No versioning, ACL, tagging, or lifecycle APIs.

Testing

Unit tests run against a fake endpoint under both build configurations:

go test ./storage/s3/
go test -tags force_tinygo_logic ./storage/s3/

Integration tests need an S3-compatible endpoint, for example RustFS:

docker run -d --name rustfs -p 19000:9000 -e RUSTFS_ACCESS_KEY=rustfsadmin -e RUSTFS_SECRET_KEY=rustfsadmin -e RUSTFS_VOLUMES=/data rustfs/rustfs
S3_TEST_ENDPOINT=http://127.0.0.1:19000 S3_TEST_ACCESS_KEY=rustfsadmin S3_TEST_SECRET_KEY=rustfsadmin go test ./storage/s3/

Documentation

Overview

Package s3 is an S3 client that builds with TinyGo.

The maintained Go clients cannot be used here. aws-sdk-go-v2 reaches for the full net/http.Transport API, which TinyGo declares as an empty struct, and its transport layer imports net/http/httputil, which does not compile under TinyGo at all. This package therefore speaks the S3 REST API directly: a SigV4 signer, request builders, and XML decoding, over whichever HTTP stack the build selects.

client, err := s3.New(
	s3.WithRegion("ap-northeast-1"),
	s3.WithCredentials(s3.Credentials{AccessKeyID: id, SecretAccessKey: secret}),
)
obj, err := client.Get(ctx, "bucket", "photos/cat.jpg")
defer obj.Body.Close()

Implementation selection

Signing, request building, and response decoding are shared code: the two builds differ only in how a request reaches the network.

  • standard Go builds use net/http and crypto/tls
  • TinyGo builds use github.com/shibukawa/tinygodriver/https, which performs TLS through the TLS stack of the host OS
  • go build -tags force_tinygo_logic selects the TinyGo path under host Go, which is how that path is tested without a TinyGo toolchain

Neither build follows redirects through http.Client, because a redirected request must be signed again for its new host. Redirects are handled here instead, so a bucket in another region works the same way on both builds.

Credentials

Credentials are static values or environment variables. There is no shared credentials file, no SSO, and no IMDS lookup:

s3.WithCredentials(s3.Credentials{AccessKeyID: id, SecretAccessKey: secret})
s3.WithCredentialsFromEnv() // AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, ...

New reads the environment when no credentials option is given.

Scope

Whole-object operations only. Multipart upload is not implemented, so Put sends one request and is bounded by what the endpoint accepts in a single PUT (5 GiB on AWS). Large uploads should use a stream the package can rewind or hash, see Put.

Index

Constants

View Source
const Backend = aws.Backend

Backend identifies the HTTP stack selected by build constraints: "net/http" on standard Go builds, "https" on TinyGo builds.

View Source
const DefaultTimeout = 60 * time.Second

DefaultTimeout bounds one logical operation when no timeout is configured.

Variables

View Source
var (
	ErrNoSuchKey      = errors.New("s3: no such key")
	ErrNoSuchBucket   = errors.New("s3: no such bucket")
	ErrAccessDenied   = errors.New("s3: access denied")
	ErrBucketExists   = errors.New("s3: bucket already exists")
	ErrBucketNotEmpty = errors.New("s3: bucket not empty")
	ErrInvalidRange   = errors.New("s3: requested range not satisfiable")
	ErrBadCredentials = errors.New("s3: credentials rejected")

	// Configuration failures are the shared ones, so errors.Is matches whether
	// the caller compares against s3 or cloud/aws.
	ErrNoCredentials   = aws.ErrNoCredentials
	ErrNoRegion        = aws.ErrNoRegion
	ErrTooManyRedirect = errors.New("s3: too many redirects")
)

Sentinel errors. S3 error codes are mapped onto them so application code can branch with errors.Is without matching strings.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client talks to one S3 endpoint. It is safe for concurrent use.

func New

func New(opts ...Option) (*Client, error)

New builds a Client. Region, endpoint, and credentials fall back to the environment, so a configured shell needs no options at all.

func (*Client) CreateBucket

func (c *Client) CreateBucket(ctx context.Context, bucket string) error

CreateBucket creates a bucket in the client's region. A bucket that already belongs to the caller reports ErrBucketExists.

func (*Client) Delete

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

Delete removes an object. Deleting a key that does not exist succeeds, which is how S3 itself behaves.

func (*Client) DeleteBucket

func (c *Client) DeleteBucket(ctx context.Context, bucket string) error

DeleteBucket removes an empty bucket.

func (*Client) Endpoint

func (c *Client) Endpoint() string

Endpoint reports the endpoint URL in use.

func (*Client) Get

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

Get retrieves an object. The caller closes Object.Body.

func (*Client) GetRange

func (c *Client) GetRange(ctx context.Context, bucket, key string, offset, length int64) (*Object, error)

GetRange retrieves length bytes starting at offset. A length of zero or less reads to the end of the object.

func (*Client) Head

func (c *Client) Head(ctx context.Context, bucket, key string) (*ObjectInfo, error)

Head reports an object's metadata without transferring it.

func (*Client) List

func (c *Client) List(ctx context.Context, bucket string, opts ...ListOption) (*ListResult, error)

List returns one page of a bucket listing. A truncated page carries NextToken, which WithContinuationToken feeds back to fetch the next one.

func (*Client) Put

func (c *Client) Put(ctx context.Context, bucket, key string, body io.Reader, opts ...PutOption) (*PutResult, error)

Put stores body under key.

There is no multipart upload, so the whole object is sent in one request and the endpoint's single-request limit applies (5 GiB on AWS).

SigV4 signs a hash of the body, so the body must be read twice. A body that implements io.Seeker (an *os.File, a *bytes.Reader) is hashed and rewound; anything else is buffered in memory. Configure WithUnsignedPayload to stream instead, at the cost of the signature no longer covering the body.

func (*Client) Region

func (c *Client) Region() string

Region reports the signing region, which redirect handling may have updated.

type Credentials

type Credentials = aws.Credentials

Credentials are the values SigV4 signs with. It is an alias, so credentials built here work with any other client in this repository.

func CredentialsFromEnv

func CredentialsFromEnv() Credentials

CredentialsFromEnv reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN.

type Error

type Error struct {
	Op         string // "Get", "Put", "List", ...
	Bucket     string
	Key        string
	StatusCode int
	Code       string
	Message    string
	RequestID  string
	// contains filtered or unexported fields
}

Error is a failed S3 operation. Code and Message come from the XML error document when the endpoint sent one.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the sentinel this failure maps onto, so errors.Is works.

type ListOption

type ListOption func(*listConfig)

ListOption configures a single List call.

func WithContinuationToken

func WithContinuationToken(token string) ListOption

WithContinuationToken resumes a truncated listing from ListResult.NextToken.

func WithDelimiter

func WithDelimiter(delimiter string) ListOption

WithDelimiter groups keys sharing a prefix up to the delimiter, reporting them in ListResult.CommonPrefixes. Use "/" to walk a listing like a directory tree.

func WithMaxKeys

func WithMaxKeys(maxKeys int) ListOption

WithMaxKeys caps how many keys one page returns.

func WithPrefix

func WithPrefix(prefix string) ListOption

WithPrefix limits the listing to keys with this prefix.

func WithStartAfter

func WithStartAfter(key string) ListOption

WithStartAfter begins the listing after this key.

type ListResult

type ListResult struct {
	Objects        []ObjectInfo
	CommonPrefixes []string
	IsTruncated    bool

	// NextToken continues a truncated listing through WithContinuationToken.
	NextToken string
}

ListResult is one page of a listing.

type Object

type Object struct {
	ObjectInfo
	Body io.ReadCloser
}

Object is an object body together with its metadata. Body must be closed.

type ObjectInfo

type ObjectInfo struct {
	Key             string
	Size            int64
	ETag            string
	ContentType     string
	ContentEncoding string
	LastModified    time.Time

	// Metadata holds user metadata, with the x-amz-meta- prefix removed.
	Metadata map[string]string
}

ObjectInfo is the metadata S3 reports for an object.

type Option

type Option func(*config)

Option configures a Client.

func WithCredentials

func WithCredentials(creds Credentials) Option

WithCredentials sets static credentials.

func WithCredentialsFromEnv

func WithCredentialsFromEnv() Option

WithCredentialsFromEnv reads credentials from the environment. New does this already when no credentials option is given; the option states it explicitly.

func WithEndpoint

func WithEndpoint(endpoint string) Option

WithEndpoint overrides the endpoint URL, for S3-compatible servers such as RustFS or MinIO. It defaults to AWS_ENDPOINT_URL_S3, AWS_ENDPOINT_URL, or the regional AWS endpoint.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient supplies the http.Client to use.

Its CheckRedirect must return http.ErrUseLastResponse on standard Go builds: a redirected request has to be signed again for its new host, which this package does itself.

func WithPathStyle

func WithPathStyle(pathStyle bool) Option

WithPathStyle selects between https://endpoint/bucket/key and https://bucket.endpoint/key addressing.

The default is virtual-host addressing for amazonaws.com endpoints and path addressing everywhere else, which is what S3-compatible servers expect.

func WithRegion

func WithRegion(region string) Option

WithRegion sets the signing region. It defaults to AWS_REGION, then AWS_DEFAULT_REGION.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout bounds one logical operation, including redirects and reading the response body. Zero means DefaultTimeout.

func WithUnsignedPayload

func WithUnsignedPayload(unsigned bool) Option

WithUnsignedPayload signs requests with UNSIGNED-PAYLOAD instead of the body hash, so Put streams a body it cannot rewind instead of buffering it.

The request headers stay signed. Use it only over https, where TLS protects the body that the signature no longer covers.

type PutOption

type PutOption func(*putConfig)

PutOption configures a single Put.

func WithContentEncoding

func WithContentEncoding(encoding string) PutOption

WithContentEncoding sets the object's Content-Encoding, for a body that is already compressed.

func WithContentLength

func WithContentLength(length int64) PutOption

WithContentLength states the body size for a stream the package cannot measure, which matters together with WithUnsignedPayload: without a length, the body goes out chunked, and AWS rejects a chunked PutObject.

func WithContentType

func WithContentType(contentType string) PutOption

WithContentType sets the object's Content-Type.

func WithMetadata

func WithMetadata(metadata map[string]string) PutOption

WithMetadata attaches user metadata, sent as x-amz-meta-* headers.

type PutResult

type PutResult struct {
	ETag      string
	VersionID string
}

PutResult reports what the endpoint stored.

Jump to

Keyboard shortcuts

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