Documentation
¶
Overview ¶
Package objstore is one object-storage bucket with compare-and-swap: conditional PUT (PutIfAbsent, PutIfMatch) and GET (GetIfChanged), the ETag with the body, paginated listing and batch delete. New picks the backend from the endpoint: an S3 URL (AWS, MinIO, any S3-compatible service) or file:// for a durable local store with chunked write-back.
Objects are meant to be written once and never mutated except by deletion; the conditional writes cover the few that must change. ListPrefixes returns the child prefixes under a prefix (S3's delimiter), so discovering a thousand top-level names costs one request, not one per object.
ConfigureCMEK adds envelope encryption of every object under ns/<name>/ with a per-name data key wrapped by a customer key (see package kms for the providers).
Index ¶
- Constants
- Variables
- func Urgent(ctx context.Context) context.Context
- func WithTimings(ctx context.Context, t *Timings) context.Context
- type Backend
- type Call
- type Config
- type Envelope
- type Store
- func (s *Store) CheckNamespaceKey(ctx context.Context, name string) error
- func (s *Store) CheckNamespaceKeyCached(ctx context.Context, name string) error
- func (s *Store) ConfigureCMEK(provider kms.KeyProvider)
- func (s *Store) Delete(ctx context.Context, key string) error
- func (s *Store) DeleteMany(ctx context.Context, keys ...string) error
- func (s *Store) DropBucket(ctx context.Context) error
- func (s *Store) EnsureBucket(ctx context.Context) error
- func (s *Store) Get(ctx context.Context, key string) ([]byte, error)
- func (s *Store) GetIfChanged(ctx context.Context, key, etag string) ([]byte, string, bool, error)
- func (s *Store) GetRange(ctx context.Context, key string, offset, length int64) ([]byte, error)
- func (s *Store) GetWithETag(ctx context.Context, key string) ([]byte, string, error)
- func (s *Store) InstallNamespaceKey(ctx context.Context, name string, envelope Envelope) error
- func (s *Store) List(ctx context.Context, prefix string) ([]string, error)
- func (s *Store) ListPage(ctx context.Context, prefix, after string, limit int) ([]string, string, error)
- func (s *Store) ListPrefixes(ctx context.Context, prefix string) ([]string, error)
- func (s *Store) ListPrefixesPage(ctx context.Context, prefix, after string, limit int) ([]string, string, error)
- func (s *Store) Put(ctx context.Context, key string, data []byte) error
- func (s *Store) PutIfAbsent(ctx context.Context, key string, data []byte) (bool, error)
- func (s *Store) PutIfMatch(ctx context.Context, key string, data []byte, etag string) (bool, error)
- func (s *Store) RetireNamespaceKey(ctx context.Context, name, incarnation string) error
- func (s *Store) RotateNamespaceKey(ctx context.Context, name string) (*Envelope, error)
- func (s *Store) SSE() (mode, kmsKeyID string)
- func (s *Store) SetCMEKRefreshInterval(interval time.Duration)
- func (s *Store) WithBackend(wrap func(Backend) Backend) *Store
- type Timings
Constants ¶
const EncryptionCustomerManaged = "customer-managed"
EncryptionCustomerManaged is the Envelope mode InstallNamespaceKey accepts: the data key is wrapped under a key the customer controls.
const MaxListPage = 1000
Variables ¶
var (
ErrEndpointDenied = errors.New("store: endpoint denied by allow-list")
)
var ErrNotFound = errors.New("object not found")
var ErrRange = errors.New("invalid object range")
Functions ¶
Types ¶
type Backend ¶
type Backend = backend
Backend is the backend contract, exported for test wrappers (storetest.Fault).
type Call ¶
type Call int
Call is a kind of work the store does for a caller: the file store's system calls, and the write gate every backend shares.
const ( CallGate Call = iota // waiting for a write slot (Config.MaxInflightWrites) CallCreate // creating an object's temp file (and its directory) CallWrite // write(2) of an object's temp file CallWriteBack // sync_file_range of a chunk (writeBackChunk) CallFsync // fsync of an object's temp file CallLock // the key's stripe lock CallLink // rename(2) or link(2) into place CallDirSync // fsync of a directory after a publish CallRead // an object read, whole or ranged CallList // a listing's directory walk CallDelete // unlink(2) )
type Config ¶
type Config struct {
Endpoint string
Bucket string
AllowedEndpoints []string
SSE string
KMSKeyID string
// RequestsPerSecond paces every request to the object store; 0 = no
// pacing. A burst past what the provider serves does not fail, it
// stalls: at ~900 requests/s against Hetzner Object Storage every
// in-flight request hung for 13-15 s, the lease renewal among them, and
// the namespace was fenced. Pacing below that keeps a prefetch burst
// and the heartbeat both moving.
RequestsPerSecond float64
// RequestTimeout bounds one S3 request end to end; 0 = 60 s. A store
// whose queue is deeper than that truncates large uploads mid-body
// (MinIO answers 400 IncompleteBody) rather than finishing them.
RequestTimeout time.Duration
// MaxInflightWrites bounds the object writes and deletes in flight at
// once, except those marked Urgent (lease heartbeats, log commits,
// manifest heads); 0 = 16. A background job publishes thousands of
// objects in a burst; without a bound they queue ahead of the write
// path's own small requests on the store.
MaxInflightWrites int
}
type Envelope ¶
type Envelope struct {
Mode string `json:"mode"`
KeyName string `json:"key_name,omitempty"`
KeyVersion string `json:"key_version,omitempty"`
DEKWrapped []byte `json:"dek_wrapped,omitempty"`
}
Envelope is a namespace's key record: the wrapped data encryption key and the customer key that wraps it. It never holds plaintext key bytes.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is one bucket of write-once objects with the OCC primitives a log and a manifest build on (PutIfAbsent to append, PutIfMatch to swap).
sse/kmsKeyID are the Config values the backend was built with, kept here so callers can REPORT the bucket's encryption (namespace metadata) without reaching into a backend. They are descriptive: the backend applies them.
func New ¶
New connects to a bucket. endpoint selects the backend: "" = real AWS S3, an http(s) URL = S3-compatible (MinIO), "file:///path" = local filesystem (no dependencies — dev and hermetic CI; unix-only).
func (*Store) CheckNamespaceKey ¶
CheckNamespaceKey revalidates KMS access at the API boundary, including cache-only queries. AWS checks share the lease renewal cadence.
func (*Store) CheckNamespaceKeyCached ¶
CheckNamespaceKeyCached enforces the key-cache lease for internal calls. Use CheckNamespaceKey for access checks at a request boundary.
func (*Store) ConfigureCMEK ¶
func (s *Store) ConfigureCMEK(provider kms.KeyProvider)
ConfigureCMEK enables transparent encryption of ns/<name>/ objects. A small envelope record outside that prefix bootstraps the encrypted manifest and metadata. The record contains a wrapped DEK, never plaintext key bytes.
func (*Store) DeleteMany ¶
DeleteMany removes keys. Missing keys are not an error. Empty input is a no-op.
func (*Store) DropBucket ¶
DropBucket deletes every object and then the bucket itself (test teardown; the harness's fresh bucket per test must not outlive the test).
func (*Store) EnsureBucket ¶
EnsureBucket creates the bucket if missing (dev/test convenience).
func (*Store) GetIfChanged ¶
GetIfChanged conditionally reads one revision. Empty etag is unconditional. An unchanged object returns nil bytes, its ETag, true, nil. A changed object returns its complete bytes and ETag with false. Absence is ErrNotFound; failures never report unchanged. ETags are opaque backend values.
func (*Store) GetRange ¶
GetRange reads exactly length bytes starting at offset. Short/out-of-bounds ranges fail instead of returning a plausible partial index block.
func (*Store) GetWithETag ¶
GetWithETag returns the object and its ETag for conditional replacement.
func (*Store) InstallNamespaceKey ¶
InstallNamespaceKey publishes the recovery envelope before the first encrypted object. The caller holds the namespace lifecycle lease.
func (*Store) ListPage ¶
func (s *Store) ListPage(ctx context.Context, prefix, after string, limit int) ([]string, string, error)
ListPage returns at most limit keys under prefix, lexically after after. next is the last returned key when another page remains, otherwise empty.
func (*Store) ListPrefixes ¶
ListPrefixes returns the immediate child prefixes under prefix — S3's CommonPrefixes with Delimiter "/", each ending in "/", lexically sorted. Discovering top-level names costs one request per page of names instead of one key per object in the bucket. A prefix appears only if at least one object lives under it, on both backends.
func (*Store) ListPrefixesPage ¶
func (*Store) PutIfAbsent ¶
PutIfAbsent writes key only if it does not already exist. Returns false when the key existed. This is the OCC primitive a write-ahead log builds on.
func (*Store) PutIfMatch ¶
PutIfMatch replaces key only if its current ETag equals etag — compare-and- swap, the manifest-swap primitive. (false, nil) = precondition failed: the object changed under us, or no longer exists.
func (*Store) RetireNamespaceKey ¶
RetireNamespaceKey leaves the deleted manifest head as a plaintext name-reuse fence, then removes its wrapped DEK. The lifecycle caller holds the lease. Repeating this after a crash between the CAS and delete is safe.
func (*Store) RotateNamespaceKey ¶
RotateNamespaceKey asks the provider to wrap the existing DEK with its active key version. The encrypted objects and plaintext DEK stay unchanged. It returns the current envelope so an interrupted metadata publication can be repaired on the next write.
func (*Store) SSE ¶
SSE reports the bucket's configured server-side encryption: the S3 mode ("", "AES256" or "aws:kms") and the KMS key id when one is set. The caller decides what it means for its users; the store does not interpret it.
func (*Store) SetCMEKRefreshInterval ¶
SetCMEKRefreshInterval ties AWS access and rotation probes to the lease heartbeat cadence. Set it once, before use.
type Timings ¶
type Timings struct {
// contains filtered or unexported fields
}
Timings sums the wall time the store spent in each Call for the calls made under one context (WithTimings), and how many there were: a background job can log its own. Concurrent calls add up, so the sum can pass the span's wall time. The file store fills every Call; S3 only the gate.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package kms wraps and unwraps data encryption keys under a customer key: LocalFile (32-byte keys in a directory), AWSKMS and GCPKMS, and a Router that picks one by the key name's scheme (local:, aws:arn:, gcp:projects/).
|
Package kms wraps and unwraps data encryption keys under a customer key: LocalFile (32-byte keys in a directory), AWSKMS and GCPKMS, and a Router that picks one by the key name's scheme (local:, aws:arn:, gcp:projects/). |
|
Package storetest provides test Stores: a MinIO- or file-backed bucket per test, and a fault-injecting, metering wrapper around one.
|
Package storetest provides test Stores: a MinIO- or file-backed bucket per test, and a fault-injecting, metering wrapper around one. |