compose

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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

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

func GenerateValue(kind string, length int) (string, error)

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

type Environment map[string]string

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 Parse

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

Parse decodes a compose.yaml document.

func (*File) Notices

func (f *File) Notices() []Notice

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

func (f *File) Validate() error

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

func (f *File) ValidateForBuild() error

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

func FindMagicVars(s string) []MagicVar

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.

func (*Networks) UnmarshalYAML

func (n *Networks) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML implements the list-or-map union described 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

type Port struct {
	HostPort      int
	ContainerPort int
}

Port is one short-form ports: entry. ContainerPort is what store.DesiredService.Port (a single container port, not a host:container pair) actually uses.

func (*Port) UnmarshalYAML

func (p *Port) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML supports ports:'s short form only: "container" or "host:container". Long-form mapping entries are rejected rather than silently dropped.

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

type UnresolvedVar struct {
	Service string
	EnvKey  string
	Token   string
}

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

type Volume struct {
	Name          string
	ContainerPath string
}

Volume is one short-form "name:/container/path" entry.

func (*Volume) UnmarshalYAML

func (v *Volume) UnmarshalYAML(node *yaml.Node) error

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.

Jump to

Keyboard shortcuts

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