api

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package api is the HTTP (REST) surface of the orchestrator. It wraps FleetManager and Environment operations behind a chi-based router, serves the harness, and is consumed by operations tooling. The shape of every request and response mirrors apps/orchestrator/api/openapi.yaml, which is the human-readable contract for this API.

Types in this file are hand-maintained and intentionally decoupled from the internal FleetManager/orchestrator types so that the wire contract can evolve independently of the in-process API. The conversion helpers in convert.go enforce that separation.

Index

Constants

View Source
const (
	CodeNotFound        = "not_found"
	CodeConflict        = "conflict"
	CodeInvalidArgument = "invalid_argument"
	CodeUnavailable     = "unavailable"
	CodeInternal        = "internal"
	CodeUnauthorized    = "unauthorized"
	CodeUnimplemented   = "unimplemented"

	// CodeRouteNotFound distinguishes "this URL isn't a route this server
	// exposes" (wrong host, wrong port, not the orchestrator at all) from
	// CodeNotFound's "route exists, resource doesn't" (e.g. an unknown vm
	// or host id).
	CodeRouteNotFound = "route_not_found"
)

Standard error codes. The REST boundary uses these to give callers a stable vocabulary for programmatic handling.

View Source
const RequestIDHeader = "X-Request-ID"

RequestIDHeader is the canonical HTTP header used to carry a per-request correlation ID. Clients (typically the control-server) may set this on inbound requests to propagate an upstream ID; otherwise the middleware generates a fresh one.

View Source
const SessionCookieName = "fuse_session"

SessionCookieName is the name of the HttpOnly cookie that carries the bearer token for browser callers. A separate SPA POSTs the operator's token to /login, which sets this cookie; subsequent API requests are authenticated from it by BearerAuth via [tokenFromRequest]. The token never lives in JavaScript-readable storage.

Variables

This section is empty.

Functions

func BearerAuth

func BearerAuth(expectedToken string, keys APIKeyAuthenticator, onFailure AuthFailureFunc) func(http.Handler) http.Handler

BearerAuth returns a chi-compatible middleware that authenticates the caller as either the static master token or a live API key, and records the resulting Principal in the request context. Returns 401 with the standard Error envelope on mismatch.

The token is read from the Authorization header ("Bearer <token>") for CLI/server callers, or — when no usable header is present — from the SessionCookieName cookie for browser callers (a separate SPA stores the token in an HttpOnly cookie via POST /login rather than in JavaScript). The same token is checked against the master secret first (constant-time), then against the API key store.

keys may be nil to disable API-key auth (only the master token is accepted). If expectedToken is empty AND keys is nil, the middleware is a no-op pass-through (insecure/dev mode, matching fused's Insecure flag pattern); requests carry no Principal.

func CIDRAllowlist

func CIDRAllowlist(cidrs []string, onReject IPRejectFunc) (func(http.Handler) http.Handler, error)

CIDRAllowlist returns a middleware that rejects requests whose remote address is not within any of the given CIDR blocks. Returns 403 with the standard Error envelope on rejection.

If cidrs is empty, the middleware is a no-op pass-through (open access). CIDRs are parsed at construction time; an invalid CIDR returns an error so the server fails fast at startup rather than silently admitting traffic.

func MetricsMiddleware

func MetricsMiddleware(
	requestsTotal *prometheus.CounterVec,
	requestDuration *prometheus.HistogramVec,
	requestsInFlight prometheus.Gauge,
) func(http.Handler) http.Handler

MetricsMiddleware records RED (Rate, Errors, Duration) metrics for every HTTP request using the chi route pattern as the "route" label, keeping cardinality bounded regardless of path parameters.

func RequestID

func RequestID(ctx context.Context) string

RequestID returns the per-request ID from ctx, or "" if absent. Use this in handlers and downstream callbacks (auth/IP audit) to correlate log lines and audit events with a single request.

func RequestIDMiddleware

func RequestIDMiddleware(next http.Handler) http.Handler

RequestIDMiddleware reads X-Request-ID from the inbound request (validating it against [validRequestID]) and either trusts it or generates a fresh ID. It then:

  1. Sets the ID on the response via RequestIDHeader before any downstream handler can write the response body.
  2. Stores the ID in the request context under the typed key, so downstream code can fetch it with RequestID.

This middleware should be mounted as the outermost layer of the orchestrator router so every other middleware (CIDR, auth, metrics) and every handler observes the same ID. Always succeeds — never short-circuits the chain.

Types

type APIKey

type APIKey struct {
	ID         string     `json:"id"`
	Label      string     `json:"label,omitempty"`
	CreatedAt  time.Time  `json:"created_at"`
	LastUsedAt *time.Time `json:"last_used_at,omitempty"`
	RevokedAt  *time.Time `json:"revoked_at,omitempty"`
}

APIKey is the JSON shape of a key's metadata. The raw secret is never included here — it appears only once, in CreateAPIKeyResponse.

type APIKeyAuthenticator

type APIKeyAuthenticator interface {
	// Authenticate returns (keyID, true) for a live, non-revoked key, or
	// ("", false) if the token matches no key or the key is revoked.
	Authenticate(ctx context.Context, rawToken string) (string, bool)
}

APIKeyAuthenticator validates a presented bearer token against the set of issued API keys. It is implemented by the orchestrator's API key store. A nil APIKeyAuthenticator disables key auth (only the master token is accepted).

type APIKeyList

type APIKeyList struct {
	APIKeys []APIKey `json:"api_keys"`
}

APIKeyList is the response body for GET /v1/api-keys.

type APIKeyStore

type APIKeyStore interface {
	Authenticate(ctx context.Context, rawToken string) (string, bool)
	Create(ctx context.Context, label string, now time.Time) (apikeys.APIKeyRecord, string, error)
	List(ctx context.Context) ([]apikeys.APIKeyRecord, error)
	Revoke(ctx context.Context, id string, now time.Time) error
}

APIKeyStore is the subset of the orchestrator's API key store that the REST handlers depend on. It is an interface so handlers can be tested with a stub and so the api package does not couple to *sql.DB. It also satisfies APIKeyAuthenticator (via Authenticate), so the same value wires into BearerAuth.

type AuthFailureFunc

type AuthFailureFunc func(remoteAddr, method, path, requestID string)

AuthFailureFunc is invoked once for every rejected authentication attempt. It receives the per-request correlation ID (see RequestIDMiddleware) so audit events and log lines can be tied back to the originating request, even when the rejection happens before the handler runs.

The requestID is always non-empty when RequestIDMiddleware is mounted upstream of this middleware (the standard router order).

type CreateAPIKeyRequest

type CreateAPIKeyRequest struct {
	Label string `json:"label,omitempty"`
}

CreateAPIKeyRequest is the JSON body accepted by POST /v1/api-keys. label is an optional human memory aid ("ci", "partner-acme").

type CreateAPIKeyResponse

type CreateAPIKeyResponse struct {
	APIKey
	Key string `json:"key"`
}

CreateAPIKeyResponse is returned by POST /v1/api-keys. Key is the raw secret and is shown exactly once — it cannot be recovered afterward.

type CreateEnvironmentRequest

type CreateEnvironmentRequest struct {
	TaskID         string            `json:"task_id"`
	Spec           ResourceSpec      `json:"spec"`
	ManifestInline string            `json:"manifest_inline"`
	Secrets        map[string]string `json:"secrets,omitempty"`
	StartupScript  string            `json:"startup_script,omitempty"`
	GatewayURL     string            `json:"gateway_url,omitempty"`
	GatewayToken   string            `json:"gateway_token,omitempty"`
	Expose         []ExposeSpec      `json:"expose,omitempty"`
}

CreateEnvironmentRequest is the JSON body accepted by POST /v1/environments.

ManifestInline is optional raw manifest JSON, base64-encoded. When omitted, the orchestrator uses a minimal internal manifest.

type CreateSnapshotRequest

type CreateSnapshotRequest struct {
	Comment          string            `json:"comment,omitempty"`
	Mode             string            `json:"mode,omitempty"`
	RetentionSeconds int64             `json:"retention_seconds,omitempty"`
	Metadata         map[string]string `json:"metadata,omitempty"`
	ExportRef        string            `json:"export_ref,omitempty"`
	ExportStatus     string            `json:"export_status,omitempty"`
}

CreateSnapshotRequest is the optional body for POST /v1/environments/{vm}/snapshots.

type Endpoint added in v0.2.0

type Endpoint struct {
	As   string `json:"as,omitempty"`
	URL  string `json:"url"`
	Port int    `json:"port"`
}

Endpoint is a published network endpoint for an environment.

type Environment

type Environment struct {
	ID        string       `json:"id"`
	State     string       `json:"state"`
	TaskID    string       `json:"task_id"`
	HostID    string       `json:"host_id,omitempty"`
	URL       string       `json:"url"`
	Spec      ResourceSpec `json:"spec"`
	CreatedAt time.Time    `json:"created_at"`
	UpdatedAt time.Time    `json:"updated_at"`
	Error     string       `json:"error,omitempty"`
	Endpoints []Endpoint   `json:"endpoints,omitempty"`
}

Environment is the JSON shape returned for a single VM.

type EnvironmentList

type EnvironmentList struct {
	Environments []Environment `json:"environments"`
}

EnvironmentList is the response body for GET /v1/environments.

type Error

type Error struct {
	Error ErrorBody `json:"error"`
}

Error is the JSON envelope returned for every non-2xx response. It intentionally wraps a single inner Error object so clients have one place to look for machine-readable failure metadata.

type ErrorBody

type ErrorBody struct {
	Code    string            `json:"code"`
	Message string            `json:"message"`
	Details map[string]string `json:"details,omitempty"`
}

ErrorBody is the inner payload of an Error envelope.

type ExecEnvironmentRequest added in v0.4.0

type ExecEnvironmentRequest struct {
	// Cmd is the argv to run in the guest, e.g. ["ls", "-l", "/var/log"].
	// Argv needs no quoting rules and cannot be turned into an injection by
	// interpolating a value, so it is the default.
	Cmd []string `json:"cmd,omitempty"`

	// Shell runs the string under `sh -lc`. It is the explicit opt-in for
	// what argv cannot express: pipelines, redirects, and globs.
	Shell string `json:"shell,omitempty"`

	// TimeoutMS bounds the command inside the guest. Zero takes the server
	// default; anything above the server ceiling is clamped to it.
	TimeoutMS int `json:"timeout_ms,omitempty"`
}

ExecEnvironmentRequest is the JSON body accepted by POST /v1/environments/{vmId}?action=exec.

Exactly one of cmd or shell must be set.

type ExecEnvironmentResponse added in v0.4.0

type ExecEnvironmentResponse struct {
	ExitCode int    `json:"exit_code"`
	Stdout   string `json:"stdout"`
	Stderr   string `json:"stderr"`
}

ExecEnvironmentResponse is the outcome of a guest command.

A non-zero exit_code arrives with HTTP 200: the command ran and failed, which is a successful answer to the question that was asked. Stdout and stderr are separate, and are plain strings rather than base64 because exec output is overwhelmingly text and every caller would otherwise pay a decoding tax for the rare binary case.

type ExposeSpec added in v0.2.0

type ExposeSpec struct {
	Port int    `json:"port"`
	As   string `json:"as,omitempty"`
}

ExposeSpec requests that a guest port be published as a reachable endpoint.

type ForkEnvironmentRequest added in v0.2.0

type ForkEnvironmentRequest struct {
	ReuseSnapshotID string `json:"reuse_snapshot_id,omitempty"`
	Comment         string `json:"comment,omitempty"`
}

ForkEnvironmentRequest is the optional body for POST /v1/environments/{vmId}?action=fork.

type Handler

type Handler struct {
	// Fleet is the FleetManager instance the handlers proxy to.
	// In production this is *orchestrator.FleetManager. Tests may
	// pass any implementation compatible with the Go method set.
	Fleet *orchestrator.FleetManager

	// Resolver turns inline manifest payloads into raw bytes.
	// Defaults to InlineResolver if left nil.
	Resolver Resolver

	// NewProvider constructs a Provider for a host URL. Required for
	// host registration (POST /v1/hosts). Nil means host registration
	// is disabled and returns 501.
	NewProvider ProviderFactory

	// AuthToken is the static Bearer token required for all endpoints.
	// Empty means no auth (insecure/dev mode).
	AuthToken string

	// Version is the orchestrator build version, stamped into the Server
	// response header on every request (e.g. "fuse-orchestrator/0.4.0").
	// Empty renders as "fuse-orchestrator/dev".
	Version string

	// APIKeys is the store of revocable API keys. When non-nil it is
	// consulted by BearerAuth as a second accept path (after the master
	// token) and backs the /v1/api-keys management endpoints. Nil
	// disables API-key auth entirely.
	APIKeys APIKeyStore

	// AllowedCIDRs is a list of CIDR blocks from which requests are
	// accepted. Empty means open access.
	AllowedCIDRs []string

	// SecureCookies sets the Secure attribute on the session cookie
	// issued by POST /login. The server wires this to whether it is
	// serving TLS; it must be true in any cross-site browser deployment
	// (SameSite=None requires Secure) and should be true in production.
	SecureCookies bool

	// OnAuthFailure is called on every rejected auth attempt. The
	// server wires this to the FleetManager's event append for audit.
	// The requestID argument is the per-request correlation ID set
	// by [RequestIDMiddleware]; it is non-empty for any request that
	// passed through the standard [Handler.Router] chain.
	OnAuthFailure AuthFailureFunc

	// OnIPReject is called when a request is rejected by the CIDR
	// allowlist. Wired the same way as OnAuthFailure.
	OnIPReject IPRejectFunc

	// Metrics holds Prometheus counter/histogram vecs for HTTP RED
	// metrics. When non-nil, a recording middleware is installed on
	// the router. The three fields correspond to the outputs of
	// orchestrator.NewPrometheusMetrics.
	MetricsRequestsTotal    *prometheus.CounterVec
	MetricsRequestDuration  *prometheus.HistogramVec
	MetricsRequestsInFlight prometheus.Gauge
}

Handler is the HTTP dependency graph for the orchestrator REST API.

func (*Handler) Router

func (h *Handler) Router() (http.Handler, error)

Router returns an http.Handler serving the orchestrator REST API. It is safe to mount at any path prefix; routes are registered with absolute paths so `chi.Mount` and `http.StripPrefix` both work.

Middleware order (outermost first):

  1. Request-ID — assigns/propagates X-Request-ID so all downstream middleware and audit callbacks share a correlation ID.
  2. Metrics — records RED metrics for every request, including those rejected by CIDR or auth.
  3. CIDR allowlist — rejects before auth so blocked IPs can't even probe for valid tokens.
  4. Bearer auth — rejects unauthenticated requests.
  5. Route handlers.

type Healthcheck

type Healthcheck struct {
	// Fleet is the FleetManager whose readiness we report. A nil
	// pointer is treated as "not initialized" and fails readiness.
	Fleet *orchestrator.FleetManager

	// Store is the durable state backend (Postgres in prod, in-memory
	// in tests). Readiness performs a cheap ListVMs against it.
	Store orchestrator.StateStore

	// CheckTimeout bounds each readiness dependency check. If zero,
	// defaults to defaultHealthCheckTimeout.
	CheckTimeout time.Duration

	// BuildVersion is the orchestrator build version reported by
	// /v1/version. Empty renders as "dev" (matching main.version's zero
	// value).
	BuildVersion string
}

Healthcheck wires /health, /ready, and /v1/version handlers.

/health: liveness probe. Always 200; no dependencies. /ready: readiness probe. 200 when state store + fleet are reachable.

503 with a JSON breakdown when any check fails.

/v1/version: identifies the process as the fuse orchestrator.

All three are plaintext / unauthenticated by design — k8s and ALB probes don't (and shouldn't) carry bearer tokens, and a CLI probing an unknown URL can't carry a confirmed token yet either. This handler is mounted on the outer mux in server/main.go alongside /metrics, outside the auth + CIDR middleware chain installed by Handler.Router.

func (*Healthcheck) Liveness

func (h *Healthcheck) Liveness(w http.ResponseWriter, _ *http.Request)

Liveness reports that the process is up and able to serve HTTP. It deliberately does no work and depends on nothing — kubelet uses this to decide whether to restart the pod.

func (*Healthcheck) Readiness

func (h *Healthcheck) Readiness(w http.ResponseWriter, r *http.Request)

Readiness reports whether the orchestrator can currently serve traffic. It checks:

  • state_store: a bounded ListVMs call. Failures here typically mean Postgres is unreachable or the connection pool is wedged.
  • fleet: that the FleetManager is non-nil. (We can't easily check "Started" without poking at unexported state; a nil manager is the realistic failure mode at boot.)

On any failure the response is 503 with a per-check breakdown so operators can see at a glance which dependency is unhappy.

func (*Healthcheck) Version added in v0.5.0

func (h *Healthcheck) Version(w http.ResponseWriter, _ *http.Request)

Version identifies this process as the fuse orchestrator, unauthenticated so a caller can tell it apart from a host agent (or nothing at all) before it has a valid token. `fuse connect` probes this before saving a context.

type HostCapacity

type HostCapacity struct {
	CPUs      int    `json:"cpus"`
	RamMB     int    `json:"ram_mb"`
	StorageGB int    `json:"storage_gb"`
	VMCount   int    `json:"vm_count"`
	GPUs      int    `json:"gpus,omitempty"`
	GPUKind   string `json:"gpu_kind,omitempty"`
}

HostCapacity is the wire shape of a host's resource envelope.

type HostInfo

type HostInfo struct {
	ID        string       `json:"id"`
	URL       string       `json:"url"`
	Region    string       `json:"region,omitempty"`
	Backend   string       `json:"backend,omitempty"`
	State     string       `json:"state"`
	Capacity  HostCapacity `json:"capacity"`
	Allocated HostCapacity `json:"allocated"`
	LastSeen  time.Time    `json:"last_seen"`
	CreatedAt time.Time    `json:"created_at"`
	UpdatedAt time.Time    `json:"updated_at"`

	// Warnings carries non-fatal notices from registration (e.g. a
	// declared capacity value that exceeds what was probed from the host
	// agent). Only ever populated on the POST /v1/hosts response.
	Warnings []string `json:"warnings,omitempty"`
}

HostInfo is the JSON shape returned for a single host.

type HostList

type HostList struct {
	Hosts []HostInfo `json:"hosts"`
}

HostList is the response body for GET /v1/hosts.

type IPRejectFunc

type IPRejectFunc func(remoteAddr, method, path, requestID string)

IPRejectFunc is invoked once for every request rejected by the CIDR allowlist. Same shape and contract as AuthFailureFunc.

type InlineResolver

type InlineResolver struct{}

InlineResolver decodes the ManifestInline field as standard base64. It rejects empty inputs and any leading/trailing whitespace via the stdlib decoder's strictness.

func (InlineResolver) Resolve

Resolve implements Resolver.

type Principal

type Principal struct {
	// Master is true when the request authenticated with the static
	// master token (ORCH_AUTH_TOKEN). When auth is disabled (empty
	// master token, insecure mode), requests are also treated as Master.
	Master bool
	// KeyID is the public id of the authenticating API key, empty for a
	// master-token request.
	KeyID string
}

Principal identifies how a request authenticated. It is stored in the request context by BearerAuth so handlers (and audit) can distinguish the master operator from an individual API key — e.g. to restrict key management to the master token.

func PrincipalFromContext

func PrincipalFromContext(ctx context.Context) (Principal, bool)

PrincipalFromContext returns the authenticated principal for the request. When auth is disabled (insecure mode) no principal is set; callers should treat a missing principal as the master operator, mirroring BearerAuth's open pass-through. The bool reports whether a principal was present.

type ProviderFactory

type ProviderFactory func(url, token string, backend orchestrator.HostBackend) orchestrator.Provider

ProviderFactory constructs a Provider for a registered host given its URL, auth token, and virtualization backend. The REST handler calls this during POST /v1/hosts to avoid importing provider-specific packages. In production a firecracker backend returns a firecracker.Provider and a qemu backend returns a qemu.Provider; tests pass a stub.

type RegisterHostRequest

type RegisterHostRequest struct {
	ID       string       `json:"id"`
	URL      string       `json:"url"`
	Token    string       `json:"token,omitempty"`
	Region   string       `json:"region,omitempty"`
	Backend  string       `json:"backend,omitempty"`
	Capacity HostCapacity `json:"capacity"`
}

RegisterHostRequest is the JSON body accepted by POST /v1/hosts.

Backend selects the host's virtualization backend ("firecracker" or "qemu"). Omitted or empty defaults to "firecracker". Only "qemu" hosts may register with Capacity.GPUs > 0.

type Resolver

type Resolver interface {
	Resolve(req CreateEnvironmentRequest) ([]byte, error)
}

Resolver turns a CreateEnvironmentRequest into raw manifest bytes. The default implementation understands inline base64 only; a future revision can plug in out-of-band ref resolution without touching handler code.

type ResourceSpec

type ResourceSpec struct {
	CPUs              int32  `json:"cpus,omitempty"`
	RamMB             int32  `json:"ram_mb,omitempty"`
	StorageGB         int32  `json:"storage_gb,omitempty"`
	Region            string `json:"region,omitempty"`
	MaxRuntimeSeconds int64  `json:"max_runtime_seconds,omitempty"`
	Image             string `json:"image,omitempty"`
	GPUs              int32  `json:"gpus,omitempty"`
	GPUKind           string `json:"gpu_kind,omitempty"`
}

ResourceSpec is the JSON shape of the hardware spec attached to an environment create request or response body.

Image names a base rootfs for the provider to boot the VM from (a name the firecracker host agent resolves against its own named-rootfs directory). Empty means the provider's default base.

type Snapshot

type Snapshot struct {
	ID               string           `json:"id"`
	VMID             string           `json:"vm_id"`
	TaskID           string           `json:"task_id,omitempty"`
	TenantID         string           `json:"tenant_id,omitempty"`
	ParentSnapshotID string           `json:"parent_snapshot_id,omitempty"`
	Mode             string           `json:"mode,omitempty"`
	State            string           `json:"state,omitempty"`
	Comment          string           `json:"comment,omitempty"`
	SizeBytes        int64            `json:"size_bytes,omitempty"`
	CreatedAt        time.Time        `json:"created_at"`
	UpdatedAt        time.Time        `json:"updated_at,omitempty"`
	RetentionUntil   *time.Time       `json:"retention_until,omitempty"`
	LastError        string           `json:"last_error,omitempty"`
	ExportRef        string           `json:"export_ref,omitempty"`
	Exports          []SnapshotExport `json:"exports,omitempty"`
}

Snapshot is the JSON shape of a persisted snapshot record.

type SnapshotExport

type SnapshotExport struct {
	Destination string    `json:"destination"`
	Status      string    `json:"status,omitempty"`
	RequestedAt time.Time `json:"requested_at,omitempty"`
	UpdatedAt   time.Time `json:"updated_at,omitempty"`
	LastError   string    `json:"last_error,omitempty"`
}

SnapshotExport is the JSON shape of an optional exported snapshot artifact.

type SnapshotList

type SnapshotList struct {
	Snapshots []Snapshot `json:"snapshots"`
}

SnapshotList is the response body for GET /v1/snapshots.

Jump to

Keyboard shortcuts

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