Documentation
¶
Overview ¶
Package discovery parses each stack's per-service podman-compose.yaml files and exposes the result as a structured model (services, targets, infra, volumes, networks, dependency graph). The result is the daemon's source of truth for everything operational — every later phase (supervision, env, logs, volumes) keys off these types.
The compose contract ¶
A service's builds/podman-compose.yaml doubles as the daemon's service manifest. The conventions below are what make discovery work; they hold for every service repo, so service compose files don't re-document them:
- Every cmd/<target> entrypoint has a compose mirror named <service>-<target> gated behind profiles: ["<target>"]. The profile keeps targets out of a plain `podman compose up`: the daemon runs targets itself (go-exec by default, the profiled container on demand) and orchestrates ordering through depends_on, never through Compose's profile cascade. It also prevents a containerized copy from binding the host port a locally-run target owns.
- Compose services without a profile are infrastructure (databases, mailservers): always brought up, never run as targets.
- A target with a healthcheck (compose or Dockerfile) is a long-runner; one without is a one-shot. One-shots run to completion on infra-up and must be idempotent — they re-run on every infra-up cycle.
- Sibling services are never declared in each other's compose files. Each service describes only its own infra; cross-service connections resolve through daemon-allocated env (<SIBLING>_<VAR> references).
- Constants (credentials, test keys) are inlined; values the daemon allocates or derives (ports, hosts, URLs, DSNs) appear as ${VAR} references the daemon fills.
Discovery is deliberately strict: contract violations surface as errors at daemon startup, so misconfigurations never become runtime surprises. The one place it is lenient is healthcheck classification — see the kind derivation in discovery.go for why.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RenderTopology ¶
RenderTopology returns an ASCII tree of the service's dependency graph, formatted for `a-novel run topology` output. One service per invocation; the daemon RPC handler concatenates per service when --service is omitted.
Layout puts infra first (it's the root), then one-shots (in compose-graph order), then long-runners, with each leaf annotated by current phase + (mode).
func ValidateEnvRefs ¶
func ValidateEnvRefs(stacks []*Stack)
ValidateEnvRefs scans every compose env block in `disc` for ${VAR} references the daemon doesn't know how to fill, and appends a non-fatal warning to the stack's Errors list. Called at end of discovery so every parse error and validation warning land in one stream the daemon prints at startup.
What's "resolvable":
- `*_PORT` → allocated by the daemon
- `*_HOST`, `*_URL` → synthesized from `*_PORT` (same service)
- `POSTGRES_DSN` → synthesized when POSTGRES_PORT allocated
- `<SERVICE>_<VAR>` → cross-service reference, if <SERVICE> is a known service in this stack
- Any key declared as a literal (no `${...}` references) in the SAME env block — that's a sibling constant
Anything else gets a warning: "compose service X references ${VAR} but the daemon can't fill it". The compose will still substitute with an empty string at runtime; this warning surfaces the gap.
Types ¶
type DiscoveryError ¶
type DiscoveryError struct {
Service string // service name within the stack
Path string // file or directory path the error points at
Reason string // human-readable explanation
}
DiscoveryError is a non-fatal classification problem (orphan compose service, missing compose mirror for a cmd/X, etc.). Collected per stack and surfaced via Status so the user can fix them without preventing daemon startup. Truly fatal errors (can't read the stack path) bubble up as the function's error.
func (DiscoveryError) Error ¶
func (e DiscoveryError) Error() string
type Infra ¶
type Infra struct {
Name string // compose service name, e.g., "postgres-json-keys"
Service string // owning service name
Stack string // owning stack name
Dockerfile string // referenced Dockerfile (for build)
HasHealthcheck bool
DependsOn []string
Ports []string
Environment map[string]string
Volumes []string // volume mounts the infra declares
}
Infra is a compose service with no profile assignment and no matching cmd/<name>/ directory.
type Service ¶
type Service struct {
Name string // e.g., "service-json-keys"
Stack string // owning stack name
Path string // absolute path to the service directory
ComposePath string // absolute path to its compose file
Targets []*Target
Infra []*Infra
Volumes []*Volume
Networks []string // network names declared at compose top-level
// Dependency edges, one per compose `depends_on` entry. Each edge
// connects two compose-service names (target or infra); resolution
// to *Target/*Infra is done by ResolveDeps at the daemon level.
DependsOn map[string][]string // compose-service-name → [list of names it depends on]
}
Service is one app/service-* directory, with everything we discovered about its compose file.
type Stack ¶
type Stack struct {
Name string
Path string
Default bool
Services []*Service
// Errors collected during discovery for this stack. Non-fatal —
// surfaced via Status / startup logs so the user can fix them without
// the daemon refusing to start outright. Strict-failure cases (the
// stack path doesn't exist at all) are returned as the function's
// error, not added here.
Errors []DiscoveryError
}
Stack is the discovered shape of one registered git checkout. It owns the services found under <path>/app/service-*.
func DiscoverStacks ¶
DiscoverStacks parses every service in every registered stack. Returns one Stack per input, with Services populated and DiscoveryErrors collected per stack. A truly inaccessible stack path returns an error (no Stack entry for it); a stack with malformed individual services returns the Stack with the bad services represented in Errors.
type Target ¶
type Target struct {
Name string // e.g., "rest" (cmd directory name)
ComposeName string // e.g., "service-json-keys-rest" (compose service name)
Service string // owning service name
Stack string // owning stack name
Profile string // compose profile name (same as Name by convention)
Kind TargetKind // OneShot | LongRunner
CmdDir string // absolute path to cmd/<name>/
Dockerfile string // absolute path to the build's Dockerfile (may be "")
DependsOn []string // compose-service-names this target depends_on
Ports []string // raw "${VAR}:N" port mappings (parsed by phase 4)
Environment map[string]string
}
Target is a discovered Go cmd/<name>/ entry with its compose mirror.
type TargetKind ¶
type TargetKind int
TargetKind classifies a target as one-shot or long-runner.
const ( TargetKindUnknown TargetKind = iota TargetKindOneShot // no HEALTHCHECK in compose OR Dockerfile TargetKindLongRunner // has HEALTHCHECK in compose OR Dockerfile )
func (TargetKind) String ¶
func (k TargetKind) String() string
String renders the kind in the shape the proto enum uses.