compose

package
v0.2.0-beta.12 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: Apache-2.0 Imports: 17 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 accept both Compose's short and long forms (see yaml.go), but only the fields Levelrail's own model has room for: no port ranges, no UDP, no tmpfs/npipe mounts. restart:, networks:, and depends_on: all parse and are surfaced as non-blocking Notices instead of being silently dropped or translated: see Notices for why none of the three has a real translation onto how Levelrail runs a service (depends_on: in particular is never used to sequence container startup order, that's reconciler-level work out of scope here). command: and entrypoint: both parse and translate into store.DesiredService's own Command and Entrypoint fields. volumes: additionally accepts an absolute host path on the left side as a bind mount (ValidateForBuild rejects one; see that method's own doc comment for why), gated at the HTTP layer to AbilityRoot and, even then, against internal/bindmount's own forbidden-path list (see validateBindMountHostPath).

Index

Constants

View Source
const PullPolicyAlways = "always"

PullPolicyAlways is pull_policy: always, the only non-default value this package supports.

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 parses the compose file at svc.Build.Path (relative to sourceDir) and returns one spec.Service per compose service, plus a warning for each healthcheck left untranslated.

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: could not be translated (see resolveHealthcheck), for the caller to surface.

A bind-mount volumes: entry (compose.go's own doc comment) becomes a store.ServiceBindMount, not a store.ServiceVolume: the caller must require AbilityRoot before persisting a result carrying any BindMounts, this package has no notion of a caller's abilities.

Types

type Command added in v0.2.0

type Command []string

Command is command:'s and entrypoint:'s shared string-or-list union: a list passes through as exec-form args, a bare string is Compose's own shorthand for shell form, wrapped here as ["/bin/sh", "-c", "<string>"] to match Docker's own documented interpretation of a string CMD or ENTRYPOINT.

func (*Command) UnmarshalYAML added in v0.2.0

func (c *Command) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML implements the string-or-list union described above.

type DependsOn added in v0.2.0

type DependsOn []string

DependsOn is a service's own depends_on:'s list-or-map union, the same shape as Networks: either a plain list of service names, or a map of name to per-dependency config (condition, restart, ...) decoded down to just the names.

func (*DependsOn) UnmarshalYAML added in v0.2.0

func (d *DependsOn) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML implements the list-or-map union described above.

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.
  • depends_on: (NoticeLevelWarning): parsed but never used to sequence container startup order, so a service can start before what it depends on is ready. A real semantic gap, worth a warning so an operator whose service crashloops on startup isn't left guessing why.

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. Bind mounts stay rejected on this path even though Validate now allows them: the expanded result is a spec.Service (toSpecService, expand.go), and spec.Volume has no host-path concept to carry one into, so allowing one through here would either drop it silently or produce a nonsensical empty-named volume downstream.

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). Docker never runs it: resolveHealthcheck (healthcheck.go) translates it into this platform's own readiness probe.

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 both of ports:'s forms: the short scalar form ("container" or "host:container") and the long mapping form (target/published/protocol/mode keys). mode: is accepted but ignored, since Levelrail has no host-vs-ingress publishing distinction to carry it onto.

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
	// DependsOn is depends_on:, never used to sequence container startup
	// order (see Notices); kept only so Notices can tell it was declared.
	DependsOn DependsOn
	// Command overrides the image's own default CMD
	// (store.DesiredService.Command), parsed from command:'s own
	// string-or-list union (Command's own UnmarshalYAML in yaml.go): a
	// plain string is shell-wrapped as ["/bin/sh", "-c", "<string>"],
	// matching Compose's own documented behavior for that form.
	Command Command
	// Entrypoint overrides the image's own default ENTRYPOINT
	// (store.DesiredService.Entrypoint), parsed with the same
	// string-or-list union as Command.
	Entrypoint Command
	// PullPolicy is pull_policy:, normalized by normalizePullPolicy into
	// exactly the two states store.DesiredService.PullPolicy
	// distinguishes: PullPolicyAlways forces a fresh pull even when the
	// image is already present locally, empty means today's existing
	// pull-if-absent behavior. Real Compose's other recognized spellings
	// ("missing", "if_not_present", an unset field) all normalize to
	// empty too; "never" and "build" are rejected, see
	// normalizePullPolicy.
	PullPolicy string
}

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 and command 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) found in Environment is removed from it entirely and returned via secretEnv instead, since its real value belongs in secret storage, not a literal desired-state column. Command has no equivalent secret-storage indirection (a Command entry can't be swapped for a decrypted-at-create-time secret the way an env var can), so a generatable token found there is always reported as unresolved instead, same as a default-less non-generatable token.

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
	HostPath      string
	ContainerPath string
	// ReadOnly mounts read-only inside the container, from an optional
	// trailing ":ro" on the short-form entry.
	ReadOnly bool
}

Volume is one short-form "name:/container/path" entry, either a named Docker volume (Name set, HostPath empty) or a bind mount of a real host directory (HostPath set, Name empty): exactly one of the two is ever set, see Volume.UnmarshalYAML (yaml.go) for how the left side of the entry decides which.

func (*Volume) UnmarshalYAML

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

UnmarshalYAML supports both of volumes:'s forms: the short scalar form ("name:/path", optionally with a trailing ":ro"/":rw") and the long mapping form (type/source/target/read_only keys). See parseVolumeShortForm and parseVolumeLongForm.

Jump to

Keyboard shortcuts

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