spec

package
v0.8.5 Latest Latest
Warning

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

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

Documentation

Overview

Package spec parses and validates the app.yaml file: the one declarative file a user writes in their repo, per the app spec design. Validation happens in two layers. Structural shape (required fields, enums, string patterns, unknown-key rejection) is checked against the embedded JSON Schema (schema/app.schema.json), kept as the single source of truth so CI and the deploy-time check share one definition, not two hand-maintained copies that drift. Rules the schema can't express (a field's validity depending on another field's value) are checked by Validate after parsing.

Index

Constants

View Source
const (
	ContextProduction    = "production"
	ContextDeployPreview = "deploy-preview"
	ContextBranchDeploy  = "branch-deploy"
	ContextBranchPrefix  = "branch:"
)

Deploy context names. Production is the app's own settings and has no stored row.

View Source
const (
	HeaderSideRequest  = "request"
	HeaderSideResponse = "response"
)

Header sides for HeaderNameProblem.

View Source
const (
	MaxLabels           = 32
	MaxLabelKeyLength   = 255
	MaxLabelValueLength = 4096
)

Sanity limits on custom labels: a genuine safety bound against unbounded container metadata growth, not a business threshold, so a fixed constant is appropriate here (the project's "no hardcoded thresholds" rule targets values a deployment might reasonably need to tune, like a retention window or a rate limit; a handful of operator hand-written monitoring/tooling labels per service isn't that). Docker itself doesn't publish an official label count/size limit, so these are chosen generously enough for real use (a monitoring agent or log shipper typically wants a handful of keys) while still bounding how much metadata one service can accumulate.

View Source
const (
	BuildDockerfile = "dockerfile"
	BuildCompose    = "compose"
	BuildRailpack   = "railpack"
	BuildStatic     = "static"
	BuildImage      = "image"
)

Build input types: the app spec's build.type values, matching the BuildKit-based build detection (Dockerfile, Compose, Railpack, static) plus one non-build type: a prebuilt image already sitting in a registry, deployed as-is with no build step at all (see Build.Image and internal/deploy's deployImage).

View Source
const (
	StrategyRolling   = "rolling"
	StrategyRecreate  = "recreate"
	StrategyBlueGreen = "blue-green"
	// StrategyCanary saves the canary: policy; the stable promotion itself
	// still rolls out blue-green.
	StrategyCanary = "canary"
)

Deploy strategies, part of the app spec. Blue-green is the effective default, since it's easier to get right than rolling with a single replica, applied by DefaultStrategy when Strategy is empty, not baked into the schema itself.

View Source
const (
	EngineFake        = "" // zero value only, never valid; see Validate
	EnginePostgres    = "postgres"
	EngineRedis       = "redis"
	EngineMySQL       = "mysql"
	EngineMongoDB     = "mongodb"
	EngineMariaDB     = "mariadb"
	EngineKeyDB       = "keydb"
	EngineClickHouse  = "clickhouse"
	EngineDragonfly   = "dragonfly"
	EngineValkey      = "valkey"
	EngineMeilisearch = "meilisearch"
	EngineQdrant      = "qdrant"
	EngineTypesense   = "typesense"
	EngineMemcached   = "memcached"
)

Supported managed database engines: Postgres and Redis shipped as first-class resources in the initial release, MySQL, MongoDB, MariaDB, KeyDB, ClickHouse, and Dragonfly joined once internal/reconcile/database's controller grew matching engine cases.

View Source
const (
	StaticSourceRedirects = "_redirects"
	StaticSourceHeaders   = "_headers"
	StaticSeverityError   = "error"
	StaticSeverityWarning = "warning"
	// StaticSidecarSuffix names the compiled rules file stored beside a published tree.
	StaticSidecarSuffix = ".routes.json"
)

Static routing sources, severities and sidecar naming.

View Source
const (
	StaticSlashOff    = "off"
	StaticSlashAdd    = "add"
	StaticSlashRemove = "remove"
)

Trailing slash modes for StaticServeOptions.TrailingSlash.

View Source
const (
	EnvStaticMaxRules        = "APP_STATIC_ROUTES_MAX_RULES"
	EnvStaticMaxHeaderBlocks = "APP_STATIC_ROUTES_MAX_HEADER_BLOCKS"
	EnvStaticMaxFileBytes    = "APP_STATIC_ROUTES_MAX_FILE_BYTES"
)

Environment variables that tune the rule-count caps in StaticLimits.

View Source
const (
	// TriggerModePush deploys on every push to the git source's
	// configured branch. The default, matching pre-trigger-mode
	// behavior.
	TriggerModePush = "push"
	// TriggerModeRelease deploys only on a tag ref push, or (GitHub
	// only) a "release" webhook event with action "published".
	TriggerModeRelease = "release"
)

Git source deploy trigger modes (store.GitSource.TriggerMode): which pushes to a connected repo actually trigger a deploy.

View Source
const (
	JobModeFresh      = "fresh"
	JobModeExec       = "exec"
	JobCatchUpSkip    = "skip"
	JobCatchUpRunOnce = "run-once"
)

Job modes and catch-up values accepted in app.yaml.

View Source
const DefaultReplicas = 1

DefaultReplicas is used when a service doesn't set replicas.

View Source
const EgressModeAllowlist = "allowlist"

EgressModeAllowlist is the only meaningful Egress.Mode value today; see Egress's own doc comment for why the block is opt-in rather than defaulting to deny-all.

View Source
const EnvBuildSecretsMax = "APP_BUILD_SECRETS_MAX"

EnvBuildSecretsMax overrides how many build.secrets one service may list.

View Source
const GPUAll = -1

GPUAll is GPU.Count's "every GPU on the node" value.

View Source
const InstanceLabelKey = ReservedLabelPrefix + "instance"

InstanceLabelKey is the Docker label key every container, network, and volume this control-plane instance creates gets stamped with, set to its own persistent instance ID (store.GetOrCreateInstanceID). Two separate control-plane instances (two installs, or a dev and a prod instance) can end up sharing one Docker daemon; without this, a brand-labeled or brand-namespaced resource alone doesn't say which instance created it, so one instance's stale-resource cleanup could mistake the other's live resource for its own garbage and remove it. Under ReservedLabelPrefix so no operator-supplied label can collide with or spoof it (ValidateLabels below already rejects that prefix).

View Source
const PendingImageTag = ":pending"

PendingImageTag is the tag an app is created with when its first image has not been built yet (the CLI's own git-build path, which must send some image for an app POST that requires one). It never names a real registry image, so the application reconciler treats it as "not built yet" rather than something to pull.

View Source
const ReservedLabelPrefix = "platform-reserved."

ReservedLabelPrefix is the Docker label key namespace no operator- supplied custom label may use, keeping it open for this platform's own bookkeeping labels (none exist yet: today a service's containers are found purely by name pattern, internal/reconcile/application's ContainerName/ownsContainer, not by label). It's a fixed, brand- independent string on purpose, not derived from brand.ShortName: brand.ShortName is runtime-configurable (APP_BRAND_SHORT_NAME), and a validation boundary keyed to a value that can change between deploys would make a previously-valid app.yaml unpredictably valid or invalid depending on unrelated brand config. It also has to hold for cmd/levelrail-cli, which validates a local app.yaml with no server connection and therefore no brand config to consult at all. This package stays decoupled from internal/brand for the same reason DiscoverPath (discover.go) does.

View Source
const StaticSourceSpec = "app.yaml"

StaticSourceSpec labels rules that come from app.yaml.

Variables

View Source
var (

	// StaticPlaceholderRe matches a :name placeholder in a rule target.
	StaticPlaceholderRe = regexp.MustCompile(`:([A-Za-z_][A-Za-z0-9_]*)`)
)

Functions

func AddStaticHeaderLine

func AddStaticHeaderLine(b *StaticHeaderBlock, line string, l StaticLimits) error

AddStaticHeaderLine parses one "Name: value" or "! Name" line into b.

func CompileStaticEntries

func CompileStaticEntries(routes []StaticRule, blocks []StaticHeaderBlock, opts *StaticServeOptions, l StaticLimits) ([]StaticRule, []StaticHeaderBlock, error)

CompileStaticEntries validates the app.yaml routes and header blocks and returns them with matchers prepared and Source set.

func ContextBranchPattern

func ContextBranchPattern(name string) (string, bool)

ContextBranchPattern returns the pattern of a "branch:<pattern>" context.

func DiscoverPath

func DiscoverPath(dir string, brandedName string) (string, error)

DiscoverPath finds the app spec file in dir, checking candidate filenames in order and returning the first one that exists. The spec file is discovered by a list of candidate filenames (app.yaml, deploy.yaml, plus the branded one) so a product rename does not break existing users later. The generic names are checked first for exactly that reason: if the product is ever renamed, brandedName changes, but a repo with an app.yaml already committed keeps working without the user touching anything.

brandedName is the caller-supplied branded filename stem (derived from brand.Brand, not imported here, so this package stays decoupled from internal/brand); pass "" to skip it, e.g. in tests.

func HeaderNameProblem

func HeaderNameProblem(side, name string) string

HeaderNameProblem returns why name cannot be used on side, or "".

func IsPendingImage added in v0.2.0

func IsPendingImage(image string) bool

IsPendingImage reports whether image is a PendingImageTag placeholder.

func IsStaticRedirectStatus

func IsStaticRedirectStatus(s int) bool

IsStaticRedirectStatus reports whether s is a 3xx code.

func IsToken

func IsToken(s string) bool

IsToken reports whether s is an RFC 9110 token, the grammar for a header name.

func NormalizeRepoPath added in v0.2.0

func NormalizeRepoPath(field, p string) (string, error)

NormalizeRepoPath cleans a repository-relative path typed by an operator. An empty result means the repository root. It rejects absolute paths, backslashes, NUL bytes and any ".." that would leave the repository.

func ParseStaticHeaders

func ParseStaticHeaders(src string, l StaticLimits) ([]StaticHeaderBlock, []StaticProblem)

ParseStaticHeaders parses Netlify _headers syntax: a path line, then indented "Name: value" lines, or "! Name" to remove a header.

func ParseStaticRedirects

func ParseStaticRedirects(src string, l StaticLimits) ([]StaticRule, []StaticProblem)

ParseStaticRedirects parses Netlify _redirects syntax: "from [key=val ...] to [status[!]]" per line. Bad lines are skipped and reported, never applied half understood.

func SafeHeaderValue

func SafeHeaderValue(v string) bool

SafeHeaderValue reports whether v can be written into a header without splitting it: no CR, LF or NUL, and no other control byte except tab.

func SchemaJSON added in v0.2.0

func SchemaJSON() ([]byte, error)

SchemaJSON returns the embedded app.yaml JSON Schema, for tooling that composes it into larger documents.

func StaticSidecarPath

func StaticSidecarPath(rootDir string) string

StaticSidecarPath is where the ruleset of the tree at rootDir is stored: beside the tree, so it is never served and follows the tree on rollback.

func ValidateContextConfig

func ValidateContextConfig(label, buildType string, c ContextOverrides) error

ValidateContextConfig checks one context's overrides against the app's build type.

func ValidateContextName

func ValidateContextName(name string) error

ValidateContextName checks that name is a settable context and its pattern is valid.

func ValidateDocument added in v0.2.0

func ValidateDocument(data []byte) (*Spec, []Issue)

ValidateDocument runs the same two layers Parse does (schema, then Validate's semantic rules) but never stops at the first problem: every schema violation comes back at once, located by path and line, for a live editor to annotate while the operator is still typing. The returned Spec is non-nil only when there are no issues. Deploy time still calls Parse directly; this exists only to give that same check friendlier, earlier feedback, not a second source of truth.

func ValidateLabels

func ValidateLabels(labels map[string]string) error

ValidateLabels checks labels against the rules above: reserved prefix, empty keys, and the sanity limits. Exported so internal/api's validateAppResource can run the identical check on API-sourced labels (which bypass Parse/Validate entirely, they're never described in app.yaml), the same "define once here, both layers call it" shape StrategyRolling/StrategyRecreate/StrategyBlueGreen already establish for the API layer to reference rather than re-derive.

func ValidateServiceName

func ValidateServiceName(name string) error

ValidateServiceName checks a service name against the key rules plus the 63 character DNS label limit.

func ValidateStaticBuild

func ValidateStaticBuild(name, buildType, command, publish, path string, args map[string]string) error

ValidateStaticBuild checks command and publish for a build of buildType.

func ValidateStaticHeaderBlock

func ValidateStaticHeaderBlock(b *StaticHeaderBlock, l StaticLimits) error

ValidateStaticHeaderBlock checks a block built from app.yaml and prepares it.

func ValidateStaticOptions

func ValidateStaticOptions(o *StaticServeOptions) error

ValidateStaticOptions checks the serving switches.

func ValidateStaticRule

func ValidateStaticRule(r *StaticRule, l StaticLimits) error

ValidateStaticRule checks a rule and prepares its matcher. It is the single gate for file rules and app.yaml rules alike.

func WriteStaticSidecar

func WriteStaticSidecar(rootDir string, rs *StaticRuleset) error

WriteStaticSidecar stores rs beside the tree at rootDir, replacing any earlier one.

Types

type Autoscale

type Autoscale struct {
	Min    int     `yaml:"min"`
	Max    int     `yaml:"max"`
	Metric string  `yaml:"metric"`
	Target float64 `yaml:"target"`
	// Tolerances are percents of Target: scale up above it, scale down below it.
	ScaleUpTolerance   int `yaml:"scale_up_tolerance,omitempty"`
	ScaleDownTolerance int `yaml:"scale_down_tolerance,omitempty"`
	// Durations use Go syntax such as 90s or 10m.
	ScaleUpCooldown   string `yaml:"scale_up_cooldown,omitempty"`
	ScaleDownCooldown string `yaml:"scale_down_cooldown,omitempty"`
	Stabilization     string `yaml:"stabilization,omitempty"`
	MaxStepUp         int    `yaml:"max_step_up,omitempty"`
	MaxStepDown       int    `yaml:"max_step_down,omitempty"`
}

Autoscale is a service's replica autoscaling policy (internal/autoscale). It owns the replica count, so it cannot be combined with replicas:.

func (*Autoscale) Policy

func (a *Autoscale) Policy(app string, ceiling int) (autoscale.Policy, error)

Policy converts the block into a normalized, validated policy for app. ceiling is the global maximum replicas, 0 to skip that check.

func (*Autoscale) Validate

func (a *Autoscale) Validate(service string, replicas int) error

Validate checks the block; a nil block is valid.

type Backup

type Backup struct {
	Schedule string `yaml:"schedule"`
	Retain   int    `yaml:"retain,omitempty"`
}

Backup describes a database's backup schedule.

type Budget

type Budget struct {
	// Egress is a size such as 500GiB or 2TB.
	Egress      string  `yaml:"egress,omitempty"`
	Cost        float64 `yaml:"cost,omitempty"`
	Action      string  `yaml:"action,omitempty"`
	Thresholds  []int   `yaml:"thresholds,omitempty"`
	ThrottleRPS int     `yaml:"throttle_rps,omitempty"`
}

Budget is a service's monthly egress and cost guardrail (internal/spendcap).

func (*Budget) Cap

func (b *Budget) Cap(app string) (spendcap.Cap, error)

Cap converts the block into a normalized, validated cap for app.

func (*Budget) Validate

func (b *Budget) Validate(service string) error

Validate checks the block; a nil block is valid.

type Build

type Build struct {
	Type string `yaml:"type"`
	Path string `yaml:"path,omitempty"`
	// BaseDirectory scopes the build context to a subdirectory of the
	// repo, e.g. "apps/web" in a monorepo. Empty means the repo root.
	// Meaningful for dockerfile, railpack, and static; not meaningful
	// for image (nothing gets built) or compose (the compose file's own
	// context: field already scopes each service).
	BaseDirectory string `yaml:"baseDirectory,omitempty"`
	// Command is the shell command that builds a static site, run in a
	// sandboxed build container. Only meaningful for build.type: static.
	Command string `yaml:"command,omitempty"`
	// Publish is the directory the build writes the site to, relative to the
	// base directory. Required with Command; the files in it are what is served.
	Publish string `yaml:"publish,omitempty"`
	// Image is a full registry reference (e.g.
	// "ghcr.io/org/app:v1.2.3"), only meaningful for build.type: image:
	// a CI pipeline (or anything else) already built and pushed this
	// exact image, so there is nothing for this control plane to build,
	// only to deploy as-is. See internal/deploy's deployImage.
	Image string `yaml:"image,omitempty"`
	// RegistryCredential names a store.RegistryCredential (by its Name,
	// not ID: app.yaml is hand-written, an opaque ID would be hostile
	// to author) to authenticate with when pulling Image from a private
	// registry. Empty means an unauthenticated (public) pull.
	RegistryCredential string `yaml:"registryCredential,omitempty"`
	// Args are Dockerfile build-time ARG values (e.g. a base image
	// version, a build-time feature flag), passed through as
	// --build-arg equivalents to BuildKit. Only meaningful for
	// build.type: dockerfile.
	Args map[string]string `yaml:"args,omitempty"`
	// Secrets names app secrets mounted as BuildKit secrets (id = name)
	// for RUN --mount=type=secret. Only meaningful for dockerfile.
	Secrets []string `yaml:"secrets,omitempty"`
}

Build describes how a service's image gets built.

func ResolveGitBuild added in v0.2.0

func ResolveGitBuild(buildType, baseDirectory, buildPath string) (Build, error)

ResolveGitBuild turns a git source's persisted build settings into the Build a deploy runs. baseDirectory is the build context (empty is the repository root); buildPath is the Dockerfile (or static output directory) relative to the repository root, never to baseDirectory. The returned Build.Path is relative to the context, which is what the deploy pipeline joins onto it.

func (Build) RunsStaticBuild

func (b Build) RunsStaticBuild() bool

RunsStaticBuild reports whether the build compiles a static site in a build container, as opposed to serving committed files as-is.

type Canary

type Canary struct {
	Stages []int `yaml:"stages,omitempty"`
	// Interval is how long each stage observes traffic, Go duration syntax.
	Interval     string  `yaml:"interval,omitempty"`
	MaxErrorRate float64 `yaml:"max_error_rate,omitempty"`
	MaxP95Ratio  float64 `yaml:"max_p95_ratio,omitempty"`
}

Canary is a service's staged canary policy (internal/canary). Unset fields fall back to the APP_CANARY_* defaults.

func (*Canary) Policy

func (c *Canary) Policy(base canarypolicy.Policy) (canarypolicy.Policy, error)

Policy overlays the block on base and validates the result.

type ContextBuild

type ContextBuild struct {
	Command string            `yaml:"command,omitempty"`
	Publish string            `yaml:"publish,omitempty"`
	Args    map[string]string `yaml:"args,omitempty"`
}

ContextBuild is the build subset a context may override.

type ContextConfig

type ContextConfig struct {
	Env   map[string]string `yaml:"env,omitempty"`
	Build ContextBuild      `yaml:"build,omitempty"`
}

ContextConfig overrides env and build settings for one deploy context.

func (ContextConfig) DeployConfig

func (c ContextConfig) DeployConfig() ContextOverrides

DeployConfig converts c to the resolver's form.

type ContextOverrides

type ContextOverrides struct {
	Env          map[string]string
	BuildCommand string
	Publish      string
	BuildArgs    map[string]string
}

ContextOverrides is the settings one context can override. Empty fields inherit.

func (ContextOverrides) Empty

func (c ContextOverrides) Empty() bool

Empty reports whether c overrides nothing.

type Database

type Database struct {
	Engine  string  `yaml:"engine"`
	Version string  `yaml:"version,omitempty"`
	Backup  *Backup `yaml:"backup,omitempty"`
	// EphemeralInPreviews opts this database into a disposable,
	// preview-scoped instance of its own (a full container, its own
	// volume, its own credentials) rather than sharing whatever this
	// database resolves to in production: one per pull request, created
	// alongside the preview and destroyed with it, with no restore path
	// once torn down. Off by default, like every other opt-in toggle in
	// this codebase; only meaningful for a database attached to an app
	// with preview environments enabled (store.GitSource.PreviewEnabled).
	EphemeralInPreviews bool `yaml:"ephemeralInPreviews,omitempty"`
	// IsolatedInPreviews opts this database into an isolated credential
	// per preview, created on this same existing database (no new
	// container, no new volume) rather than the whole disposable
	// instance EphemeralInPreviews creates: a Postgres role, or a Redis
	// ACL user scoped to its own key prefix. Ignored when
	// EphemeralInPreviews is also set, since an ephemeral instance
	// already has its own credentials. Postgres and Redis only today.
	// Off by default.
	IsolatedInPreviews bool `yaml:"isolatedInPreviews,omitempty"`
}

Database is one entry under databases:.

type Egress added in v0.2.0

type Egress struct {
	Mode  string        `yaml:"mode"`
	Allow []EgressAllow `yaml:"allow,omitempty"`
}

Egress is a service's outbound network policy.

type EgressAllow added in v0.2.0

type EgressAllow struct {
	Host string `yaml:"host"`
	Port int    `yaml:"port"`
}

EgressAllow is one host+port pair a service's outbound traffic may reach when Egress.Mode is allowlist. Host is a bare hostname, resolved (and re-resolved on an interval, DNS records aren't static) by the egress sidecar rather than pinned to an IP at deploy time.

type EnvVar

type EnvVar struct {
	// Value is a literal value, set when the YAML node was a plain
	// string scalar rather than a mapping.
	Value string
	// From references another resource's computed value, e.g.
	// "postgres.main.url". Mutually exclusive with Value, Secret, and
	// Vault.
	From string
	// Secret means the operator provides this at deploy time via
	// the platform's own envelope-encrypted secret storage, never
	// written to app.yaml or the git repo. Mutually exclusive with
	// Vault: a given env var resolves its value from exactly one of
	// the two secret sources, never both.
	Secret bool
	// Required, only meaningful alongside Secret: fail the deploy if no
	// value has been provided, rather than starting the container with
	// the variable unset.
	Required bool
	// Vault means this value is resolved live from an external
	// HashiCorp Vault instance at container-create time
	// (internal/reconcile/application's resolveEnv), never stored by
	// this platform at all: the alternative to Secret above, not a
	// variant of it. nil means not vault-backed.
	Vault *VaultRef
}

EnvVar is one entry in a service's env block. app.yaml allows two shapes for the same field, per the app spec's own example, which shows both:

DATABASE_URL: { from: postgres.main.url }
API_KEY:      { secret: true, required: true }

A plain string scalar is also accepted as shorthand for a literal value, since forcing every simple env var into object form would make the common case more verbose than the shape it's modeled on (Docker Compose's own env shorthand).

func (*EnvVar) UnmarshalYAML

func (e *EnvVar) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML implements the string-or-object union described on EnvVar, using yaml.v3's node-based Unmarshaler interface: the node's Kind tells a scalar apart from a mapping before deciding which shape to decode into, rather than trying one and falling back on error.

type ExecCommand added in v0.2.0

type ExecCommand []string

ExecCommand is health.*.exec: a list is run as argv, a string through /bin/sh -c.

func (*ExecCommand) UnmarshalJSON added in v0.2.0

func (e *ExecCommand) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts a shell string or an argv array.

func (*ExecCommand) UnmarshalYAML added in v0.2.0

func (e *ExecCommand) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts a shell string or an argv list.

type GPU added in v0.2.0

type GPU struct {
	Count   int      `yaml:"count,omitempty" json:"count,omitempty"`
	Devices []string `yaml:"devices,omitempty" json:"devices,omitempty"`
}

GPU is resources.gpu: `all`, a count, or {count, devices} where devices are NVIDIA indexes or UUIDs (devices wins over count).

func (GPU) Requested added in v0.2.0

func (g GPU) Requested() bool

Requested reports whether any GPU is being asked for.

func (*GPU) UnmarshalJSON added in v0.2.0

func (g *GPU) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts "all", a number, or an object.

func (*GPU) UnmarshalYAML added in v0.2.0

func (g *GPU) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts `all`, an integer, or a mapping.

type HeaderRule

type HeaderRule struct {
	Path   string            `yaml:"path"`
	Values map[string]string `yaml:"values,omitempty"`
	Remove []string          `yaml:"remove,omitempty"`
}

HeaderRule is one entry under a static service's headers:.

type Health

type Health struct {
	Readiness *Probe `yaml:"readiness,omitempty"`
	Liveness  *Probe `yaml:"liveness,omitempty"`
	// ReadyTimeout overrides how long a fresh deploy waits for Readiness
	// to pass before the deploy is marked ReadinessFailed (internal/
	// reconcile/application.defaultReadyBudget, 60s, when unset). A slow
	// cold start (a JVM app, a big migration, a slow external connection)
	// can legitimately take longer than 60s without ever being unhealthy.
	ReadyTimeout string `yaml:"readyTimeout,omitempty"`
}

Health holds a service's readiness and liveness probe configuration.

func (*Health) Validate added in v0.2.0

func (h *Health) Validate(service string) error

Validate checks both probes, naming service in any error.

type Hooks

type Hooks struct {
	PreDeploy  string `yaml:"preDeploy,omitempty"`
	PostDeploy string `yaml:"postDeploy,omitempty"`
}

Hooks are the two deploy-lifecycle commands a service can declare. Both run via "sh -c" inside the newly created container (see internal/reconcile/application.Controller's own doc comment for the full timing and failure-handling contract): PreDeploy before the container's readiness probe and before any old container is retired, PostDeploy after the whole replica set has cut over. Either field may be set alone.

type Issue added in v0.2.0

type Issue struct {
	Path    string `json:"path"`
	Line    int    `json:"line"`
	Message string `json:"message"`
}

Issue is one problem found in an app.yaml document, located by dotted path and source line when known. Mirrors internal/pipeline.Issue's shape, the live-validation convention this codebase already follows for its own declarative YAML format.

type Job added in v0.2.0

type Job struct {
	Name     string   `yaml:"name"`
	Schedule string   `yaml:"schedule"`
	Command  []string `yaml:"command"`
	Timezone string   `yaml:"timezone,omitempty"`
	// Mode is "fresh" (default, a new container from the service's image
	// and spec) or "exec" (inside the running container).
	Mode string `yaml:"mode,omitempty"`
	// Concurrency is "allow", "forbid" (default) or "replace".
	Concurrency     string  `yaml:"concurrency,omitempty"`
	Timeout         string  `yaml:"timeout,omitempty"`
	Retries         int     `yaml:"retries,omitempty"`
	RetryBackoff    string  `yaml:"retryBackoff,omitempty"`
	CatchUp         string  `yaml:"catchUp,omitempty"`
	CatchUpLookback string  `yaml:"catchUpLookback,omitempty"`
	CPU             float64 `yaml:"cpu,omitempty"`
	Memory          string  `yaml:"memory,omitempty"`
	WarnAfter       string  `yaml:"warnAfter,omitempty"`
	// Enabled defaults to true when omitted.
	Enabled *bool `yaml:"enabled,omitempty"`
}

Job is one entry under a service's jobs:. Durations use Go syntax ("30s", "10m", "1h"); Memory the same "512Mi"/"1Gi" shape as resources.

type Probe

type Probe struct {
	Path            string      `yaml:"path,omitempty"`
	Scheme          string      `yaml:"scheme,omitempty" json:"scheme,omitempty"`
	Host            string      `yaml:"host,omitempty" json:"host,omitempty"`
	TLSSkipVerify   bool        `yaml:"tls_skip_verify,omitempty" json:"tls_skip_verify,omitempty"`
	FollowRedirects *bool       `yaml:"follow_redirects,omitempty" json:"follow_redirects,omitempty"`
	ExpectedStatus  StatusCodes `yaml:"expected_status,omitempty" json:"expected_status,omitempty"`
	Exec            ExecCommand `yaml:"exec,omitempty" json:"exec,omitempty"`
	Interval        string      `yaml:"interval,omitempty"`
	Timeout         string      `yaml:"timeout,omitempty"`
	Failures        int         `yaml:"failures,omitempty"`
}

Probe is a single health check: an HTTP(S) request to Path, or a command run inside the container when Exec is set.

func (Probe) ProbeConfig added in v0.2.0

func (p Probe) ProbeConfig() (probe.Config, error)

ProbeConfig converts p into internal/probe's shape.

type Resources

type Resources struct {
	Memory string  `yaml:"memory,omitempty"`
	CPU    float64 `yaml:"cpu,omitempty"`
	// SwapMemory caps memory plus swap combined, the same "512Mi"/"1Gi"
	// shape as Memory (Docker's own MemorySwap semantics); only
	// meaningful alongside Memory, see Validate.
	SwapMemory string `yaml:"swapMemory,omitempty"`
	// CPUSet pins the container to specific host CPUs, Docker's own
	// cpuset-cpus format (e.g. "0-3" or "0,2").
	CPUSet string `yaml:"cpuSet,omitempty"`
	// GPU requests NVIDIA GPUs; the service is placed only on a node
	// with a usable GPU. nil means no GPU.
	GPU *GPU `yaml:"gpu,omitempty"`
}

Resources holds a service's resource limits.

type Route

type Route struct {
	From   string            `yaml:"from"`
	To     string            `yaml:"to"`
	Status int               `yaml:"status,omitempty"`
	Force  bool              `yaml:"force,omitempty"`
	Query  map[string]string `yaml:"query,omitempty"`
}

Route is one entry under a static service's routes:. Status defaults to 301.

type Service

type Service struct {
	Build Build `yaml:"build"`
	// Image is shorthand for build: {type: image, image: ...}; Parse folds it
	// into Build, so consumers only ever read Build.
	Image   string   `yaml:"image,omitempty"`
	Domains []string `yaml:"domains,omitempty"`
	Port    int      `yaml:"port,omitempty"`
	// HostPort pins the host-side port Docker binds Port to, 0 meaning
	// auto-assign (the same zero-value-means-unset convention Port
	// itself uses). Its storage home is store.DesiredService.HostPort
	// (migrations/0056_service_host_port.sql).
	HostPort int `yaml:"host_port,omitempty"`
	// BindAddress picks which network interface Port (and HostPort, if
	// pinned) binds to on the host: "private" (loopback only, the
	// default when empty), "public" (every interface, an explicit opt-
	// in), or a literal IP. See internal/bindaddr.Resolve for the exact
	// rules; EffectiveBindAddress applies the default.
	BindAddress string            `yaml:"bind_address,omitempty"`
	Health      *Health           `yaml:"health,omitempty"`
	Resources   *Resources        `yaml:"resources,omitempty"`
	Env         map[string]EnvVar `yaml:"env,omitempty"`
	Replicas    int               `yaml:"replicas,omitempty"`
	Strategy    string            `yaml:"strategy,omitempty"`
	// Labels are arbitrary operator-supplied Docker labels applied to the
	// service's container at create time, an escape hatch for tooling
	// this platform doesn't know about (a monitoring agent or log
	// shipper that keys off container labels, a homegrown script, and
	// so on). See ValidateLabels (labels.go) for what's rejected:
	// notably, any key under ReservedLabelPrefix, kept open for this
	// platform's own bookkeeping labels.
	Labels map[string]string `yaml:"labels,omitempty"`

	// Volumes are named Docker volumes or host-directory bind mounts this
	// service's container mounts (see Volume's own doc comment for how
	// the two are distinguished): named volumes were previously a
	// database-only capability, bind mounts a compose-import-only one. A
	// named volume's Name is a logical name scoped to this service, not a
	// global Docker volume name (see internal/deploy's translation into
	// store.ServiceVolume for the actual, platform-prefixed name); two
	// services can each declare a volume named "data" without colliding.
	Volumes []Volume `yaml:"volumes,omitempty"`

	// Hooks are shell commands the reconciler runs inside this service's
	// own container at defined points in a deploy (internal/reconcile/
	// application's controller). Nil means neither is configured, the
	// same "declarative, resolved before storing" shape Health/Resources
	// already follow.
	Hooks *Hooks `yaml:"hooks,omitempty"`

	// Command overrides the image's own default CMD
	// (store.DesiredService.Command), nil meaning the image's own
	// default: the app.yaml equivalent of
	// internal/compose.Service.Command, which this mirrors. A plain argv
	// list, never shell-interpreted.
	Command []string `yaml:"command,omitempty"`

	// Egress restricts this service's outbound network traffic to an
	// explicit host+port allowlist, enforced by
	// internal/reconcile/application's egress sidecar. Nil (the default,
	// and the only state every service had before this field existed)
	// means today's open-egress behavior, unchanged: this is deliberately
	// opt-in, not deny-by-default, since Levelrail runs arbitrary
	// third-party apps for existing users rather than AX's own
	// tightly-scoped agent sandboxes, and defaulting every existing app
	// to deny-all egress on upgrade would be a breaking change.
	Egress *Egress `yaml:"egress,omitempty"`

	// LoadBalancer configures balancing across this service's replicas
	// (internal/loadbalancer). Nil keeps the plain single-upstream route.
	LoadBalancer *loadbalancer.Config `yaml:"loadbalancer,omitempty"`

	// Budget caps this service's monthly egress and cost. Nil leaves any
	// budget set from the dashboard or CLI alone.
	Budget *Budget `yaml:"budget,omitempty"`

	// Autoscale owns this service's replica count within min and max. Nil
	// leaves any policy set from the dashboard or CLI alone.
	Autoscale *Autoscale `yaml:"autoscale,omitempty"`
	// Canary tunes staged canary releases (strategy: canary). Nil uses the
	// APP_CANARY_* defaults.
	Canary *Canary `yaml:"canary,omitempty"`

	// Contexts overrides env and build settings for deploy-preview,
	// branch-deploy or a "branch:<pattern>" context. Production is the
	// service's own settings.
	Contexts map[string]ContextConfig `yaml:"contexts,omitempty"`

	// DependsOn names sibling services: keys (within this same Spec, or
	// within the same compose file for a compose-expanded service, see
	// internal/compose.ExpandBuildService) this service waits on before
	// the reconciler starts its own containers: internal/reconcile/
	// application.Controller does not create a container for this
	// service until every named dependency has at least one running
	// container. This matches real Docker Compose's own default
	// depends_on: semantic (service_started, not service_healthy): a
	// start-order guarantee, not a health-based wait. See Validate for
	// the reference and cycle checks.
	DependsOn []string `yaml:"dependsOn,omitempty"`

	// Jobs are cron-scheduled commands run for this service, each in a
	// fresh container of its image (or exec'd into the running one), kept
	// in step with this list on every deploy. Nil leaves jobs created
	// through the dashboard or CLI alone; an explicit empty list removes
	// every spec-declared job.
	Jobs []Job `yaml:"jobs,omitempty"`

	// Routes, Headers and Static shape a build.type: static site: redirects
	// and rewrites, response headers, and the SPA, clean URL and 404 options.
	Routes  []Route        `yaml:"routes,omitempty"`
	Headers []HeaderRule   `yaml:"headers,omitempty"`
	Static  *StaticOptions `yaml:"static,omitempty"`
}

Service is one entry under services:.

func (*Service) EffectiveBindAddress added in v0.2.0

func (svc *Service) EffectiveBindAddress() string

EffectiveBindAddress returns svc.BindAddress, or bindaddr.Default (private, loopback-only) if unset. Mirrors EffectiveStrategy/ EffectiveReplicas' own "resolve the default here, once" shape.

func (*Service) EffectiveReplicas

func (svc *Service) EffectiveReplicas() int

EffectiveReplicas returns svc.Replicas, or DefaultReplicas if unset. Schema validation guarantees Replicas is never negative when set; zero means "not specified in app.yaml", not "zero replicas".

func (*Service) EffectiveStrategy

func (svc *Service) EffectiveStrategy() string

EffectiveStrategy returns svc.Strategy, or the default (blue-green, since it's easier to get right than rolling with a single replica) if unset.

func (Service) StaticRoutingInput

func (s Service) StaticRoutingInput() StaticInput

StaticRoutingInput converts the routing fields to the form the deploy compiles; the _redirects and _headers file texts are filled in by the caller.

type Spec

type Spec struct {
	Version   int                 `yaml:"version"`
	Services  map[string]Service  `yaml:"services"`
	Databases map[string]Database `yaml:"databases,omitempty"`
}

Spec is a fully parsed, schema-valid app.yaml.

func Parse

func Parse(data []byte) (*Spec, error)

Parse validates raw app.yaml bytes against the embedded JSON Schema, then unmarshals into a Spec and runs the semantic checks the schema can't express. Both layers must pass for Parse to succeed; a caller never sees a Spec that's schema-valid but semantically broken, or vice versa.

func (*Spec) BudgetFor

func (s *Spec) BudgetFor(service string) (*Budget, error)

BudgetFor returns a service's budget: block, or nil when it has none.

func (*Spec) LoadBalancerFor added in v0.2.0

func (s *Spec) LoadBalancerFor(service string) (*loadbalancer.Config, error)

LoadBalancerFor returns a service's loadbalancer: block, or nil when the service has none.

func (*Spec) Validate

func (s *Spec) Validate() error

Validate checks rules the JSON Schema can't express: values whose validity depends on another field, or on the rest of the document, not just their own shape. Parse always runs this after schema validation succeeds, callers building a Spec by hand (tests, future tooling) should call it too before trusting a Spec.

type StaticHeaderBlock

type StaticHeaderBlock struct {
	Source string           `json:"source"`
	Path   string           `json:"path"`
	Set    []StaticHeaderKV `json:"set,omitempty"`
	Remove []string         `json:"remove,omitempty"`
	// contains filtered or unexported fields
}

StaticHeaderBlock applies Set and Remove to responses whose path matches Path.

func (*StaticHeaderBlock) Pattern

func (b *StaticHeaderBlock) Pattern() *StaticPattern

Pattern returns the compiled Path matcher, nil until validated.

func (*StaticHeaderBlock) SetPattern

func (b *StaticHeaderBlock) SetPattern(p *StaticPattern)

SetPattern stores the matcher compiled for Path.

type StaticHeaderKV

type StaticHeaderKV struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

StaticHeaderKV is one response header.

type StaticInput

type StaticInput struct {
	Redirects string
	Headers   string
	// Routes and HeaderBlocks come from app.yaml and rank before the files.
	Routes       []StaticRule
	HeaderBlocks []StaticHeaderBlock
	Options      StaticServeOptions
}

StaticInput is what a deploy hands to the static routes compiler.

type StaticLimits

type StaticLimits struct {
	MaxRules        int
	MaxHeaderBlocks int
	MaxFileBytes    int
	MaxValueLen     int
}

StaticLimits caps the size of a site's routing rules.

func DefaultStaticLimits

func DefaultStaticLimits() StaticLimits

DefaultStaticLimits are the caps used when no environment override is set.

type StaticOptions

type StaticOptions struct {
	SPA           bool   `yaml:"spa,omitempty"`
	CleanURLs     bool   `yaml:"cleanUrls,omitempty"`
	TrailingSlash string `yaml:"trailingSlash,omitempty"`
	NotFound      string `yaml:"notFound,omitempty"`
}

StaticOptions is a static service's static: block.

type StaticPattern

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

StaticPattern is a path pattern with :name segments and a trailing * splat, compiled to an RE2 expression so matching is linear in the path length.

func CompileStaticPattern

func CompileStaticPattern(p string) (*StaticPattern, error)

CompileStaticPattern compiles a path pattern.

func ValidateStaticHeaderPath

func ValidateStaticHeaderPath(p string, l StaticLimits) (*StaticPattern, error)

ValidateStaticHeaderPath checks a header block path and compiles it.

func (*StaticPattern) Match

func (pt *StaticPattern) Match(path string) map[string]string

Match returns the captured values, or nil when path does not match.

type StaticProblem

type StaticProblem struct {
	Source   string `json:"source"`
	Line     int    `json:"line,omitempty"`
	Severity string `json:"severity"`
	Message  string `json:"message"`
}

StaticProblem is one validation finding. Line is 0 for spec rules.

type StaticQueryMatch

type StaticQueryMatch struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

StaticQueryMatch requires a query parameter; Value is a literal or ":name".

type StaticRule

type StaticRule struct {
	Source string             `json:"source"`
	From   string             `json:"from"`
	To     string             `json:"to"`
	Status int                `json:"status"`
	Force  bool               `json:"force,omitempty"`
	Query  []StaticQueryMatch `json:"query,omitempty"`
	// contains filtered or unexported fields
}

StaticRule is one redirect, rewrite or status rule.

func (*StaticRule) Pattern

func (r *StaticRule) Pattern() *StaticPattern

Pattern returns the compiled From matcher, nil until validated.

type StaticRuleset

type StaticRuleset struct {
	Version  int                 `json:"version"`
	Rules    []StaticRule        `json:"rules"`
	Headers  []StaticHeaderBlock `json:"headers"`
	Options  StaticServeOptions  `json:"options"`
	Problems []StaticProblem     `json:"problems"`
}

StaticRuleset is everything compiled for one deploy.

func CompileStatic

func CompileStatic(in StaticInput, l StaticLimits) (*StaticRuleset, error)

CompileStatic validates every source and returns the ruleset. Invalid file lines become problems and are skipped; an invalid app.yaml entry, a bad option or a cap breach is an error, so the deploy fails before anything is served.

func LoadStaticSidecar

func LoadStaticSidecar(rootDir string) (*StaticRuleset, error)

LoadStaticSidecar reads the ruleset of the tree at rootDir. A tree published before routing rules existed has none and yields nil without an error.

func (*StaticRuleset) Prepare

func (rs *StaticRuleset) Prepare() error

Prepare compiles the matchers of a ruleset read back from JSON.

type StaticServeOptions

type StaticServeOptions struct {
	SPA           bool   `json:"spa,omitempty"`
	CleanURLs     bool   `json:"clean_urls,omitempty"`
	TrailingSlash string `json:"trailing_slash,omitempty"`
	NotFound      string `json:"not_found,omitempty"`
}

StaticServeOptions are the static serving switches.

type StatusCodes added in v0.2.0

type StatusCodes string

StatusCodes is health.*.expected_status: "200-399", 204, or a list mixing codes and ranges. It is kept in its normalized string form.

func (*StatusCodes) UnmarshalJSON added in v0.2.0

func (s *StatusCodes) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts a string, a number, or an array of either.

func (*StatusCodes) UnmarshalYAML added in v0.2.0

func (s *StatusCodes) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts a scalar or a sequence of scalars.

type VaultRef added in v0.2.0

type VaultRef struct {
	Path string `yaml:"path"`
	Key  string `yaml:"key"`
}

VaultRef is app.yaml's { vault: { path, key } } env var shape: Path is the Vault KV secret's path, Key is the field name within that secret's data. Both required together, mirroring internal/store's own VaultEnvRef which this parses into (see internal/deploy's vaultEnvRefs).

type Volume

type Volume struct {
	Name     string `yaml:"name,omitempty"`
	HostPath string `yaml:"hostPath,omitempty"`
	Path     string `yaml:"path"`
	ReadOnly bool   `yaml:"readOnly,omitempty"`
}

Volume is one entry under a service's volumes:. Exactly one of Name (a named Docker volume) or HostPath (a bind mount of a real host directory, gated the same as internal/compose's own bind-mount support: see validateBindMountHostPath) is set; see Validate. ReadOnly is only meaningful alongside HostPath, matching store.ServiceBindMount.

Jump to

Keyboard shortcuts

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