upload

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

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

View Source
const (
	RoleModel        = "model"
	RoleInput        = "input"
	RoleExternalData = "external_data"
)

Manifest file roles (the server-side ManifestFile.role enum).

Variables

View Source
var ErrDuplicateFilename = errors.New("duplicate filename")

ErrDuplicateFilename reports two manifest files sharing a basename, which would collide in the server's canonical layout.

View Source
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).

View Source
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.

View Source
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

func CRC32CBase64(sum uint32) string

CRC32CBase64 renders a CRC32C sum in the GCS convention: base64 of the big-endian 4-byte value.

func CanonicalPathPreview

func CanonicalPathPreview(spec FileSpec) string

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

func RemoveState(sessionID string) error

RemoveState deletes the state file for sessionID; a missing file is not an error (removal is idempotent).

func StateDir

func StateDir() (string, error)

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

func ValidateSessionID(sessionID string) error

ValidateSessionID rejects identifiers that cannot be used as one opaque local state/lock name. Quoted errors keep control bytes inert.

Types

type BucketSpec

type BucketSpec struct {
	Index int   `json:"index"`
	Dims  []int `json:"dims"`
}

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

func DigestFile(path string) (Digest, error)

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

func BuildManifest(model string, inputs, external []string, note NoteFunc) ([]FileSpec, error)

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.

func LoadManifestDoc

func LoadManifestDoc(path string, note NoteFunc) ([]FileSpec, error)

LoadManifestDoc parses an --input-manifest document and digests its files. File order is preserved; inputs without an explicit input_index are numbered by their order of appearance among inputs.

type NoteFunc

type NoteFunc func(path string, size int64)

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.

func (*SessionLease) Close

func (l *SessionLease) Close() error

Close releases the session lock.

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.

func LoadState

func LoadState(sessionID string) (*State, error)

LoadState reads the state file for sessionID. A missing file surfaces as os.ErrNotExist and an unparsable one as ErrStateCorrupt, so callers can fall back to server-side session state in both cases.

func (*State) Save

func (s *State) Save() error

Save writes the state file atomically with 0600 permissions (dir 0700).

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

func (u *Uploader) StartSession(ctx context.Context, uploadURL string) (string, error)

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL