Documentation
¶
Overview ¶
Package compose parses a Docker Compose file into Levelrail's own desired-state model, in two shapes depending on the caller. The direct-import path (ToDesiredServices, via Validate) is deliberately narrow: every service needs a pre-built image (no build:), since there is no build context to build one from (a pasted file, no git checkout). The git-sourced expand path (ExpandBuildService, via ValidateForBuild) allows build: for exactly that reason: it always has a real checkout. Both paths share the same narrow scope otherwise: environment/ports/volumes support only their short-form syntax, and depends_on parses but is ignored (reconciler-level startup ordering, out of scope here). restart: and networks: parse and are surfaced as non-blocking Notices instead of being silently dropped or translated: see Notices for why neither has a real translation onto how Levelrail runs a service.
Index ¶
- func ExpandBuildService(svc spec.Service, sourceDir string) (services map[string]spec.Service, warnings []string, err error)
- func GenerateValue(kind string, length int) (string, error)
- func ToDesiredServices(appName string, f *File) (services []store.DesiredService, warnings []string, err error)
- type Environment
- type File
- type Healthcheck
- type MagicVar
- type Networks
- type Notice
- type NoticeLevel
- type Port
- type Service
- type UnresolvedVar
- type Volume
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ExpandBuildService ¶
func ExpandBuildService(svc spec.Service, sourceDir string) (services map[string]spec.Service, warnings []string, err error)
ExpandBuildService reads and parses the compose file svc.Build.Path points at (resolved relative to sourceDir, the same git checkout root every other build.type already resolves its own paths against), and returns one spec.Service per compose service it declares: a build:-bearing compose service becomes an ordinary build.type: dockerfile entry, an image:-only one becomes build.type: image. The caller (Pipeline.DeploySpec) is expected to splice these entries into its own Services map in svc's place and fan out exactly as it already does for any other declared service, no reconciler or build-pipeline change needed: every compose-declared service converges through the exact same one-container-per-service path every other service does.
svc.Build.Type must be spec.BuildCompose; anything else is a caller bug, not a user-facing error.
warnings carries one message per expanded service whose healthcheck: is a real, non-HTTP check (see resolveHealthcheck): the caller is expected to surface these to the operator, since that service's health is deliberately left unset rather than guessed.
func GenerateValue ¶
GenerateValue produces a real value for a generatable MagicVar kind. length <= 0 uses each kind's own sane default.
func ToDesiredServices ¶
func ToDesiredServices(appName string, f *File) (services []store.DesiredService, warnings []string, err error)
ToDesiredServices translates f into one store.DesiredService per compose service, named "<appName>-<serviceKey>" and linked to appName as their AppID (matching the naming convention internal/deploy's own multi-service fan-out uses). Runs Validate first, so a caller only needs to call this one function.
warnings carries one message per service whose healthcheck: is a real, non-HTTP check (see resolveHealthcheck): the caller is expected to surface these to the operator (log line, response field, ...) since that service's health is deliberately left unset rather than guessed.
Types ¶
type Environment ¶
Environment is environment:'s string-or-list union: a KEY: VALUE map, or a list of "KEY=VALUE" strings (a bare "KEY" means "inherit from the host shell" in real Compose, decoded here as an empty value since there's no host shell to inherit from).
func (*Environment) UnmarshalYAML ¶
func (e *Environment) UnmarshalYAML(node *yaml.Node) error
UnmarshalYAML implements the map-or-list union described above.
type File ¶
type File struct {
Version string
Services map[string]Service
// Domains maps a service key to the real domain it should be
// reachable at, from the top-level x-levelrail-domains extension
// (Compose's own reserved x- prefix for tool-specific keys). Used
// both to set that service's own store.DesiredService.Domains (real
// ingress routing) and to resolve any ${SERVICE_FQDN_*} reference
// within that same service's environment (ResolveMagicVars).
Domains map[string]string
// Networks lists this file's own top-level networks: names, sorted.
// Only used by Notices to detect that custom networks were declared
// at all; Levelrail doesn't create per-network isolation from this.
Networks []string
}
File is a parsed compose.yaml.
func (*File) Notices ¶
Notices reports every non-blocking observation across f:
- restart: (NoticeLevelNote): Levelrail's reconciler, not Docker's native restart policy, is the sole authority on keeping a container running (see docker.ContainerSpec's own doc comment on why every container it creates gets Docker's "no" restart policy). A declared restart: policy other than "no" parses but has no effect, so this is surfaced as a note rather than fabricating a translation that wouldn't be true.
- networks: (NoticeLevelWarning): every service in an app already shares one flat Docker network (internal/reconcile/application's NetworkName, one per app, not per compose network), so a compose file's custom networks: can't isolate services from each other here the way they would under real Compose. That's a real semantic gap for a file that expressed isolation intent, worth a warning rather than a silent drop.
func (*File) Validate ¶
Validate reports every unsupported-shape problem across all services, not just the first, so a template author can fix them in one pass. Used by the direct-import path (ToDesiredServices), which has no build context (no git checkout, just a pasted file) to build a build: block from, so it rejects one outright. See ValidateForBuild for the git-sourced deploy-spec path, which does have one.
func (*File) ValidateForBuild ¶
ValidateForBuild is Validate, except a service's build: block is allowed rather than rejected: used only by the git-sourced expand-a-compose-file-into-services path (ExpandBuildService), which has a real checkout to resolve a build context against, unlike the direct-import path Validate itself still guards.
type Healthcheck ¶
type Healthcheck struct {
Test healthcheckTest `yaml:"test"`
Interval string `yaml:"interval"`
Timeout string `yaml:"timeout"`
Retries int `yaml:"retries"`
StartPeriod string `yaml:"start_period"`
}
Healthcheck is one service's healthcheck: block, Docker Compose's own command-based health check schema (test/interval/timeout/retries/ start_period). Never executed: this platform's own health model is HTTP-path based, so resolveHealthcheck (healthcheck.go) extracts a readiness path from a curl/wget test when it can, and leaves health unset otherwise.
type MagicVar ¶
type MagicVar struct {
Token string
Kind string
Key string
Length int
Default string
HasDefault bool
Generatable bool
}
MagicVar is one parsed SERVICE_ placeholder found in an env value.
func FindMagicVars ¶
FindMagicVars returns every SERVICE_ token in s, in encounter order, including duplicates.
type Networks ¶
type Networks []string
Networks is a service's own networks:'s list-or-map union: a plain list of network names, or a map of name to per-network config (aliases, ipv4_address, ...) this decodes down to just the name, same reasoning as rawFile.Networks above.
type Notice ¶
type Notice struct {
Level NoticeLevel
Message string
}
Notice is one non-blocking observation about a parsed compose file.
type NoticeLevel ¶
type NoticeLevel string
NoticeLevel distinguishes a purely informational Notice from an operator-facing Warning about a semantic gap. Neither ever fails Validate: both describe compose keywords that parsed successfully but don't behave the way they would under real Docker Compose.
const ( NoticeLevelNote NoticeLevel = "note" NoticeLevelWarning NoticeLevel = "warning" )
The two NoticeLevel values Notices ever produces.
type Port ¶
Port is one short-form ports: entry. ContainerPort is what store.DesiredService.Port (a single container port, not a host:container pair) actually uses.
type Service ¶
type Service struct {
Image string
Build *rawBuild
Environment Environment
Ports []Port
Volumes []Volume
Labels map[string]string
Networks Networks
Restart string
Healthcheck *Healthcheck
}
Service is one entry under services:.
type UnresolvedVar ¶
UnresolvedVar is one SERVICE_ token ResolveMagicVars couldn't resolve: not a generatable kind, and no bash-style ${...:-default} fallback.
func ResolveMagicVars ¶
func ResolveMagicVars( f *File, generate func(kind, key string, length int) (string, error), persist func(serviceKey, envKey, value string) error, ) (secretEnv map[string][]string, unresolved []UnresolvedVar, err error)
ResolveMagicVars scans every service's environment for SERVICE_ placeholders, mutating f in place: a token with a bash-style default substitutes that default as a literal value; a generatable token (PASSWORD/USER/BASE64/HEX/REALBASE64) is removed from Environment entirely and returned via secretEnv instead, since its real value belongs in secret storage, not a literal desired-state column.
generate is called once per unique (kind, key) pair even when referenced by several services, so they all resolve to the same value (e.g. an app service's DB_PASSWORD and its sibling postgres service's own POSTGRES_PASSWORD both referencing SERVICE_PASSWORD_DB). persist is then called once per (service, env key) that ended up secret-backed, since secret storage is keyed per real service, not per magic-var key: the caller is expected to write that same value into whichever per-service secret store its later container-create step reads from.
A magic-var token embedded inside a larger string (not the entire env value, e.g. a composite DATABASE_URL) can still splice in a generated value: the whole assembled string is then secret-backed (persisted and added to secretEnv), the same as a whole-value generatable token, since it now contains one. A default-less, non-generatable token in that position is unresolved.
func (UnresolvedVar) String ¶
func (u UnresolvedVar) String() string
type Volume ¶
Volume is one short-form "name:/container/path" entry.
func (*Volume) UnmarshalYAML ¶
UnmarshalYAML supports volumes:'s short form only: "name:/path", optionally with a trailing ":ro"/":rw" this parses but doesn't use. A path-like "name" (bind mount) decodes with an empty Name so Validate reports it, rather than failing the whole parse here.