137 lines
6.1 KiB
Go
137 lines
6.1 KiB
Go
// Package upload provides producer-facing helpers for submitting distributor
|
|
// source bundles to the HTTP upload API.
|
|
//
|
|
// The package is intended for Go producer applications that already create
|
|
// reports or other Markdown bundle contents and want to hand those bundles to a
|
|
// running distributor server. It builds on pkg/bundle for manifest generation,
|
|
// path validation, digest calculation, local bundle writing, and local bundle
|
|
// validation. It does not expose distributor internals, server configuration,
|
|
// storage backends, destination state, or publish behavior.
|
|
//
|
|
// # Client Construction
|
|
//
|
|
// NewClient creates a Client from ClientOptions. Endpoint is required and must
|
|
// be an http or https distributor server base URL without userinfo, query, or
|
|
// fragment. The client derives /v1/pipelines/<pipeline-id>/upload for
|
|
// submissions and /runs/<run-id> for status checks. Token is required and is
|
|
// sent as Authorization: Bearer <token>. Token values are redacted from errors
|
|
// produced by the client.
|
|
//
|
|
// HTTPClient is optional. When omitted, the package uses a client with a
|
|
// conservative timeout. Retry is optional; zero values select safe defaults.
|
|
// RetryOptions.MaxAttempts, BaseDelay, and MaxDelay must not be negative, and
|
|
// MaxDelay must be greater than or equal to BaseDelay.
|
|
//
|
|
// # Upload Workflows
|
|
//
|
|
// UploadBundle uploads an existing local source bundle root to the configured
|
|
// PipelineID. PipelineID is required and must match the server's slug-like
|
|
// pipeline id syntax. The root must contain manifest.json. By default,
|
|
// UploadBundle loads the manifest and validates the complete local bundle with
|
|
// pkg/bundle before making any HTTP request. The generated gzip-compressed tar
|
|
// archive contains manifest.json and exactly the manifest-listed files;
|
|
// unlisted files are not uploaded.
|
|
//
|
|
// UploadFiles is the convenience workflow for producer applications that have
|
|
// generated files but have not yet assembled a bundle directory. PipelineID is
|
|
// required and selects the configured distributor workflow. UploadFiles uses
|
|
// pkg/bundle to create a temporary complete bundle from explicit
|
|
// bundle.BundleFile values, validates it by default, archives it, uploads it,
|
|
// and removes temporary files when the call returns. UploadFiles does not write
|
|
// into producer source directories. A zero Created timestamp follows
|
|
// pkg/bundle defaulting behavior.
|
|
//
|
|
// The producer contract has four separate identifiers: the bearer token
|
|
// authenticates the client, PipelineID selects the distributor workflow, the
|
|
// source manifest ID identifies the logical artifact within that workflow, and
|
|
// IdempotencyKey identifies one producer run and retry group.
|
|
//
|
|
// Validation is enabled by default. Set DisableValidation when the application
|
|
// has already performed equivalent local validation and wants to skip the
|
|
// package's validation step. Validate and DisableValidation must not both be
|
|
// true.
|
|
//
|
|
// # Idempotency And Retry
|
|
//
|
|
// Every upload request includes Idempotency-Key. If UploadBundleOptions or
|
|
// UploadFilesOptions provides IdempotencyKey, the client validates and uses
|
|
// that value. Otherwise, it generates a random 128-bit lowercase hexadecimal
|
|
// key once for that upload operation and reuses it for all retries from that
|
|
// call.
|
|
//
|
|
// Generated idempotency keys are useful for retrying transient failures within
|
|
// a single process call. Producers that need cross-process retry safety should
|
|
// provide their own stable key, such as a key derived from the producer job id
|
|
// or report id. Valid keys are non-empty ASCII strings up to 128 bytes using
|
|
// letters, digits, '.', '_', '-', and ':'.
|
|
//
|
|
// The client retries only safe cases: 503 Service Unavailable, temporary
|
|
// network errors, and ambiguous mid-upload failures. Retries use the same
|
|
// idempotency key and replayable gzip archive body. The client does not retry
|
|
// 400, 401, 409, 413, 415, or any response after 202 Accepted. Context
|
|
// cancellation is honored before each attempt and while waiting between
|
|
// retries.
|
|
//
|
|
// # Results And Errors
|
|
//
|
|
// Result represents upload admission. A successful UploadBundle or UploadFiles
|
|
// call means the server accepted the upload and returned a run id; it does not
|
|
// mean the asynchronous distribution run has finished successfully.
|
|
//
|
|
// Status fetches the current server status for a run id and returns RunStatus.
|
|
// This is a separate polling helper; upload calls do not wait for publication
|
|
// completion.
|
|
//
|
|
// Non-2xx upload and status responses return *HTTPError when the server status
|
|
// can be represented as an HTTP failure. HTTPError includes the numeric status
|
|
// code, HTTP status string, response message, and server retryable flag when
|
|
// present. A 409 Conflict response is returned as *IdempotencyConflictError,
|
|
// which wraps HTTPError and can be detected with errors.As.
|
|
//
|
|
// Example: upload producer files with a stable idempotency key.
|
|
//
|
|
// ctx := context.Background()
|
|
// client, err := upload.NewClient(upload.ClientOptions{
|
|
// Endpoint: "https://distributor.example.com",
|
|
// Token: os.Getenv("DISTRIBUTOR_UPLOAD_TOKEN"),
|
|
// })
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
//
|
|
// result, err := client.UploadFiles(ctx, upload.UploadFilesOptions{
|
|
// PipelineID: "reports.daily",
|
|
// ID: "reports.daily.2026-06-06",
|
|
// IdempotencyKey: "reports.daily.2026-06-06",
|
|
// Files: []bundle.BundleFile{
|
|
// {SourcePath: "/tmp/report.md", Path: "report.md"},
|
|
// {SourcePath: "/tmp/summary.txt", Path: "summary.txt"},
|
|
// },
|
|
// })
|
|
// if err != nil {
|
|
// var conflict *upload.IdempotencyConflictError
|
|
// if errors.As(err, &conflict) {
|
|
// return fmt.Errorf("upload conflicts with an earlier different bundle: %w", err)
|
|
// }
|
|
// return err
|
|
// }
|
|
//
|
|
// status, err := client.Status(ctx, result.RunID)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// _ = status
|
|
//
|
|
// Example: upload an existing bundle root.
|
|
//
|
|
// result, err := client.UploadBundle(ctx, upload.UploadBundleOptions{
|
|
// PipelineID: "reports.daily",
|
|
// Root: "/var/lib/reports/daily-2026-06-06",
|
|
// IdempotencyKey: "reports.daily.2026-06-06",
|
|
// })
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// _ = result
|
|
package upload
|