Documentation
¶
Overview ¶
Package storage provides shithub's storage abstractions:
- ObjectStore: a small interface for S3-compatible object storage (works against MinIO in dev/test and DigitalOcean Spaces in prod).
- reposfs: filesystem-backed bare git repository helpers (sharded layout, atomic helpers, strict path validation).
- Quota: a placeholder type for disk-quota plumbing (no enforcement yet — wired up in later sprints).
Path validation is the security boundary: every entry point that takes owner/repo names from outside the package goes through RepoPath, which rejects unsafe inputs against a strict whitelist.
Index ¶
- Variables
- func WriteAtomic(path string, src io.Reader) error
- type ListOpts
- type ListResult
- type MemoryStore
- func (m *MemoryStore) Delete(_ context.Context, key string) error
- func (m *MemoryStore) Get(_ context.Context, key string) (io.ReadCloser, ObjectMeta, error)
- func (m *MemoryStore) List(_ context.Context, prefix string, opts ListOpts) (ListResult, error)
- func (m *MemoryStore) Put(_ context.Context, key string, body io.Reader, opts PutOpts) (PutResult, error)
- func (m *MemoryStore) SignedURL(_ context.Context, key string, ttl time.Duration, method string) (string, error)
- func (m *MemoryStore) Stat(_ context.Context, key string) (ObjectMeta, error)
- type ObjectMeta
- type ObjectStore
- type PutOpts
- type PutResult
- type Quota
- type RepoFS
- func (r *RepoFS) CloneBareShared(ctx context.Context, src, dst string) error
- func (r *RepoFS) Delete(path string) error
- func (r *RepoFS) Exists(path string) (bool, error)
- func (r *RepoFS) InitBare(ctx context.Context, path string) error
- func (r *RepoFS) Move(oldPath, newPath string) error
- func (r *RepoFS) RepoPath(owner, name string) (string, error)
- func (r *RepoFS) Root() string
- func (r *RepoFS) SetPreciousObjects(ctx context.Context, path string) error
- type S3Config
- type S3Store
- func (s *S3Store) Delete(ctx context.Context, key string) error
- func (s *S3Store) Get(ctx context.Context, key string) (io.ReadCloser, ObjectMeta, error)
- func (s *S3Store) List(ctx context.Context, prefix string, opts ListOpts) (ListResult, error)
- func (s *S3Store) Put(ctx context.Context, key string, body io.Reader, opts PutOpts) (PutResult, error)
- func (s *S3Store) SignedURL(ctx context.Context, key string, ttl time.Duration, method string) (string, error)
- func (s *S3Store) Stat(ctx context.Context, key string) (ObjectMeta, error)
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound is returned by Get/Stat for absent keys and by reposfs // helpers when a path doesn't exist. ErrNotFound = errors.New("storage: not found") // ErrPreconditionFailed is returned by Put when an If-None-Match check fails. ErrPreconditionFailed = errors.New("storage: precondition failed") // ErrInvalidPath is returned by RepoPath (and any helper that takes // owner/repo names) for inputs that violate the whitelist. ErrInvalidPath = errors.New("storage: invalid path") // ErrAlreadyExists is returned by Move and InitBare when the destination // is already populated. Lets callers distinguish a race from corruption. ErrAlreadyExists = errors.New("storage: already exists") // ErrEscapesRoot is returned by Delete and Move when a path resolves // outside the configured storage root. Hard fail — never silently ignore. ErrEscapesRoot = errors.New("storage: path escapes root") )
Functions ¶
func WriteAtomic ¶
WriteAtomic writes src to path via a tempfile in the same directory, fsyncs, and renames. A crash between write and rename leaves the temp file behind (callers may sweep these on startup) but never a partial file at path.
The temp file MUST live on the same mount as path so the rename is atomic — callers should not pass paths that cross mount points.
Types ¶
type ListOpts ¶
type ListOpts struct {
// ContinuationToken resumes pagination from a prior page.
ContinuationToken string
// MaxKeys caps the page size. Zero means backend default.
MaxKeys int
// Recursive, when false, treats "/" as a delimiter and surfaces common
// prefixes (folders) in ListResult.CommonPrefixes.
Recursive bool
}
ListOpts controls a List.
type ListResult ¶
type ListResult struct {
Objects []ObjectMeta
CommonPrefixes []string
NextContinuationToken string
IsTruncated bool
}
ListResult is one page of a List.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-process ObjectStore implementation. Used by tests. Honors the same If-None-Match semantics as the s3 backend.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore constructs an empty in-memory store.
func (*MemoryStore) Delete ¶
func (m *MemoryStore) Delete(_ context.Context, key string) error
Delete implements ObjectStore. Idempotent.
func (*MemoryStore) Get ¶
func (m *MemoryStore) Get(_ context.Context, key string) (io.ReadCloser, ObjectMeta, error)
Get implements ObjectStore.
func (*MemoryStore) List ¶
func (m *MemoryStore) List(_ context.Context, prefix string, opts ListOpts) (ListResult, error)
List implements ObjectStore. ContinuationToken is the last key returned in the previous page; results are sorted lexicographically.
func (*MemoryStore) Put ¶
func (m *MemoryStore) Put(_ context.Context, key string, body io.Reader, opts PutOpts) (PutResult, error)
Put implements ObjectStore.
func (*MemoryStore) SignedURL ¶
func (m *MemoryStore) SignedURL(_ context.Context, key string, ttl time.Duration, method string) (string, error)
SignedURL implements ObjectStore. Tests can rely on the prefix to distinguish memory-backed URLs from real ones.
func (*MemoryStore) Stat ¶
func (m *MemoryStore) Stat(_ context.Context, key string) (ObjectMeta, error)
Stat implements ObjectStore.
type ObjectMeta ¶
type ObjectMeta struct {
Key string
Size int64
ETag string
ContentType string
LastModified time.Time
}
ObjectMeta is the metadata returned by Get/Stat.
type ObjectStore ¶
type ObjectStore interface {
// Put writes body to key. Returns the resulting object's metadata
// (etag, size). Honors opts.IfNoneMatch (when "*", fails with
// ErrPreconditionFailed if the key already exists).
Put(ctx context.Context, key string, body io.Reader, opts PutOpts) (PutResult, error)
// Get returns a reader for the object at key. Caller must Close.
// Returns ErrNotFound when key is absent.
Get(ctx context.Context, key string) (io.ReadCloser, ObjectMeta, error)
// Stat returns metadata for key without fetching the body.
// Returns ErrNotFound when key is absent.
Stat(ctx context.Context, key string) (ObjectMeta, error)
// Delete removes key. Returns nil for a missing key (idempotent).
Delete(ctx context.Context, key string) error
// List enumerates objects under prefix. Pagination via opts.ContinuationToken.
List(ctx context.Context, prefix string, opts ListOpts) (ListResult, error)
// SignedURL returns a pre-signed URL for the given method ("GET" or
// "PUT") on key, valid for ttl. The URL grants direct browser/client
// access without exposing credentials — used for avatar/attachment
// uploads and large downloads in later sprints.
SignedURL(ctx context.Context, key string, ttl time.Duration, method string) (string, error)
}
ObjectStore is the abstract interface every storage backend implements. Implementations: s3 (any S3-compatible endpoint via minio-go) and memory (in-process map for tests).
type PutOpts ¶
type PutOpts struct {
ContentType string
// IfNoneMatch, when "*", causes Put to fail with ErrPreconditionFailed
// if the destination already exists. Other values are not supported.
IfNoneMatch string
// ContentLength, when > 0, is passed to the backend as a hint. When 0,
// the backend buffers / streams as needed.
ContentLength int64
}
PutOpts controls a Put.
type Quota ¶
type Quota struct {
Used int64 // bytes currently used
Limit int64 // bytes allowed (0 = unlimited)
}
Quota is the disk-usage budget for a user or org. Recorded in the DB under users.disk_quota_* and orgs.disk_quota_* (added when those tables exist). S04 wires the type only — enforcement lives in a future policy package called from the push pipeline (S14) and attachment uploads.
func (Quota) Available ¶
Available returns Limit - Used, clamped at zero. Returns -1 when the quota is unlimited.
func (Quota) WouldExceed ¶
WouldExceed reports whether writing additional bytes n would push past the limit. Always false for an unlimited quota.
type RepoFS ¶
type RepoFS struct {
// contains filtered or unexported fields
}
RepoFS owns the on-disk layout for bare git repositories. All callers that touch repo paths route through this type so the path-validation rules live in exactly one place.
func NewRepoFS ¶
NewRepoFS validates root (must be absolute, must exist, must be a directory) and returns the layer.
func (*RepoFS) CloneBareShared ¶
CloneBareShared clones src → dst as a bare repo with object alternates pointing back at src. Disk usage of the result is essentially refs + a small overhead; objects live in src's `objects/` until the fork is detached (S16 hard-delete cascade repacks each fork before removing the source).
Both paths must be contained in r.root and on the same volume — the same-volume requirement is what makes alternates safe (S04).
On success the dst directory exists with `git init --bare` shape plus an `objects/info/alternates` file pointing at src/objects. On failure the dst directory is removed so a retry sees a clean slate.
func (*RepoFS) InitBare ¶
InitBare creates a bare git repository at path. Default branch is "trunk" — there is no path through this package that creates a bare repo with a different initial branch.
The parent directory tree is created on demand. ErrAlreadyExists is returned if path is non-empty.
func (*RepoFS) Move ¶
Move atomically renames oldPath to newPath. Both must be under root. If newPath already exists, returns ErrAlreadyExists rather than overwriting (avoids silent corruption on concurrent moves).
func (*RepoFS) RepoPath ¶
RepoPath returns the absolute disk path for the bare repository at (owner, name). Validates inputs and guarantees the result is rooted at r.root. Both inputs are lowercased before path construction.
func (*RepoFS) SetPreciousObjects ¶
SetPreciousObjects marks a bare repo's objects as not-prunable. The canonical foot-gun for forks is source-repo `git gc` removing objects that forks reach via alternates; setting this on the source after a fork is created prevents that. Idempotent.
type S3Config ¶
type S3Config struct {
Endpoint string // host[:port], no scheme
Region string
AccessKeyID string
SecretAccessKey string
Bucket string
UseSSL bool
ForcePathStyle bool
}
S3Config configures the S3-compatible client. Mirrors config.S3StorageConfig.
type S3Store ¶
type S3Store struct {
// contains filtered or unexported fields
}
S3Store is an ObjectStore backed by any S3-compatible endpoint.
func NewS3Store ¶
NewS3Store constructs the client. The bucket must already exist; this constructor does not create it (deploy/dev scripts seed buckets).
func (*S3Store) Get ¶
func (s *S3Store) Get(ctx context.Context, key string) (io.ReadCloser, ObjectMeta, error)
Get implements ObjectStore.
func (*S3Store) Put ¶
func (s *S3Store) Put(ctx context.Context, key string, body io.Reader, opts PutOpts) (PutResult, error)
Put implements ObjectStore.