Rene Nochebuena 90a2d38c9c
einherjar/ci: main / gate (push) Successful in 59s
einherjar/ci: main / release (push) Successful in 5s
chore: name CoreDevelopers as the only code owner
Co-authored-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
2026-10-04 17:26:34 +00:00

einherjar/storage-s3

version license go health

A vault is only as free as the key that opens it. Carry a key every lock understands.

code.nochebuena.dev/einherjar/storage-s3 is the object storage component of the Einherjar framework. It speaks the S3 API through the AWS SDK for Go v2, so the server behind it is a deployment choice — AWS, Cloudflare R2, Ceph, SeaweedFS, Garage, or anything else that answers S3. It wraps *s3.Client behind a lifecycle-aware Component with four common operations — upload, download, delete, and presigned downloads. For anything beyond that scope, Native() returns the client itself.


Usage

Setup

import storages3 "code.nochebuena.dev/einherjar/storage-s3"

s := storages3.New(logger, cfg) // cfg from env, or storages3.DefaultConfig() plus the required fields
lc.Append(s)   // OnInit builds the client; OnStart checks the bucket and creates it if absent
// s is observability.Checkable (HeadBucket, LevelCritical):
srv.Get("/health", health.NewHandler(logger, s).ServeHTTP)

Operations

Every Provider method takes the SDK's own input type. Leave Bucket unset to use the configured bucket; set it to reach another one. The input is copied, never modified.

import (
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

_, err := s.PutObject(ctx, &s3.PutObjectInput{
    Key:         aws.String("uploads/photo.jpg"),
    Body:        bytes.NewReader(data),
    ContentType: aws.String("image/jpeg"),
})

out, err := s.GetObject(ctx, &s3.GetObjectInput{Key: aws.String("uploads/photo.jpg")})
if err != nil {
    return err // already an xerrors value
}
defer out.Body.Close()

_, err = s.DeleteObject(ctx, &s3.DeleteObjectInput{Key: aws.String("uploads/photo.jpg")})

Over plain HTTP the upload is signed over its payload, so Body must be an io.ReadSeeker (bytes.Reader, strings.Reader, *os.File). For a stream, use TLS or the SDK's upload manager through Native().

Presigned download (time-limited access without credentials)

req, err := s.PresignGetObject(ctx, &s3.GetObjectInput{Key: aws.String("uploads/photo.jpg")}, 15*time.Minute)
// req.URL is the string to hand out

Native escape hatch

For multipart uploads, listings, bucket policies, or any operation not in Provider, use the client:

native := s.Native() // *s3.Client
_, err := native.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{Bucket: aws.String(bucket)})
err = storages3.HandleError(err)

Error handling

if err := s.HandleError(someErr); err != nil {
    // NoSuchKey / NoSuchBucket / a bare 404                    → ErrNotFound
    // AccessDenied / InvalidAccessKeyId / SignatureDoesNotMatch → ErrPermissionDenied
    // BucketAlreadyExists / BucketAlreadyOwnedByYou            → ErrAlreadyExists
    // context.Canceled                                         → ErrCancelled
    // context.DeadlineExceeded                                 → ErrDeadlineExceeded
    // anything else                                            → ErrInternal
}

HandleError is also available as a package-level function: storages3.HandleError(err).


Environment variables

Variable Required Default Description
EINHERJAR_S3_ENDPOINT No — Base URL with scheme, e.g. http://seaweedfs:8333. Empty resolves AWS for the region
EINHERJAR_S3_REGION No us-east-1 Signing region
EINHERJAR_S3_ACCESS_KEY_ID Yes — Access key ID
EINHERJAR_S3_SECRET_ACCESS_KEY Yes — Secret access key
EINHERJAR_S3_BUCKET Yes — Default bucket: checked and created at startup, probed by the health check
EINHERJAR_S3_USE_PATH_STYLE No true <endpoint>/<bucket> addressing. Self-hosted servers need it; set false for AWS

The access key pair is the only credential source. The component never reads the AWS environment variables, shared config files or instance metadata.


Dependency graph

contracts  (zero dependencies)
    ↑
  core
    ↑
storage-s3  (contracts, core, aws-sdk-go-v2/service/s3)
    ↑
  your app

Verification

cd storage-s3/
go build ./...
go vet ./...
go test ./...
gofmt -l .

The artifact survives the battle that created it. Store it well. Someone will need it after you are gone.

S
Description
Object storage over the S3 API — works with any S3-compatible provider.
Readme AGPL-3.0
74 KiB
0 Stars 3 Watchers 0 Forks
Languages
Go 100%