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

maintenancerunner

package
v0.0.0-...-2fd5d2c Latest Latest
Warning

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

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

Documentation

Overview

Package maintenancerunner defines the contract between the Celerity CLI and the maintenance runner, and implements the runner side of it.

A Celerity SQL database sits on a private network, so nothing outside it can open a connection, including the machine the operator runs `celerity schema` from. The runner closes that gap without asking anyone to build network access. It is deployed inside the network, where it has a data-plane path to the database, and invoked over the provider's control plane, which is reachable with the same credentials that just ran the deploy.

What crosses the boundary is the *operation*, not the connection. The CLI requests "provision the application user on ordersDb"; the runner performs it against a local connection and returns a report. Provisioning and migration run through the same internal/sqlschema package in both places, so there is no second implementation to keep in step.

Provider boundaries

Everything in this package is cloud-neutral: the request and response format, the target configuration, dispatch and the operations themselves. Two things are not, and both live outside it. How the runner is hosted and invoked is the awsrunner sub-package's business, and where credentials come from is a dbconn.CredentialSource's. Adding a provider means adding those two, not changing this.

Why the request does not describe the database

A request names a database; it does not carry a host, a port or a credential. Everything needed to connect is stamped into the runner's own configuration at deploy time (see TargetsEnvVar). A caller therefore cannot point the runner at a database of their choosing, and the set of things it will ever connect to is fixed by the blueprint rather than by whoever holds permission to invoke it.

Index

Constants

View Source
const (
	// RunnerAnnotation marks the concrete resource that is the runner.
	RunnerAnnotation = "celerity.maintenance.runner"

	// RunnerAnnotationValue is the value RunnerAnnotation carries.
	RunnerAnnotationValue = "true"
)

Annotations the transformer stamps on a deployed runner so the CLI can find it in instance state without knowing what the provider named it.

View Source
const (
	// OpProvisionUser creates or repairs the application database user.
	OpProvisionUser = "provision-user"

	// OpPing checks the runner can reach and authenticate to a database. It
	// exists so a deploy can verify the path works without changing anything.
	OpPing = "ping"

	// OpApply applies pending migrations, as the database owner.
	OpApply = "apply"
)

Operations the runner accepts.

Provisioning and ping are named routines that execute no SQL they are handed. Apply is different and deliberately so, because migrations *are* SQL written by the author of the blueprint and there is no way to run them inside the network without carrying them in. What bounds it is the invoking identity rather than the payload. The runner's own permissions reach exactly the databases the blueprint gave it, and a caller must already hold invoke permission on it, which is the same permission that could otherwise redeploy the function outright. The migration set is also checksum-verified against atlas.sum before anything runs, so a payload that disagrees with the directory it claims to be is rejected rather than applied.

View Source
const HostEnvVar = "CELERITY_MAINTENANCE_HOST"

HostEnvVar overrides host detection. It exists for the case detection cannot cover, running the runner somewhere that looks like nothing in particular, such as a container during a test.

View Source
const TargetsEnvVar = "CELERITY_MAINTENANCE_TARGETS"

TargetsEnvVar is the environment variable the transformer stamps the runner's database targets into, as a JSON object keyed by the abstract database name.

An environment variable rather than a config store because it is the one piece of configuration every host platform supplies the same way. AWS Lambda caps environment variables at 4KB in total, which holds on the order of ten databases; beyond that the targets need to move to a parameter, which is a change to this constant's contract and nothing else.

Variables

This section is empty.

Functions

func HostNames

func HostNames() []string

HostNames returns the host platforms this binary was built with, sorted.

func RegisterHost

func RegisterHost(host Host)

RegisterHost adds a host platform. Called from a provider package's init.

func RegisterInvoker

func RegisterInvoker(deployTarget string, build func() Invoker)

RegisterInvoker adds a deploy target's invoker. Called from a provider package's init.

func Run

func Run(logger *zap.Logger) error

Run starts the maintenance runner. It does not return in normal operation.

Targets are read once at startup rather than per invocation. They are deploy-time configuration, and a bad one should fail here rather than on whichever invocation happens to land on a fresh execution environment.

Types

type ApplyReport

type ApplyReport struct {
	// Applied names the migrations that ran, in order. Empty when the database
	// was already up to date.
	Applied []string `json:"applied,omitempty"`

	// Blocked names migrations that were left unapplied because their risk
	// exceeds what the operator authorised, with the risk that stopped them.
	Blocked []BlockedMigration `json:"blocked,omitempty"`
}

ApplyReport describes what a migration run did, in the same terms the local path prints so an operator cannot tell from the output which route was taken.

func (*ApplyReport) Changed

func (r *ApplyReport) Changed() bool

Changed reports whether the run altered the database.

type BlockedMigration

type BlockedMigration struct {
	Name string `json:"name"`
	Risk string `json:"risk"`
}

BlockedMigration is a pending migration held back by the risk gate.

type Executor

type Executor struct {
	// contains filtered or unexported fields
}

Executor performs operations against the databases it was deployed to serve.

Nothing in here is cloud-specific. It reads its targets, resolves credentials through a dbconn.CredentialSource and runs the same internal/sqlschema code the CLI runs locally. What differs per provider is how the executor is hosted (a Lambda on AWS) and where credentials come from, and both of those are supplied from outside.

It holds no state between invocations beyond its targets, because a maintenance operation is rare enough that pooling a connection would only mean holding an idle one open against the database's connection limit.

func NewExecutor

func NewExecutor(targets Targets, credentials dbconn.CredentialSource, logger *zap.Logger) *Executor

NewExecutor builds an executor over a set of database targets, resolving credentials through the given provider's source.

func (*Executor) Handle

func (e *Executor) Handle(ctx context.Context, req Request) *Response

Handle runs one request and always returns a response. An operation that fails is reported in the response rather than as a host-level error, so the CLI has something to print beyond a stack trace.

type Handler

type Handler func(ctx context.Context, req Request) *Response

Handler receives one maintenance request and always returns a response.

type Host

type Host interface {
	// Name identifies the host platform ("aws").
	Name() string

	// Detect reports whether this process is running on it. Implementations
	// should key on something the platform itself sets, not on configuration a
	// user could get wrong.
	Detect() bool

	// Credentials resolves database credentials from the platform's secret
	// store.
	Credentials() dbconn.CredentialSource

	// Serve delivers invocations to handle. It does not return in normal
	// operation.
	Serve(handle Handler, logger *zap.Logger) error
}

Host is the platform the runner runs on. It covers what delivers invocations to the runner and where credentials come from there.

The runner binary itself names no provider. It registers whichever hosts were compiled into it, picks the one it is running on, and serves.

type Invoker

type Invoker interface {
	// Invoke runs one operation against a deployed runner. It returns an error
	// only when the runner could not be reached; an operation that ran and
	// failed comes back as a Response with OK false.
	Invoke(ctx context.Context, instance *state.InstanceState, req Request) (*Response, error)

	// Deployed reports whether the instance has a runner at all. An instance
	// deployed before runners existed, or one whose database is publicly
	// reachable, does not.
	Deployed(instance *state.InstanceState) bool
}

Invoker calls a deployed maintenance runner and returns its response.

One implementation per deploy target, selected by InvokerFor, for the same reason deploydb has one resolver per target: how a runner is addressed and invoked is entirely the provider's business, while what is asked of it is not.

func InvokerFor

func InvokerFor(deployTarget string) (Invoker, error)

InvokerFor returns the maintenance runner invoker for a deploy target.

type MigrationFile

type MigrationFile struct {
	// Name is the base file name, never a path. The runner writes these into a
	// directory of its own, and a separator here would let a payload write
	// outside it.
	Name string `json:"name"`

	// Content is the file's bytes as text. Migration files are SQL and the sum
	// file is text, so nothing here needs encoding.
	Content string `json:"content"`
}

MigrationFile is one file from the migration directory, including atlas.sum.

type Request

type Request struct {
	// Operation is one of the Op constants.
	Operation string `json:"operation"`

	// Database is the abstract name of the celerity/sqlDatabase to act on, as
	// written in the blueprint.
	Database string `json:"database"`

	// Migrations carries the migration directory for OpApply, because the files
	// live on the operator's machine and the runner has no access to it.
	//
	// A synchronous Lambda invocation caps its payload at 6MB, which is far more
	// than a migration directory of text files, but ApplyLimits is checked
	// before invoking so an oversized set is reported as such rather than as a
	// truncated request.
	Migrations []MigrationFile `json:"migrations,omitempty"`

	// MaxRisk is the highest migration risk level the operator authorised, from
	// the same --allow-risky and --allow-breaking flags the local path uses. An
	// empty value permits only safe migrations.
	MaxRisk string `json:"maxRisk,omitempty"`
}

Request is the Lambda invocation payload.

func (Request) Failed

func (r Request) Failed(err error) *Response

Failed builds an unsuccessful response for a request.

type Response

type Response struct {
	Operation string `json:"operation"`
	Database  string `json:"database"`
	OK        bool   `json:"ok"`

	// Error describes why the operation failed, in terms an operator can act on.
	Error string `json:"error,omitempty"`

	// Provision is the report from OpProvisionUser.
	Provision *sqlschema.ProvisionReport `json:"provision,omitempty"`

	// Apply is the report from OpApply.
	Apply *ApplyReport `json:"apply,omitempty"`
}

Response is the Lambda invocation result.

A failed operation is a successful invocation with OK false rather than a Lambda error. The distinction matters because a Lambda error gives the CLI a stack trace and no report, while this gives it something to print.

type Target

type Target struct {
	// Engine is "postgres" or "mysql".
	Engine string `json:"engine"`

	// Host and Port are the connection-pool endpoint where there is one,
	// otherwise the database's own endpoint.
	Host string `json:"host"`
	Port string `json:"port"`

	// Database is the database name to connect to.
	Database string `json:"database"`

	// OwnerCredentialRef points at the master/owner credential, the role
	// migrations run as. Empty when the database authenticates its owner with a
	// platform-issued token instead.
	OwnerCredentialRef string `json:"ownerCredentialRef,omitempty"`

	// OwnerUser and Region are what a token is issued for, when
	// OwnerCredentialRef is empty.
	OwnerUser string `json:"ownerUser,omitempty"`
	Region    string `json:"region,omitempty"`

	// AppUser is the role the application connects as.
	AppUser string `json:"appUser"`

	// AppUserCredentialRef points at the application user's generated password.
	// Empty when the application authenticates with a token, which needs no
	// stored credential.
	AppUserCredentialRef string `json:"appUserCredentialRef,omitempty"`

	// AppUserTokenAuth selects platform-issued token authentication (AWS IAM
	// database authentication, and its equivalents) for the application user.
	AppUserTokenAuth bool `json:"appUserTokenAuth,omitempty"`
}

Target is everything the runner needs to reach one database as its owner/master, and to determine what to provision on it.

Only references travel here, never a credential. What a reference looks like is the provider's business (an ARN on AWS today), and the runner resolves it through a dbconn.CredentialSource. Its deployed identity is granted exactly the references listed here for each database, so what it can read is visible in the deployed policy rather than in a payload.

type Targets

type Targets map[string]*Target

Targets is the runner's deploy-time configuration, keyed by abstract database name.

func LoadTargets

func LoadTargets() (Targets, error)

LoadTargets reads the runner's targets from its environment.

func ParseTargets

func ParseTargets(raw string) (Targets, error)

ParseTargets parses the targets JSON.

func (Targets) Lookup

func (t Targets) Lookup(database string) (*Target, error)

Lookup returns the target for a database. A name that does not match is far more often a stale deploy than a typo, so the error lists the databases this runner is configured for.

func (Targets) Names

func (t Targets) Names() []string

Names returns the databases this runner serves, sorted.

Directories

Path Synopsis
Package awsrunner hosts and invokes the Celerity maintenance runner on AWS.
Package awsrunner hosts and invokes the Celerity maintenance runner on AWS.

Jump to

Keyboard shortcuts

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