Object storage on S3 and S3-compatible stores, GCS, Azure Blob, OCI Object Storage, AliOSS and a local directory, behind one interface. storage.New(conf) returns a Storage; every call takes a context.
Bucket(storage/objects):PutObject,UploadObject(streams),GetObject,GetObjectRange,DownloadObject(to anyio.Writer),ObjectExists,ListObjects(byte order),DeleteObjects,PresignedURL.StorageisBucketplusUploadFileandDownloadFile.DownloadFilereplaces the local file only once the whole object is read.PutOptions{ContentType, IfAbsent}on every write.IfAbsentrefuses to replace an object, atomically where the store honors the condition.- Errors: a missing object is
ErrNotFound, a writeIfAbsentrefuses isErrExists(errors.Is);ErrorWithStatusCodecarries the store's HTTP status andDeleteErrorthe paths a delete left (errors.As). storage/objectsholds the interfaces in a module of its own, with no requirements (see Modules).
- S3 semantics where stores differ.
DeleteObjectsof a missing path succeeds, as S3's delete does. It tries every path, and when some are not deleted fails with a*DeleteErrorwhoseFailednames each one with its error (errors.As); every other path is deleted, and a retry is safe. - Keys are the store's keys as given. The local directory, as a filesystem, drops a leading
/, cleans//and., refuses keys outside it, and cannot hold bothaanda/b. - Timeouts: a connection that moves no bytes either way for 5 minutes is cut; a slow but live transfer never is. The context bounds the call.
- Retries: every store retries throttling, 5xx and network errors with backoff, including AliOSS, whose SDK does not. A stream given to
UploadObjectis sent once on AliOSS. - Proxy:
ProxyConfig(S3, GCS) carries its credentials for http and https targets, honorsNO_PROXY, and never proxies the cloud metadata services. - Local writes go in place, as a file is written, so mounted volumes and file watchers see one write;
IfAbsentwrites a temporary file (.upload-*, never listed) and links it into place.
| module | holds | requires |
|---|---|---|
github.com/livekit/storage |
the backends, New, the configs |
every provider SDK, livekit/protocol |
github.com/livekit/storage/objects |
Bucket, Storage, PutOptions, the errors |
nothing |
github.com/livekit/storage/e2e |
the end-to-end suites | dockertest |
Why objects is its own module:
- A module's requirements take part in the version selection of every module that requires it, whichever packages it imports. In the root module, the interfaces would carry every SDK's and
livekit/protocol's versions (grpc, otel) into each fake or library that implements or accepts them. - With no requirements, implementing them costs no dependency: gosim's
fakes/objstoreBucketis anobjects.Bucket.
How its versions work:
go.modnames theobjectsversion the root requires;go.workbuilds the repo againstobjects/in the tree.- A PR that changes
objects/under a released version gets the next minor version committed to it byobjects-version.yaml, which also runs the checks on that commit; the PR's own runs for it wait for an approval nobody needs to give. Pull that commit before pushing again. From a fork, CI fails and prints the command. - Until the version is tagged,
go mod tidyin the root fails on it (unknown revision);go.workbuilds the repo meanwhile. - On merge,
objects-release.yamltagsobjects/vX.Y.Zon main; a failed run can be rerun by hand. Squash merges drop a branch's commits, so only a version tagged on main is sure to stay resolvable. testmust be a required check on main with branches up to date: two PRs bumped to the same version cannot both merge then.
The suites live in the e2e module, so this module's users do not inherit Docker's dependencies.
go test ./...here runs the unit tests.cd e2e && go test ./...runs every backend except AliOSS end to end: against floci emulators in Docker (S3, Azure Blob, GCS, OCI Object Storage), against MinIO for S3's conditional writes (MINIO_BINorminioonPATH), and on the local directory. Without Docker or MinIO those tests skip, except whenCIis set.- The provider tests (
TestS3,TestGCP, ...) reach real accounts when their environment variables are set; AliOSS has no emulator and is covered only that way.