Documentation
¶
Overview ¶
Package upload implements the client half of the Melange ingestion v2 protocol: manifest digesting, persisted upload-session state, and the GCS resumable upload protocol over v4 signed URLs.
Index ¶
- Constants
- Variables
- func CRC32CBase64(sum uint32) string
- func CanonicalPathPreview(spec FileSpec) string
- func LoadManifestDocV2(path string, note NoteFunc) ([]FileSpec, []BucketSpec, error)
- func RemoveState(sessionID string) error
- func StateDir() (string, error)
- func ValidateSessionID(sessionID string) error
- type BucketSpec
- type Digest
- type FileSpec
- type NoteFunc
- type SessionLease
- type State
- type StateFile
- type Uploader
- func (u *Uploader) QueryOffset(ctx context.Context, sessionURI string, total int64) (offset int64, done bool, err error)
- func (u *Uploader) StartSession(ctx context.Context, uploadURL string) (string, error)
- func (u *Uploader) UploadFile(ctx context.Context, sessionURI, path string, total, from int64, ...) error
Constants ¶
const ( RoleModel = "model" RoleInput = "input" RoleExternalData = "external_data" )
Manifest file roles (the server-side ManifestFile.role enum).
Variables ¶
var ErrDuplicateFilename = errors.New("duplicate filename")
ErrDuplicateFilename reports two manifest files sharing a basename, which would collide in the server's canonical layout.
var ErrInvalidManifest = errors.New("invalid upload manifest")
ErrInvalidManifest marks a locally-detectable manifest contract violation. Commands map it to a usage error (exit 2); filesystem read failures remain operational errors (exit 1).
var ErrSessionExpired = errors.New("upload URL or resumable session expired")
ErrSessionExpired reports a non-retryable 4xx from GCS: the signed URL or resumable session is no longer valid. The caller should reissue the file's upload URL through the API and restart that file's session.
var ErrStateCorrupt = errors.New("state file corrupt")
ErrStateCorrupt reports a state file that exists but cannot be parsed. Callers may treat it as missing (after warning) and rebuild the session state from the server.
Functions ¶
func CRC32CBase64 ¶
CRC32CBase64 renders a CRC32C sum in the GCS convention: base64 of the big-endian 4-byte value.
func CanonicalPathPreview ¶
CanonicalPathPreview returns the relative destination the server will assign for spec, with the server-chosen tag as a "{tag}" placeholder: model files land at {tag}/<name>, external data at {tag}/data/<name>, and inputs at {tag}/inputs/<NN>_<name> (two-digit zero-padded input index).
func LoadManifestDocV2 ¶
func LoadManifestDocV2(path string, note NoteFunc) ([]FileSpec, []BucketSpec, error)
LoadManifestDocV2 also returns bucket options needed for the create body.
func RemoveState ¶
RemoveState deletes the state file for sessionID; a missing file is not an error (removal is idempotent).
func StateDir ¶
StateDir returns the platform-appropriate directory for upload state files (mirroring config.ConfigDir's pattern):
- Linux/macOS: ${XDG_STATE_HOME:-~/.local/state}/melange/uploads
- Windows: %LocalAppData%\melange\uploads
func ValidateSessionID ¶
ValidateSessionID rejects identifiers that cannot be used as one opaque local state/lock name. Quoted errors keep control bytes inert.
Types ¶
type BucketSpec ¶
BucketSpec is one bounded .pt2 input-shape declaration. Every bucket in a manifest carries the same complete set of input indexes.
type Digest ¶
type Digest struct {
Size int64
CRC32C string // base64 of the big-endian 4-byte sum (GCS convention)
SHA256 string // lowercase hex
}
Digest holds the size and content digests of a local file.
func DigestFile ¶
DigestFile streams path once, computing size, CRC32C, and SHA-256 in a single read pass.
type FileSpec ¶
type FileSpec struct {
ClientFileID string
Path string // local filesystem path
Role string
InputIndex int // -1 unless Role == RoleInput
BucketIndex *int // nil unless this is a bucketed input
Filename string
Size int64
CRC32C string
SHA256 string
}
FileSpec is one fully-digested manifest entry plus the local path it came from. Specs are ordered: model first, then inputs (InputIndex order), then external data; ClientFileID is "f<position>" in that order.
func BuildBucketedManifest ¶
func BuildBucketedManifest(model string, inputs, external []string, buckets []BucketSpec, note NoteFunc) ([]FileSpec, error)
BuildBucketedManifest maps --input files to buckets in declaration order, with an equal input arity per bucket, then emits stable bucket/index order.
func BuildManifest ¶
BuildManifest digests the model, input, and external-data files into ordered manifest entries: model first, then inputs in the given order (input_index = position), then external data.
type NoteFunc ¶
NoteFunc observes a file about to be digested (used for "hashing large file" progress messages). size is from os.Stat, before the read pass.
type SessionLease ¶
type SessionLease struct {
// contains filtered or unexported fields
}
SessionLease holds the cross-process lock for one upload session. Commands retain it across state reconciliation, transfer, completion, or cancellation.
func AcquireSession ¶
func AcquireSession(ctx context.Context, sessionID string) (*SessionLease, error)
AcquireSession serializes all local work for sessionID across processes.
type State ¶
type State struct {
SessionID string `json:"session_id"`
Repo string `json:"repo"` // ACCOUNT/NAME
Tag string `json:"tag"`
CreatedAt time.Time `json:"created_at"`
Files []*StateFile `json:"files"`
}
State is the persisted record of an in-flight upload session, stored as <StateDir>/<session-id>.json.
It contains resumable session URIs, which are bearer credentials: the file is written 0600 in a 0700 directory and its contents must never be printed or logged.
type StateFile ¶
type StateFile struct {
ClientFileID string `json:"client_file_id"`
LocalPath string `json:"local_path"`
CanonicalPath string `json:"canonical_path"`
UploadURL string `json:"upload_url,omitempty"` // signed resumable-start URL
SessionURI string `json:"session_uri,omitempty"` // resumable session URI (credential)
Size int64 `json:"size"`
CRC32C string `json:"crc32c"`
Offset int64 `json:"offset"`
Uploaded bool `json:"uploaded"`
}
StateFile tracks one manifest file's upload progress. Offset is a hint only — the GCS committed offset is authoritative on resume.
type Uploader ¶
type Uploader struct {
Client *http.Client
ChunkSize int64 // rounded up to a 256 KiB multiple; default 8 MiB
StallTimeout time.Duration // per-chunk inactivity budget; 0 = no limit
MaxRetries int // consecutive retryable failures tolerated per call (default 4)
// Sleep blocks between retries; nil = timer sleep. Injectable for tests.
Sleep func(ctx context.Context, d time.Duration) error
}
Uploader speaks the GCS resumable upload protocol against v4 signed resumable-start URLs.
It deliberately runs over a BARE http.Client: no Authorization header, no melange transport chain (auth/retry/debug) — GCS must never see the PAT, signed URLs carry their own credentials, and debug logging must never see signed URLs or session URIs. Upload-specific retry lives here instead: on retryable failures the committed offset is re-queried from the server and the transfer continues from there, never resending acknowledged bytes.
func (*Uploader) QueryOffset ¶
func (u *Uploader) QueryOffset(ctx context.Context, sessionURI string, total int64) (offset int64, done bool, err error)
QueryOffset asks GCS how many bytes of the session are committed: an empty PUT with `Content-Range: bytes */<total>`. done reports an already-complete upload (HTTP 200/201); otherwise offset is the next byte to send.
func (*Uploader) StartSession ¶
StartSession opens a resumable session: POST to the signed URL with `x-goog-resumable: start` and an empty body; the Location response header is the resumable session URI (a bearer credential — never log it).
func (*Uploader) UploadFile ¶
func (u *Uploader) UploadFile(ctx context.Context, sessionURI, path string, total, from int64, onCommit func(committed int64)) error
UploadFile PUTs path's bytes to sessionURI in ChunkSize chunks starting at offset `from`. On retryable failures (5xx, 408/429, connection errors, per-chunk stalls) it re-queries the committed offset and continues from there — server-acknowledged bytes are never resent. Non-retryable 4xx surfaces as ErrSessionExpired so the caller can reissue the URL.
onCommit, when non-nil, observes every server-committed offset (for progress display and state persistence).