Help us shape the future of Go! Take the 2026 Go Developer Survey

httpapi

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: AGPL-3.0 Imports: 84 Imported by: 0

Documentation

Overview

Package httpapi is the REST surface: chi handlers and nothing else.

The API is the product (R-261). Handlers contain no business logic — it lives in a service layer under internal/core that both httpapi and mcp call. A capability that exists in one surface and not the other means someone put logic in a handler.

Index

Constants

View Source
const ACMEChallengePrefix = "/.well-known/acme-challenge/"

ACMEChallengePrefix is where a certificate authority asks for an HTTP-01 answer (RFC 8555 §8.3).

View Source
const AuthorizePath = "/admin/authorize"

AuthorizePath is the console's approval page, OAuth's authorization endpoint. Under /admin, which no app slug can take (consoleRoutes).

View Source
const IdempotencyHeader = "Idempotency-Key"

IdempotencyHeader is the canonical place to put a key.

View Source
const LoginPath = ReservedPrefix + "/login"

LoginPath is where the proxy sends someone who needs to sign in.

View Source
const MinPasscodeLength = 6

MinPasscodeLength is the shortest passcode accepted [P]. Short on purpose — a passcode is typed by people it was read out to — but not so short that the per-app limit below is all that stands in the way: four characters was within reach of anyone with enough addresses to spread the guesses over.

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

RequestIDHeader is returned on every response, including errors.

View Source
const ReservedPrefix = "/.pando"

ReservedPrefix is the one path that is Pando's on every hostname.

Its users are the sign-in page and the assets that page needs, reached from an app's hostname where every other path is the app's. Leading dot so it cannot collide with an app slug, which may not start with one.

View Source
const SSOBindCookie = "pando_sso_bind"

SSOBindCookie binds a redirect sign-in to the browser that started it (design 06 §3.2). In Pando's namespace, so the proxy strips it from every request an app receives (R-173).

View Source
const SecureSessionCookie = "__Host-" + SessionCookie

SecureSessionCookie is the session cookie on an install reached over HTTPS.

The __Host- prefix is a promise the browser keeps: a cookie by this name was set Secure, by this exact host, for path "/", with no Domain. So an app on a sibling subdomain cannot plant one for Pando's hostname to fix a visitor's session or sign them in as someone else — which a Domain cookie named pando_session could. Plain HTTP cannot use the prefix (it requires Secure), and keeps the plain name.

View Source
const SessionCookie = "pando_session"

SessionCookie carries ses_<id>.<secret> (design 02 §2.7), on an install reached over plain HTTP.

View Source
const VersionHeader = "Pando-Version"

VersionHeader carries the server's version to a signed-in caller (R-353).

Variables

This section is empty.

Functions

func AuditFunc

func AuditFunc(w interface {
	Write(ctx context.Context, e audit.Event) error
}) func(ctx context.Context, e audit.Event)

AuditFunc is how a core service that audits on its own records an event with the request ID the HTTP layer assigned, and logs rather than fails when the write does not land — the same rule as Server.audit.

func Authenticate

func Authenticate(a *Authenticator) func(http.Handler) http.Handler

Authenticate is middleware that attaches a principal to every request.

It never rejects: an unauthenticated request proceeds as anonymous and is rejected, or not, by the authorization check the handler performs. Rejecting here would create a second place where access is decided.

func Error

func Error(w http.ResponseWriter, r *http.Request, err error)

Error writes the error envelope (design 00 §3.2).

An error arriving without an envelope becomes INTERNAL and its detail is logged rather than returned: an unenveloped error is by definition one nobody wrote a user-facing message for, and guessing one would leak implementation detail into a response body.

func JSON

func JSON(w http.ResponseWriter, status int, body any)

JSON writes a successful response.

func Logger

func Logger(base *zap.Logger) func(http.Handler) http.Handler

Logger threads the request's logger through context, carrying request_id (design 00 §3.3). principal_id is added by the authentication middleware in phase 1, and app_id by handlers that resolve one.

func PrincipalFrom

func PrincipalFrom(ctx context.Context) authz.Principal

PrincipalFrom returns the request's principal, or anonymous.

func Reference

func Reference() reference.Document

Reference returns the assembled document, for the generator that writes `docs/` from it.

func RequestID

func RequestID(next http.Handler) http.Handler

RequestID stamps every request with an identifier and puts it in the context and the response. The error envelope carries it too, so a user reporting a failure hands over one string that finds the log line.

func RequestIDFrom

func RequestIDFrom(ctx context.Context) string

RequestIDFrom returns the request ID in ctx, or "".

func ReservedOrApp

func ReservedOrApp(pando, app http.Handler) http.Handler

ReservedOrApp serves Pando's reserved path, and everything else from app.

For the listeners that are not the front door: a port-mode app has a socket of its own whose every path belongs to that app (design 03 §4.2), so the router — and with it the sign-in page — is not in front of it. Without this the R-172 redirect lands back on the proxy and loops, which is the bug R-172 exists to fix, reintroduced one listener over.

Types

type ACMEChallenges

type ACMEChallenges interface {
	KeyAuthorization(ctx context.Context, token string) (string, bool, error)
}

ACMEChallenges answers HTTP-01 challenges for certificates Pando issues for the edge (R-169; notes-kubernetes-runtime-issue-72.md).

type AnonymousPolicy

type AnonymousPolicy interface {
	AllowsAnonymousGrant(ctx context.Context, withPasscode bool) error
	PublicSharing(ctx context.Context) (corepolicy.PublicSharing, error)
}

AnonymousPolicy gates sharing an app with everyone (R-076).

type AppHosts

type AppHosts interface {
	IsAppHostname(ctx context.Context, host string) (bool, error)
}

AppHosts answers whether a hostname belongs to an app.

Used only to decide whether the console or the proxy should handle a request (see consoleOrApp). It is not an authorization decision — the proxy still makes that — and a failure here falls back to the console, which is the safe direction: someone sees Pando instead of their app, rather than reaching an app without passing through the proxy.

type AuditArchives

type AuditArchives interface {
	List(ctx context.Context) ([]audit.ArchiveRecord, error)
	Open(ctx context.Context, archiveID string) (audit.ArchiveRecord, io.ReadCloser, error)
}

AuditArchives lists and opens audit archives.

type AuditReader

type AuditReader interface {
	List(ctx context.Context, q audit.Query) ([]audit.Record, error)
}

AuditReader queries the audit log.

type AuditSinks

type AuditSinks interface {
	Statuses(ctx context.Context) ([]auditstream.Status, error)
}

AuditSinks reports where each audit sink has got to (R-383).

type AuditStream

type AuditStream interface {
	ResolveCursor(ctx context.Context, s string) (audit.Cursor, error)
	Wait(ctx context.Context, q audit.StreamQuery, wait time.Duration, c clock.Clock) (audit.StreamPage, error)
	Export(ctx context.Context, q audit.StreamQuery, enc audit.Encoder, write func([]byte) error) (int, error)
	Oldest(ctx context.Context) (time.Time, bool, error)
}

AuditStream reads the audit log in commit order (R-381, R-387).

type Authenticator

type Authenticator struct {
	Sessions *state.Sessions
	Tokens   *state.Tokens
	Users    *state.Users
	Groups   interface {
		GroupsForUser(ctx context.Context, userID string) ([]string, error)
	}
	// Cookies names the session cookie this install sets, which is the only
	// one read back (cookie.go).
	Cookies CookieScheme

	// TwoFactor says which sessions must set up a second factor before they
	// are anything more (R-405). Nil holds none back.
	TwoFactor interface {
		SetupPending(ctx context.Context, user state.User) (bool, error)
	}
}

Authenticator resolves credentials to a principal. Steps 1–3 of the evaluation order; steps 4–7 belong to the authorizer.

func (*Authenticator) Authenticate

func (a *Authenticator) Authenticate(r *http.Request) (authz.Principal, error)

Authenticate resolves a request to a principal, or to anonymous.

Anonymous is a principal, not an absence: R-075's anonymous grant is checked on the same path as any other, and there is no branch that skips the check.

func (*Authenticator) SessionID added in v0.8.0

func (a *Authenticator) SessionID(r *http.Request) (string, error)

SessionID is the request's session cookie value, ses_<id>.<secret>, or "" for none. It is a credential: never log or audit it. The proxy's cache reads it too, so both resolve the same cookie (cookie.go).

func (*Authenticator) SessionPrincipal

func (a *Authenticator) SessionPrincipal(ctx context.Context, cookie string) (authz.Principal, time.Time, error)

SessionPrincipal resolves a session cookie to its principal, and says when the session expires so a caller that keeps the answer keeps it no longer (the proxy's cache, issue #93). The session, its account and the account's groups are one query (Sessions.ActiveWithUser); groups are still read live on every call (R-079).

type CookieScheme added in v0.8.0

type CookieScheme struct {
	// ExternalURL is how a browser reaches this installation (secureCookie).
	ExternalURL *url.URL
}

CookieScheme decides how the session cookie is set and which one is read.

One place, so the name a cookie is set under and the name it is read back by cannot disagree. The zero value trusts the request, which is right when Pando serves TLS itself and on a localhost install.

func (CookieScheme) Name added in v0.8.0

func (c CookieScheme) Name(r *http.Request) string

Name is the session cookie's name for this request: the __Host- one when it is Secure.

func (CookieScheme) Secure added in v0.8.0

func (c CookieScheme) Secure(r *http.Request) bool

Secure reports whether the session cookie is marked Secure (O-19).

The request alone cannot answer this. Behind a TLS-terminating reverse proxy — the topology SECURITY.md describes — r.TLS is nil on every request even though the browser's connection is encrypted, so trusting it alone ships the session cookie without Secure and one plaintext request to the hostname puts it on the wire.

So the operator states the scheme, in PANDO_SERVER_EXTERNAL_URL, and Pando believes the operator rather than the request. Not X-Forwarded-Proto: any client that can reach Pando directly can set it, which is the same reason R-053 strips inbound X-Pando-* headers unconditionally.

Unset falls back to the request, which is correct in both topologies where the request does tell the truth — Pando terminating its own TLS, and the plain-HTTP localhost install the README documents, where an unconditional Secure would stop sign-in working.

func (CookieScheme) SessionID added in v0.8.0

func (c CookieScheme) SessionID(r *http.Request) (string, error)

SessionID reads the session cookie's whole value — ID and secret, a credential — by the name this install sets it under, and only that name: on an HTTPS install a plain pando_session is one that Pando did not set, whoever did.

More than one is refused rather than choosing between them. A browser sends every cookie that matches the request, so two means someone else set one — a sibling subdomain or another port on the same host — and whichever Pando picked might be theirs.

type DeployLogOwner

type DeployLogOwner func(ctx context.Context, deploymentID string) (string, error)

DeployLogOwner says where a deploy's live log is: the base URL of the replica running it, or "" when that is this replica or no live replica holds it (issue #72). Nil with one replica.

type DetectionQueue

type DetectionQueue interface {
	Enqueue(ctx context.Context, appID string) (state.Detection, error)
}

DetectionQueue queues a detection to run on whichever replica has room (detection.Queue). Enqueue returns the detection as queued, read before any replica can claim it: a detection that fails at once would otherwise finish between queueing it and reading it back, and a 202 would report the outcome of work it says has only begun.

type Detector

type Detector interface {
	Detect(ctx context.Context, appID string) (state.Detection, error)

	// Check refuses what Detect would refuse before it starts, without
	// writing anything.
	Check(ctx context.Context, appID string) error

	// Revise changes a finished proposal as a person asked, through the AI
	// adapter, and records the exchange on it (R-336).
	Revise(ctx context.Context, appID, message string) (state.Detection, error)

	// Rebuild builds the proposal's image again, in the background, when
	// what it would build has changed — an answer the build waited on — so
	// the plan shows the score of what will run (R-311). True when it
	// started.
	Rebuild(ctx context.Context, appID string) (bool, error)
}

Detector runs detection for an app.

An interface rather than the concrete job, so the HTTP layer does not have to know how source is fetched or which adapters are involved — and so a test can assert what the endpoints do without a network, a builder or a daemon.

type FailedAttempts

type FailedAttempts interface {
	Reserve(ctx context.Context, limits ...state.AttemptLimit) (state.Reservation, error)
	Fail(ctx context.Context, res state.Reservation) error
	Release(ctx context.Context, res state.Reservation) error
	Clear(ctx context.Context, key string) error
}

FailedAttempts counts wrong guesses at a passcode (R-075a), a password (R-404) or a two-factor code (R-405), by a key that says which and whose.

Shared by every replica in production (state.FailedAttempts): a count kept in one process's memory gave an attacker one allowance per replica behind the load balancer, and a fresh one whenever a replica restarted (issue #72).

An attempt is counted as a pending failure before it is checked, atomically with reading the count; a wrong guess settles it as a failure and a right one releases it. Read first and recorded after, a burst of concurrent attempts all saw the same count and every one was checked. A limit reached only by pending attempts waits for them rather than refusing, so concurrent right guesses are not refused because of each other.

type Health

type Health interface {
	Ping(ctx context.Context) error
}

Health reports whether a dependency is reachable.

type InstallVerbs

type InstallVerbs interface {
	InstallVerbsFor(ctx context.Context, p authz.Principal) ([]string, error)
}

InstallVerbs reports the installation-wide verbs a principal holds.

Read by GET /me so the console can decide what to offer (R-265) from the server's answer rather than by inferring administration from some other response. Inference is what it did before install verbs existed — it treated a non-empty GET /apps as "this person is an administrator" — and that is a second opinion about authorization living in a client (R-261).

type PolicyDocument

type PolicyDocument interface {
	Load(ctx context.Context) (corepolicy.Document, error)
	Save(ctx context.Context, doc corepolicy.Document, updatedBy string) error
}

PolicyDocument reads and writes host policy.

type RegistryCredentials

type RegistryCredentials interface {
	SaveCredential(ctx context.Context, appID string, c oci.Credential) error
	RemoveCredential(ctx context.Context, appID string) error
	DescribeCredential(ctx context.Context, appID string) (oci.CredentialSummary, bool, error)
}

RegistryCredentials keeps the credential an image app's image is pulled with (oci.Images).

type Security

type Security interface {
	Report(ctx context.Context, appID, specID string) (security.Report, error)
	Scan(ctx context.Context, req api.ScanRequest, principal audit.Event) (state.Scan, error)
	Place(ctx context.Context, scores map[string]security.Scores) (map[string]security.Placed, error)
}

Security is what the API needs from the security service. Three calls, so a handler cannot reach past them into policy or the scan store.

type Server

type Server struct {
	Logger *zap.Logger
	DB     Health

	Identity api.IdentityAdapter
	Users    *state.Users

	// IDP is external identity: providers, redirect sign-in, linking and
	// SCIM (issue #51). Nil leaves password sign-in the only way in.
	IDP      *idp.Service
	Sessions *state.Sessions

	// TwoFactor is local accounts' second factor (R-405). Nil leaves the
	// password the only factor, and the two-factor routes say so.
	TwoFactor *twofactor.Service
	Tokens    *state.Tokens

	// Hosts is the install's cluster: its hosts and join tokens (R-414,
	// R-415). Nil leaves the cluster routes answering that it is not set up.
	Hosts *hosts.Service

	// HostLoss marks a host lost and starts its apps' standbys (R-421). Nil
	// leaves the route answering that the cluster is not set up.
	HostLoss *liveness.Service
	// Joiner joins this Pando to a cluster at setup (POST /setup/join, R-415).
	// Nil answers that joining is not set up.
	Joiner *hosts.Joiner

	// CLISignIn signs the CLI in through the browser (R-406). Nil leaves
	// pando login --username, with a password, the only way.
	CLISignIn *clisignin.Service

	// OAuth signs MCP clients in (R-413): registration, the console's
	// approval, the token endpoint. Nil leaves the MCP endpoint to clients
	// given a token by hand.
	OAuth   *oauth.Service
	Apps    *state.Apps
	Volumes *state.Volumes
	Authz   *authz.Authorizer
	Auditor *audit.Writer
	Authent *Authenticator

	Registry *api.Registry
	Adapters *state.Adapters

	// Observations shares the runtime's answers for /status and /usage between
	// requests asking at the same moment (issue #72). Authorization is checked
	// before it is consulted, every request. Nil asks the runtime every time.
	Observations *observe.Cache

	// ServiceInstances says which dependency a service Pando runs for an app
	// fills, so /status names a provisioned workload by what it is rather
	// than by its handle. Nil leaves that out.
	ServiceInstances observe.ServiceInstances

	// Address resolves changes to where an app is reached (R-162, R-163).
	// Nil means the address cannot be changed after configuration.
	Address *address.Service

	// Edges reports whether each routing adapter's edge is running (R-174).
	// Nil means no adapter's edge is reported.
	Edges *edge.Service

	// AIFunctions is which AI adapter handles each AI function (R-259). Nil
	// means the endpoints say assignment is not set up.
	AIFunctions *assist.Assignments

	// Assist runs the administrative AI functions. Nil means the endpoints
	// say AI assistance is not set up.
	Assist *assist.Service

	// TeardownNow asks for a deleted app's bundle to be torn down now rather
	// than at the collector's next pass. Nil leaves it to the pass.
	TeardownNow func()

	// AdapterKinds are the kinds of adapter this build can run, with the
	// settings each takes (api.KindInfo), for GET /adapters/kinds.
	AdapterKinds []api.KindInfo

	// AdapterCredentials holds adapters' credentials encrypted (O-20). Written
	// by POST /adapters, never read back by any handler.
	AdapterCredentials *state.AdapterCredentials

	// Reconciles is the loop's bookkeeping. The lifecycle handlers touch it
	// for one reason: a person starting an app Pando gave up on is the human
	// intervention R-151 requires, and the failure count has to be cleared for
	// the loop to look at the app again.
	Reconciles  *state.Reconciles
	Planner     *planner.Planner
	Allocations *state.Allocations
	Deployments *state.Deployments

	// Capacity is GET /capacity's runtime readings, refreshed in the
	// background (issue #72).
	Capacity *capacity.Snapshots

	// Security scores apps and answers where one stands (R-310). Nil on an
	// installation with no scanner, where the endpoints say so rather than
	// returning a zero.
	Security Security
	Deployer *deploy.Runner

	// Approvals starts every deploy, and asks for, records and acts on the
	// approval of those that need one (R-154 – R-159).
	Approvals *approval.Service

	// AutoDeploy reads and changes automatic deploy settings and takes the
	// webhooks git hosts send (R-141, R-142).
	AutoDeploy *autodeploy.Service

	// Subscriptions manages event subscriptions and notification
	// preferences (issue #50). Nil answers that they are unavailable.
	Subscriptions *subscription.Service

	// Notices is what an app's Overview asks of somebody, and each person's
	// ignores of them (R-412). Nil answers that notices are unavailable.
	Notices *notice.Service

	// Inbox is the console notifications a person reads (R-377).
	Inbox *subscription.Inbox

	// Notifier sends Pando's own notifications to the people they name, as
	// their preferences allow. Optional.
	Notifier interface {
		Notify(ctx context.Context, n api.Notification) error
	}

	Logs *deploy.LogStore

	// LogStreams shares one live runtime log stream per app part between
	// everyone on this replica watching it (O-51). Nil makes the following
	// endpoints answer that live logs are not available.
	LogStreams *logstream.Hub
	// DeployLogReauthEvery is how often a connected deploy-log stream checks
	// the viewer's access again (R-048). logstream.DefaultReauthEvery when
	// zero; tests shorten it.
	DeployLogReauthEvery time.Duration

	// QueuePollEvery is how often a queued deploy's log stream reads its
	// place in the queue (issue #93). A second when zero.
	QueuePollEvery time.Duration

	// RelayTransport carries a deploy-log request to the replica holding the
	// log. http.DefaultTransport when nil.
	RelayTransport http.RoundTripper
	// LogOwner finds the replica running a deploy, so its live log can be
	// read from any replica (issue #72). Nil with one replica.
	LogOwner DeployLogOwner
	// FailedAttempts is the shared count behind the limits on guessing a
	// passcode (R-075a) and a password (R-404). In memory, for one process,
	// when nil.
	FailedAttempts FailedAttempts

	Secrets    *state.Secrets
	Detections *state.Detections

	// Clock times a detection wait and how long one has been running. Nil is
	// the system clock.
	Clock clock.Clock

	// Detector runs detection for an app. Nil on an install with no builder or
	// runtime configured, in which case the detection endpoints say so rather
	// than returning an empty proposal — R-106's shape: with nothing
	// configured, each step degrades to a question, not a dead end.
	Detector Detector

	// DetectionQueue is where a detection the API starts is queued (issue
	// #72, O-32). Nil, with a Detector, means detection is not started on
	// create and a re-run is refused as not configured.
	DetectionQueue DetectionQueue

	// Sources keeps an uploaded source (R-262) and fetches an app's source for
	// a scan on request.
	Sources source.Sources

	// SourceConnections is the install's source connections (R-091, issue
	// #127): which one a new app's repository is read with, and the
	// repositories each can list. Nil reads every repository anonymously and
	// answers the /sources routes with an error.
	SourceConnections *sourceconn.Service

	// NotifyConnections is the install's notification adapters, built from
	// their rows on each use: connecting Slack, and the channels a
	// subscription to it can choose. Nil answers the /notify routes with an
	// error.
	NotifyConnections *notifyconn.Service

	// Images keeps the credential an image app's image is pulled with (issue
	// #41). Nil on an install that cannot store one, where setting one says so.
	Images RegistryCredentials

	// ImageRegistries builds the install's image registry adapters (R-252,
	// issue #153), which are configured with POST /adapters like any adapter
	// but built from their rows each time they are used, so saving one needs
	// no restart. Nil checks nothing before saving one.
	ImageRegistries *imageregistry.Service

	// Defaults fills in what an author left out of a spec — the install's
	// adapters, its routing shape, and the retention caps R-211 and R-223 set.
	// Nil means a hand-written spec is taken exactly as written, which is how
	// it behaved before and is wrong: an app with no log cap is an app that can
	// fill the host.
	Defaults SpecDefaults

	// Policy is host policy. Nil until phase 3 configures it, in which case the
	// source allowlist check (R-092) is skipped rather than assumed to pass —
	// the call site says so explicitly.
	Policy SourcePolicy

	// ExternalURL is how a browser reaches this installation, when something
	// other than Pando terminates TLS. It decides one thing: whether the
	// session cookie is marked Secure (O-19). Nil falls back to the request,
	// which is right when Pando serves TLS itself and on a localhost install.
	ExternalURL *url.URL

	// Minter publishes the assertion signing keys at /.well-known/jwks.json.
	Minter     *assertion.Minter
	Grants     *state.Grants
	HostPolicy AnonymousPolicy

	// Verbs answers "what may this principal do install-wide" for GET /me.
	// Nil leaves the list empty, which denies nothing — every install-level
	// endpoint checks the verb itself — but it does hide the admin entry.
	Verbs InstallVerbs

	// PolicyStore reads and writes the host policy document (R-274). Distinct
	// from HostPolicy above, which *evaluates* it: one is the document, the
	// other is the decision, and an endpoint that edits the document has no
	// business asking the evaluator anything.
	PolicyStore PolicyDocument

	// PolicyOverlay is the host policy fields set in the startup config
	// (R-271). PolicyStore already reads through it; this is for refusing a
	// change to one of them and for saying where each was set. Nil means none.
	PolicyOverlay *corepolicy.Overlay

	// StartedAt is when this process started. An adapter configured after it
	// is saved but not running, since adapters are loaded at startup (R-253).
	StartedAt time.Time

	// Restart asks the server to shut down cleanly and start again, loading
	// the adapters and the configuration file afresh. It returns at once; the
	// restart follows the response. Nil means this process cannot restart
	// itself, and POST /restart says so.
	Restart func()

	// Startup is the configuration Pando started with, for GET /config: every
	// non-secret setting and where it came from. Nil in tests that do not set it.
	Startup *config.Config

	// AuditLog reads the append-only log (R-227). Nil on an install where the
	// endpoint should 500 rather than quietly return nothing — an empty audit
	// log and an unreadable one are very different answers.
	AuditLog AuditReader

	// AuditArchives lists and serves the months retention has archived
	// (R-347). Nil answers an empty list: an install with nothing archived
	// and one that cannot archive look the same from here, and both are true.
	AuditArchives AuditArchives

	// AuditStream reads the log in commit order, for the stream and exports
	// (R-381, R-387). AuditEncoders turn native lines into other formats, by
	// name — "ocsf" (R-384). AuditSinks reports each audit sink's delivery
	// (R-383). AuditReads decides when a stream read is itself audited
	// (R-388).
	AuditStream   AuditStream
	AuditEncoders map[string]audit.Encoder
	AuditSinks    AuditSinks
	AuditReads    *audit.ReadThrottle

	// AuditSinkCheck configures an audit sink as it would be saved, so one
	// its adapter refuses is refused by POST /adapters (R-382).
	AuditSinkCheck interface {
		Validate(ctx context.Context, id, kind string, config json.RawMessage, given map[string]secret.Value) error
	}

	// Updates is whether a newer Pando is released (R-351). Nil answers that
	// the check is not running, which is true.
	Updates Updates

	// Upgrades is the in-place upgrade (R-355 – R-362). Nil answers that it
	// is not set up.
	Upgrades Upgrades

	// Version is this binary's version, sent to signed-in callers as
	// Pando-Version so a CLI can tell it is out of step (R-353). Never to an
	// anonymous one: a version is a map of which advisories apply.
	Version string

	// Backups records what has been backed up; Backup does the backing up
	// (R-212). Two fields because they are two concerns: the record has to
	// survive the thing it records, which is R-204's whole point.
	Backups *state.Backups
	Backup  *backup.Service

	// Groups and Roles back the identity endpoints (R-078, R-082).
	Groups *state.Groups
	Roles  *state.Roles

	// AppLimits decides how many apps a person may own, and AppLimitStore
	// holds the values set on users and groups (R-244). Nil AppLimits is no
	// limit.
	AppLimits     *applimit.Service
	AppLimitStore *state.AppLimits

	// IdleSettings reads and changes an app's idle settings (R-397).
	IdleSettings *idle.Settings

	// BundleSource supplies what goes into a backup. Held separately from
	// Backups because one records what was taken and the other reads what is
	// being taken — and the record has to outlive the thing (R-204).
	BundleSource *state.BundleSource

	// Idempotency deduplicates retried infrastructure-creating requests. Nil
	// disables replay rather than failing: the cost is a possible duplicate,
	// and refusing to deploy because a deduplication table is missing would be
	// a worse outcome than deploying twice.
	Idempotency *state.Idempotency

	// Console serves the embedded UI on Pando's own paths. Nil when the binary
	// was built without it, in which case those paths 404 like any other and
	// the API is unaffected — the API is the product (R-261), and the console
	// is one of its clients.
	Console http.Handler

	// AppHosts tells the console handler when a request belongs to an app
	// instead. Nil means the console answers on every hostname, which is
	// correct for a path-addressed install and wrong for a subdomain one.
	AppHosts AppHosts

	// PandoHostnames lists the hostnames Pando serves itself on besides the
	// external URL's: a routing adapter's console hostname. With ExternalURL
	// set, a hostname that is neither one of these nor an app's gets no
	// console and no API (unknownHost). Nil means the external URL's alone.
	PandoHostnames func(ctx context.Context) []string

	// ACMEChallenges answers HTTP-01 challenges for the certificates Pando
	// issues for the edge, on every hostname. Nil answers none.
	ACMEChallenges ACMEChallenges

	// AppProxy serves every request to every app (R-023). Mounted last, as the
	// catch-all, so Pando's own routes are reachable and everything else goes
	// through enforcement. There is no path that reaches an app without it.
	AppProxy http.Handler

	// ClientAddr decides which address each request came from for the audit
	// log (R-380). The zero value records the connecting peer.
	ClientAddr clientaddr.Trusted
	// contains filtered or unexported fields
}

Server holds what the HTTP surface needs.

Handlers contain no business logic (R-261). Everything lives in a service layer under internal/core that both this package and internal/mcp call — a capability in one surface and not the other means logic leaked into a handler.

func (*Server) ClusterRoutes added in v0.17.0

func (s *Server) ClusterRoutes() http.Handler

ClusterRoutes is the cluster listener (server.cluster_addr), served over TLS from the cluster authority. It is not under /api/v1 and needs no session: a joining host has nothing but its token.

func (*Server) Routes

func (s *Server) Routes() http.Handler

Routes builds the router.

type SourcePolicy

type SourcePolicy interface {
	AllowsSource(ctx context.Context, src spec.Source) error
}

SourcePolicy gates where apps may be created from (R-092).

Evaluated before anything touches disk. There is nothing to clone yet in this phase, but the check belongs at creation and putting it here now means the ordering is already right when cloning arrives in phase 6.

type SpecDefaults

type SpecDefaults interface {
	Defaults(ctx context.Context) spec.Defaults
}

SpecDefaults supplies the install's defaults for a spec.

type Updates

type Updates interface {
	Status(ctx context.Context) (update.Status, error)
	// CheckNow asks GitHub now rather than at the next tick (R-349).
	CheckNow(ctx context.Context) (update.Status, error)
}

Updates reports what the update check knows.

type Upgrades

type Upgrades interface {
	PlanFor(ctx context.Context, version string) (upgrade.Plan, error)
	Start(ctx context.Context, p authz.Principal, req upgrade.Request) (upgrade.Attempt, error)
	Last(ctx context.Context) (*upgrade.Attempt, error)
}

Upgrades plans, starts and reports in-place upgrades.

type VolumeUsage

type VolumeUsage struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Path  string `json:"path"`
	Bytes int64  `json:"bytes"`
}

VolumeUsage is one volume a part mounts and how much it holds; -1 when unknown.

type WorkloadService added in v0.15.0

type WorkloadService struct {
	SlotKey string `json:"slot_key"`
	Type    string `json:"type"`
}

WorkloadService names the slot a provisioned workload fills.

type WorkloadStatus

type WorkloadStatus struct {
	Name    string `json:"name"`
	Primary bool   `json:"primary"`

	Present bool `json:"present"`
	Running bool `json:"running"`

	// Restarting is the crash loop, which is the thing somebody looking at a
	// degraded app most needs to see. A container that exits and is restarted
	// by the runtime is "running" at almost every instant Pando looks at it.
	Restarting   bool `json:"restarting"`
	RestartCount int  `json:"restart_count"`

	// Healthy is null when the workload declares no health check: no signal is
	// not the same as unhealthy (R-221).
	Healthy  *bool  `json:"healthy"`
	ExitCode *int   `json:"exit_code,omitempty"`
	Image    string `json:"image,omitempty"`

	StartedAt *time.Time `json:"started_at,omitempty"`

	// Service is the dependency a workload Pando runs for the app fills
	// (R-131's provisioned), so a reader sees a PostgreSQL where the runtime
	// names a handle such as svc-01h….
	Service *WorkloadService `json:"service,omitempty"`
	// contains filtered or unexported fields
}

WorkloadStatus is one part of an app, as it is right now.

The adapter's own struct used to be serialized straight out, which put Go field names on the wire — `Running`, `RestartCount` — in an API that is snake_case everywhere else, and left the console reading a shape nothing documented. It is the product (R-261), so it gets a type.

type WorkloadUsage

type WorkloadUsage struct {
	Name    string `json:"name"`
	Primary bool   `json:"primary"`
	Running bool   `json:"running"`

	CPUMillis      int   `json:"cpu_millis"`
	CPULimitMillis int   `json:"cpu_limit_millis"`
	MemoryBytes    int64 `json:"memory_bytes"`
	MemoryLimit    int64 `json:"memory_limit_bytes"`

	// DiskBytes is written outside the part's volumes; -1 when unknown.
	DiskBytes int64         `json:"disk_bytes"`
	Volumes   []VolumeUsage `json:"volumes"`
}

WorkloadUsage is one part of an app and what it is using now (R-245). Limits of 0 mean none: the part may use what the host has, and HostCPUMillis and HostMemoryBytes on the reading say how much that is.

Jump to

Keyboard shortcuts

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