Documentation
¶
Overview ¶
Package rclonestore implements storage.Blobs by driving the external rclone binary as a context-bounded subprocess (argv exec — never a shell string, never librclone/cgo).
Index ¶
- type BinaryError
- type Options
- type OptionsError
- type PersistencePathError
- type ProbeError
- type PutSourceError
- type RcloneError
- type Store
- func (s *Store) Close() error
- func (b Store) Delete(ctx context.Context, key string) error
- func (b Store) Get(ctx context.Context, key string) (io.ReadCloser, error)
- func (b Store) List(ctx context.Context, prefix string) ([]string, error)
- func (b Store) Put(ctx context.Context, key string, r io.Reader) error
- func (b Store) StoragePaths() []string
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BinaryError ¶
type BinaryError struct {
Binary string
// contains filtered or unexported fields
}
BinaryError reports that the rclone binary could not be resolved on PATH. The binary name/path is operator-supplied configuration (no credential), so it is safe to name; the underlying exec.LookPath error is wrapped for errors.Is/As.
func (*BinaryError) Error ¶
func (e *BinaryError) Error() string
func (*BinaryError) Unwrap ¶
func (e *BinaryError) Unwrap() error
type Options ¶
type Options struct {
// Remote is required: a named rclone remote (e.g. "myremote") OR a ":backend:"
// connection string (e.g. ":local:/data", ":s3,provider=Minio:/bucket").
Remote string
// Prefix is an optional path fragment under the remote. If set it must be a
// safe relative path: no leading/trailing '/', no empty, "." or ".." segment.
Prefix string
// Binary is the rclone executable; default "rclone", resolved via exec.LookPath.
Binary string
// ConfigPath is an optional --config path. It is referenced by path only —
// never opened, parsed, copied, or logged here (it may hold remote secrets).
ConfigPath string
// Timeout optionally bounds every rclone invocation (including the startup
// probe). Zero means each call is bounded only by the caller's context.
Timeout time.Duration
// PersistencePaths declares local filesystem roots used by the backend. Inline
// :local: remotes are discovered automatically, so this is primarily for named
// remotes, which are never introspected. Entries are already-effective roots;
// Prefix is not appended to them.
PersistencePaths []string
}
Options configures a rclonestore Store. Only Remote is required. No field is ever logged or placed in an error verbatim: a connection-string Remote or a ConfigPath can embed remote credentials.
type OptionsError ¶
OptionsError reports an invalid field in Options. It names the Field and the Rule violated but never the offending value: Remote (a connection string) or a ConfigPath can embed credentials, so no option value is ever surfaced.
func (*OptionsError) Error ¶
func (e *OptionsError) Error() string
type PersistencePathError ¶
type PersistencePathError struct {
Path string
Rule string
// contains filtered or unexported fields
}
PersistencePathError reports an invalid local filesystem root declared by the caller or derived from an inline local remote. Path contains only the local filesystem portion, never a named remote or inline backend parameters.
func (*PersistencePathError) Error ¶
func (e *PersistencePathError) Error() string
func (*PersistencePathError) Unwrap ¶
func (e *PersistencePathError) Unwrap() error
type ProbeError ¶
type ProbeError struct {
// contains filtered or unexported fields
}
ProbeError reports that the startup reachability probe failed: rclone could not list the remote root and the failure was not a benign not-found. It wraps the underlying *RcloneError, which is credential-safe by construction (it excludes the config path, the remote, and every positional). The remote is deliberately NOT carried on ProbeError — a connection-string remote can embed secrets.
func (*ProbeError) Error ¶
func (e *ProbeError) Error() string
func (*ProbeError) Unwrap ¶
func (e *ProbeError) Unwrap() error
type PutSourceError ¶
type PutSourceError struct {
Key string
// contains filtered or unexported fields
}
PutSourceError reports that reading the caller-supplied Put reader failed before the blob could be compared against an existing object or streamed to rclone. It carries the storage key (safe to log — it is a validated canonical name) and wraps the underlying read error.
func (*PutSourceError) Error ¶
func (e *PutSourceError) Error() string
func (*PutSourceError) Unwrap ¶
func (e *PutSourceError) Unwrap() error
type RcloneError ¶
type RcloneError struct {
Subcommand string
Args []string
ExitCode int
Stderr string
// contains filtered or unexported fields
}
RcloneError reports a failed rclone invocation: a non-zero exit, a start failure, or a subprocess killed by its context deadline/cancellation. Callers classify with errors.As.
It deliberately carries only information that cannot embed a credential:
- Subcommand — the rclone subcommand (e.g. "rcat", "cat", "lsf", "deletefile").
- Args — the subcommand's own flags (the subflags). It NEVER contains the positional path arguments (which embed the remote name and the storage key) nor the global --config path (which points at a file that may hold remote credentials). The caller passes only non-secret subflags.
- ExitCode — the process exit status; -1 for a start failure or a signal kill.
- Stderr — a bounded tail (~4 KiB) of the process's stderr, surfaced as-is from rclone (whose diagnostics do not echo config secrets) and bounded so it cannot balloon an error or log line.
The config path, the remote, and every positional are excluded by construction, so an RcloneError is always safe to log.
func (*RcloneError) Error ¶
func (e *RcloneError) Error() string
Error renders the subcommand, the exit status, and a sanitized (quoted) stderr tail. The tail is strconv.Quote'd so newlines or control bytes emitted by the subprocess cannot inject into a log line. No credential-bearing value is ever included.
func (*RcloneError) Unwrap ¶
func (e *RcloneError) Unwrap() error
Unwrap returns the underlying cause so callers can classify with errors.Is / errors.As: the *exec.ExitError (or start error) on a genuine failure, or the context error (context.DeadlineExceeded / context.Canceled) when the call was killed by its deadline or cancellation.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a storage.Blobs backed by the external rclone binary. It embeds the unexported blobStore, so a Store IS-A storage.Blobs (Put/Get/Delete/List are promoted). Construct one with New.
func New ¶
New validates opts, resolves the rclone binary, and probes that the remote is reachable before returning a Store — failing loudly (fail-secure) at construction rather than at first use:
- Remote is validated as a named remote or a connection string (*OptionsError).
- Prefix, if set, is validated as a safe path fragment (*OptionsError).
- Binary (default "rclone") is resolved with exec.LookPath (*BinaryError).
- The remote root is probed with "rclone lsf --max-depth 0 -- <root>", bounded by opts.Timeout. A not-found root is treated as reachable-but-empty (a fresh store creates it on first Put); any other failure is a *ProbeError.
func (*Store) Close ¶
Close releases resources held by the Store. rclone is driven per-call as a short-lived subprocess and holds no persistent connection, so Close is a documented no-op that always returns nil; it exists only so callers can treat a Store uniformly with stores that do hold resources.
func (Store) Delete ¶
Delete removes the object at key via "rclone deletefile". Deleting an absent object is a success (idempotent): a not-found from rclone is classified and mapped to nil; every other failure propagates.
func (Store) Get ¶
Get streams the object at key back to the caller. It runs "rclone cat" to completion into an in-memory buffer and returns an independent io.ReadCloser over those bytes. Buffering (rather than piping rclone's stdout through) is what lets Get satisfy the contract's synchronous not-found: storetest expects Get itself — not a later Read — to return *storage.BlobNotFoundError, which is only knowable once rclone has exited. A missing object is classified from rclone's exit code/stderr and mapped to *storage.BlobNotFoundError.
func (Store) List ¶
List returns the storage keys of every object under the store's prefix whose key begins with the caller's prefix, lexicographically ascending and duplicate-free. It runs "rclone lsf --files-only -R" rooted at "<remote>:<prefix>": the emitted paths are relative to that root, so they ARE the storage keys. The caller's prefix is applied locally (it need not fall on a directory boundary) and is NOT name-validated. The result is sorted locally — rclone's ordering is not trusted. An empty store surfaces as a not-found on the root directory, which maps to an empty listing rather than an error.
func (Store) Put ¶
Put honors storage's content-addressed conflict contract. It first probes for an existing object:
- ABSENT (the common content-addressed case): stream r straight to "rclone rcat" with no buffering — the blob never lands in memory.
- PRESENT: read r fully, "rclone cat" the existing object, and compare bytes. Byte-identical → success/no-op (the object is NOT re-uploaded). Different → *storage.BlobConflictError with the original left untouched (no upload).
The present branch buffers both the incoming reader (io.ReadAll) and the existing object (into memory) to compare them. This is deliberately the rare path: keys are content-addressed, so a re-Put of DIFFERENT bytes under the same key is pathological, and a re-Put of IDENTICAL bytes is a cheap idempotent no-op. The probe→write sequence is a TOCTOU only under concurrent writers to the same key; the workspace store holds a single-writer lease over the key space, so no concurrent writer exists by construction.
func (Store) StoragePaths ¶
func (b Store) StoragePaths() []string
StoragePaths returns the canonical local filesystem roots used by this blob provider. Remote backends return nil. The result is a defensive copy so callers cannot mutate the provider's construction-time view.