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

errorpages

package
v0.29.28 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package errorpages serves the pages a front door falls back to when it has nothing behind it.

Why it exists

On 2026-10-11 the whole mesh was down and every host the cluster serves, the platform's own and every customer's, answered ingress-nginx's bare "503 Service Temporarily Unavailable". Nothing on the page said whose site it was, whether the fault was theirs or ours, or whether to wait. This package is the replacement: a handler with no database, no secrets and no dependency on the mesh, so it is the one thing that still answers when everything else does not. `memql error-pages` runs it, and every front-door Ingress names it as its default backend (nginx.ingress.kubernetes.io/default-backend).

Who sees what

The decision is made on the request's Host and nothing else.

  • The platform's own hosts (api, identity, mcp, os and vscode under the domain) get the configured installation name. An absent name gets the neutral page; only an explicit MemQL name selects the MemQL mark and face.
  • Every other host gets a neutral page with no MemQL mark, name, link, colour or face. A customer's visitor has never heard of the platform underneath their shopfront, and "MemQL is being updated" on someone else's site would put our name on their outage. The hosts a client's reserved account name serves (app, api and id under memql.<their domain>) are sites in this sense too: they appear under the client's brand and are read by people who have never heard of ours.

THE BARE DOMAIN IS A SITE. It is not one of the platform's hosts: the front door treats the apex as the cluster owner's main website, a site row like any other (docs/public/operate/front-door.md, component/frontdoor's Apex), and the owner wants a deployable to show nothing of the platform underneath it. So `memql.example` itself gets the neutral page while `os.memql.example` gets the MemQL one.

The platform's host set is not listed here. It is component/frontdoor's, the one place those hosts are composed: its Roles and its PlatformSites, the same two sets the rest of the engine reserves labels from. Not its Hosts, which also lists the apex and the wildcard rule. A platform site added there is branded here without anybody remembering a second list. An unset or malformed domain makes every host a site: the unconfigured case fails toward not branding somebody's page, and the subcommand says so on stderr at start.

An API client gets JSON, not a page it cannot parse: when Accept names application/json, and always on the api host.

/healthz is a path like any other here

This handler answers /healthz with the same 503 as every other path. This server is the default backend of every front door, so `https://api.<domain>/healthz` reaches it whenever the real node cannot answer, and a 200 there would turn the operator's own checks into false greens: the status-code loop in docs/public/operate/instance-scale-and-upgrade.md, which files any non-200 during a roll as a defect, the post-roll /healthz gate on identity, api and os, and any uptime monitor would all read the middle of an outage as healthy. The kubelet's probe is answered somewhere no forwarded request can reach: `memql error-pages` serves it on a second listener the Service does not list (MEMQL_ERROR_PAGES_HEALTH_ADDR), and that wiring is not this package's.

What it deliberately does not do

It logs no request. An idle instance was writing about a million log lines a day on 2026-10-11, and a default backend in the middle of an outage is exactly the process that would add one per visitor.

It is self-contained. Each page is one inline stylesheet, the mark in the markup, no script and no image, and the response's Content-Security-Policy allows that one stylesheet by hash and nothing else. The MemQL page also carries one font, the brand's wordmark face, inline as a data: URI, which its policy admits with `font-src data:` and nothing wider; the neutral page carries no font at all. Nothing a page needs is anywhere but in the response. Measured, the MemQL page is 11,895 bytes with the mark and the face inline and the neutral one 1,782, against the 20 KB the tests allow.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Handler

func Handler(domain, brandName string) http.Handler

Handler serves every path, for every method, with status 503 and Retry-After: 120, /healthz included. The platform's hosts (see ClassifyHost) get the configured installation page, every other host, the bare domain among them, the neutral one, and JSON in place of either for an API client.

A blank brandName is neutral; only the explicit name MemQL selects its mark. Other names are escaped text in the neutral layout. No settings read is needed.

It builds what it needs once: the pages, and the set of the platform's hosts under domain. A request costs a lookup and a write.

It panics if its own embedded pages cannot be rendered. That is a defect in this package and not a condition of the world: the same bytes fail on every start, the tests render both pages, and failing before the listener opens keeps a rollout from replacing working pods with broken ones.

Types

type Kind

type Kind int

Kind says whose page a host gets.

const (
	// KindSite is any host that is not one of the platform's own: a customer's
	// site, a custom domain, the bare domain, an address, a request with no host
	// at all. It gets the neutral page. It is the zero value, so a Kind nobody
	// set never brands anything.
	KindSite Kind = iota
	// KindPlatform is one of the installation's own hosts.
	KindPlatform
)

func ClassifyHost

func ClassifyHost(host, domain string) Kind

ClassifyHost answers KindPlatform for the hosts the front door serves the platform itself at under domain (api, identity, mcp, os, vscode), and KindSite for everything else: the bare domain, other subdomains, any other domain, a host nested one label deeper, an address, and every host at all when domain is empty.

Case, a port and a trailing dot are not part of the name. ingress-nginx forwards the client's Host header as it was sent, so `OS.Memql.Example:443` is a real value here and must be the platform's own host.

The handler does not call this per request: it builds the set once and classifies against it. This is the same decision, for a caller with a host and a domain and nothing else.

func (Kind) String

func (k Kind) String() string

Jump to

Keyboard shortcuts

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