Documentation
¶
Overview ¶
Package bootstrap is the Go port of the framework-level cluster infrastructure addon system (.lok8s/libs/bootstrap, all 1413 lines — bash wins on any divergence).
It applies spec.bootstrap entries after the cluster is provisioned. Works with ALL drivers (Lo, KubeOne, Capi, Kkp) — not driver-specific.
Entries form a DAG and apply CONCURRENTLY (capped) — ordering edges come from `dependsOn: [name, …]` and `wait: true`; semantics on Engine.Apply.
Addon resolution:
"cilium" → .lok8s/addons/cilium/ "./targets/foo" → clusters/<domain>/targets/foo/ "/absolute/path" → /absolute/path/
Provider-aware values:
addons/cilium/values.yaml — base (always loaded) addons/cilium/values.lo.yaml — driver-specific (if exists) addons/cilium/values.hetzner.yaml — provider-specific (if exists)
Per-entry overrides (map form): the reserved keys values / valueFiles / env / wait / dependsOn / name, plus the legacy whole-map-is-helm-values shim — full schema at ParseEntry. Effective helm-values stack: base < driver < provider < valueFiles < values:.
Index ¶
- Variables
- func ApplyHook(p *config.Paths, r execx.Runner, stdout, stderr io.Writer) func(ctx context.Context, domain, clusterYAML, kubeconfig string) error
- func InlineValues(p *config.Paths, stderr io.Writer, domain, clusterYAML, addon string) (string, error)
- func RecreatePrompt(count int, list string, stuckNS []string) string
- func ResolveEntries(clusterYAML, kind string) ([]string, error)
- type Dispatcher
- type Engine
- type Entry
- type Job
Constants ¶
This section is empty.
Variables ¶
var ErrEntriesFailed = ui.Handled(errors.New("bootstrap: one or more entries failed or were skipped"))
ErrEntriesFailed is Apply's bare non-zero exit (bash: `return 1` at the bottom of bootstrap::apply — the per-entry errors were already printed).
var PlatformOwned = []string{"cilium", "ccm"}
PlatformOwned lists the addon DIRS that must never bootstrap onto a HOSTED cluster (the platform owns them there). Extend here, nowhere else (bash: BOOTSTRAP_PLATFORM_OWNED="cilium ccm").
Functions ¶
func ApplyHook ¶
func ApplyHook(p *config.Paths, r execx.Runner, stdout, stderr io.Writer) func(ctx context.Context, domain, clusterYAML, kubeconfig string) error
ApplyHook returns the provision dispatch's Hooks.BootstrapApply seam — the function provision.Dispatcher calls as bootstrap::apply on both the full-provision and --bootstrap paths. The engine is built per call so each invocation reads the decision env (LOK8S_FORCE_RECREATE, LOK8S_BOOTSTRAP_ONLY, LOK8S_BOOTSTRAP_PARALLEL) at apply time, exactly like the sourced bash lib did.
func InlineValues ¶
func InlineValues(p *config.Paths, stderr io.Writer, domain, clusterYAML, addon string) (string, error)
InlineValues returns the cluster spec's merged inline values for ONE addon, by the SAME semantics the bootstrap applies (bash: bootstrap::inline_values — both entry shapes, `values:` + `valueFiles:` pre-merge, cluster-dir resolution). Returns "" when the spec has no entry for the addon or the entry carries no values. The kubeone driver's render_addons reads this so an addon it renders for `kubeone apply` carries the SAME values the bootstrap would overlay (issue #157: an inline-only value silently reverted at upgrade time). A parse error is a hard error, never a silent empty.
func RecreatePrompt ¶
RecreatePrompt composes the batched recreate prompt (bash: bootstrap::_recreate_prompt). Split out because this text IS the safety control — the single consent gate for every heal in the batch — so it has to be assertable rather than buried in a tty write.
The prompt REPLACES kapply's pointed per-object confirms for accepted entries (they re-apply under force, which returns 0 from both the sealed-Secret and the ns-finalize confirm without asking). Everything those confirms would have said must therefore be said here, or it is not said at all.
func ResolveEntries ¶
ResolveEntries resolves which bootstrap addon entries to apply, one compact-JSON string per element (bash: bootstrap::_resolve_entries — the exact `yq -o=json -I=0 '.spec.bootstrap[]?'` stream shape, so map entries stay one element and YAML comments are gone). Pure (no cluster access). The cases and the per-driver default are bootstrapspec.Resolve's; the lo default is the bare word `cilium`, matching the bash `echo "cilium"`.
Types ¶
type Dispatcher ¶
type Dispatcher struct {
Engine *Engine
// Drivers overrides the driver lookup (nil → the package registry,
// driver.Get) — same seam as provision.Dispatcher.
Drivers func(name string) (driver.Factory, bool)
// InventoryPublish is inventory::publish, refreshed after a standalone
// `lo bootstrap` too (same tail as provision::dispatch — fail-soft,
// probed: bash only calls it when the inventory lib is loaded, so a
// nil hook is SKIPPED).
InventoryPublish func(ctx context.Context, domain, clusterYAML, kubeconfig string)
}
Dispatcher drives the standalone `lo bootstrap` core (bash: bootstrap::dispatch — factored out of main::bootstrap so it is testable without the argsh :args builtin, mirroring provision::dispatch).
func (*Dispatcher) Dispatch ¶
func (d *Dispatcher) Dispatch(ctx context.Context, domainName string) error
Dispatch runs the standalone `lo bootstrap <domain>` flow.
CRITICAL ordering (bash comment preserved): the domain's driver export runs BEFORE Apply — it populates the spec-derived env (LOK8S_SPEC_*) that spec.bootstrap addons reference via envsubst; without it they render those values empty. Same order provision::dispatch uses.
type Engine ¶
type Engine struct {
Paths *config.Paths
Runner execx.Runner
Stdout io.Writer
Stderr io.Writer
// ApplyOne overrides the per-entry apply (tests stub it exactly like
// the bats redefine bootstrap::_apply_one). nil → the real applyOne.
// Returns the entry's exit code (0 ok, non-zero failed).
ApplyOne func(ctx context.Context, job Job, stdout, stderr io.Writer) int
// Interactive/Ask are the batched-recreate prompt's tty seams (bash:
// bootstrap::_interactive / bootstrap::_ask). One place touches
// /dev/tty, which is what makes the prompt TEXT assertable in a test.
Interactive func() bool
Ask func(prompt string) bool
// Sleep is the retry/backoff seam (tests install clock.NoSleep). nil =
// clock.Sleep, which ends with the context.
Sleep clock.SleepFunc
// SopsDecrypt decrypts one restore.d/*.sops.yaml in memory (nil → the
// secrets package's sops library decrypt — NEVER a sops|kubectl pipe).
SopsDecrypt func(path string) ([]byte, error)
}
Engine drives the bootstrap addon system. Zero-value fields fall back to the same env/tty defaults the bash implementation read.
func (*Engine) Apply ¶
Apply reads spec.bootstrap from the cluster YAML and runs the entries as a topological-parallel DAG capped at LOK8S_BOOTSTRAP_PARALLEL (default 8) — bash: bootstrap::apply. Edge semantics:
- `dependsOn: [name, …]` — an explicit edge: wait for those entries' READINESS.
- `wait: true` — a GLOBAL gate: it depends on ALL entries before it, and every entry after it depends on it.
An entry waits for its own workloads to be Ready IFF something depends on it OR it is a wait-gate; a pure leaf just applies and is done.
FAILURE is isolated to the failed entry's SUBTREE — not the whole run: only its TRANSITIVE dependents are skipped; every unrelated entry still applies. A failed wait-gate skips everything after it automatically. This is deliberate bootstrap policy: apply as much as possible, then report non-zero if anything failed or was skipped (so the caller re-runs `lo up` to converge).
type Entry ¶
type Entry struct {
// Raw is the compact-JSON entry as resolved (used verbatim in the
// "addon not found" error, like bash's ${entry}).
Raw string
// Name is the entry identity: basename / map-key, or the explicit
// `name:` override.
Name string
// Dir is the resolved addon directory (never changed by `name:`).
Dir string
// Inline is the merged inline helm values as YAML ("" when none;
// "null" for an explicit `values: null`, matching yq -r).
Inline string
// EnvLines is the newline-separated KEY=value envsubst overrides
// ("" when none) — the exact shape bash hands _apply_one.
EnvLines string
// Wait marks a global barrier gate (`wait: true`).
Wait bool
// Deps are the dependsOn entry names, in order.
Deps []string
// Explicit reports whether Name came from an explicit `name:` override
// (a name collision on it is a hard error, not a tolerated clash).
Explicit bool
// Builtin reports a bare framework-addon entry (Dir resolved through
// internal/assets: the project's .lok8s/addons/<name> when present, else
// the copy embedded in the binary) as opposed to a cluster-local target
// or an absolute path.
Builtin bool
}
Entry is one parsed spec.bootstrap entry (bash: the out-params of bootstrap::_parse_entry).
func ParseEntry ¶
ParseEntry parses ONE spec.bootstrap entry — the compact JSON from ResolveEntries — into the fields the apply path needs (bash: bootstrap::_parse_entry; the shared reader is internal/bootstrapspec, the schema doc lives there). Returns an error (after printing it) on a malformed entry or on `values:` set against a non-chart target. The valueFiles pre-merge (files in list order, inline `values:` on top) uses the SAME deep-merge idiom addons.Render stacks values with (maps deep-merge, lists REPLACE); the result rides render's existing inline-values arg.
type Job ¶
type Job struct {
Name string
Dir string
Kind string
Provider string
// Kubeconfig is the --kubeconfig path threaded through every kubectl.
Kubeconfig string
// Inline is the merged inline helm values ("" when none).
Inline string
// Wait is set when the scheduler wants the post-apply readiness wait:
// a dep-target or a wait-gate. A pure leaf runs without it.
Wait bool
// Hosted marks a hosted cluster: the platform owns the CNI and the
// cloud integration, so those addons are skipped.
Hosted bool
// BootstrapOnly is LOK8S_BOOTSTRAP_ONLY=1: the KubeOne driver did not
// apply cilium/ccm on this run, so the engine reconciles them.
BootstrapOnly bool
// EnvLines is the newline-separated KEY=value envsubst overrides.
EnvLines string
// Force re-applies under LOK8S_FORCE_RECREATE=1 semantics (the
// resolve-parked foreground heal).
Force bool
// NonInteractive marks a backgrounded apply (bash: the exported
// LOK8S_NONINTERACTIVE=1 on every concurrent subshell — no shared
// /dev/tty, so kapply never prompts and never draws).
NonInteractive bool
}
Job is one scheduled bootstrap entry as handed to the per-entry apply (bash: the argv of bootstrap::_apply_one).