operator

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package operator is the Go port of the shell-operator hooks (operator/hooks/*.sh, frozen at .archive/legacy/operator/hooks/): the three reconcilers behind `lo operator <hook>`, which the hook shims exec.

The shell-operator hook contract, unchanged: `--config` prints the binding configuration (byte-identical to the bash heredocs — pinned against testdata/*.config.yaml, which were generated ONCE from the bash hooks); otherwise the hook reads $BINDING_CONTEXT_PATH, a JSON array of events, and reconciles. Every kubectl/clusterctl call runs through the execx.Runner seam with the exact argv the bash built, so the recorded call log in a test IS the bash KLOG assertion.

Bash wins: where the bash body did something odd (patch bodies, log prefixes, fall-through on a failed sub-step), the port does the same and the comment says so. The one structural difference is how the hook bodies reach the framework: the bash sourced the driver + libs into its shell and called driver::provision / bootstrap::apply / gitops::bootstrap / deploy::apply; the Go calls the ported packages (internal/driver/lo, internal/bootstrap, internal/gitops, internal/deploy) through the same injectable seams the dispatch layer uses.

Index

Constants

View Source
const (
	// DefaultHookDir is where the image mounts the hook tree; the framework
	// pieces the bash sourced live under it (lib/, drivers/, addons/,
	// capi-templates/ …), so it doubles as PATH_LOK8S.
	DefaultHookDir = "/hooks"
	// DefaultStateDir is LOK8S_STATE_DIR's default: the writable volume laid
	// out like a lok8s project (clusters/<domain>/cluster.lok8s.yaml,
	// .kubeconfig/<cluster>.yaml).
	DefaultStateDir = "/var/lib/lok8s"
	// DefaultKustomizePluginHome is the image's khelm plugin root.
	DefaultKustomizePluginHome = "/usr/local/kustomize-plugins"
)

Defaults of the operator runtime (runtime.sh + operator/Dockerfile).

View Source
const CapiFinalizer = "lok8s.dev/capi-teardown"

CapiFinalizer intercepts deletion so the workload cluster is torn down before the Capi CR vanishes (otherwise the applied CAPI Cluster + machines + infra are orphaned and keep the cloud cluster — and its bill — alive).

View Source
const LoFinalizer = "lok8s.dev/lo-teardown"

LoFinalizer guards Lo deletion until the driver tore the cluster down.

Variables

View Source
var ErrHandled = ui.ErrHandled // one sentinel for every package; see internal/ui

ErrHandled marks a failure whose message was already printed on stderr (the CLI exits 1 without printing more).

Functions

func BuildStatusPatch

func BuildStatusPatch(status any) (string, error)

BuildStatusPatch renders the Capi status patch for one CAPI status (the jq construction in hook::trigger), pretty-printed as jq prints it.

func MapPhase

func MapPhase(phase string) string

MapPhase maps a CAPI Cluster phase to the lok8s phase.

Types

type CapiHook

type CapiHook struct {
	Paths  *config.Paths
	Runner execx.Runner
	Stdout io.Writer
	Stderr io.Writer

	// TemplateDir is the CAPI template tree (bash: ${HOOK_DIR}/capi-templates;
	// Env.CapiTemplateDir in the CLI).
	TemplateDir string
	// contains filtered or unexported fields
}

CapiHook reconciles Capi CRs.

func (*CapiHook) Config

func (h *CapiHook) Config() string

Config implements Hook.

func (*CapiHook) Reconcile

func (h *CapiHook) Reconcile(ctx context.Context, object []byte) error

Reconcile converges one Capi object (full JSON) toward its spec (capi_hook::reconcile) — provision when live, finalizer-guarded teardown when it is being deleted. Never fails (see LoHook.Reconcile).

func (*CapiHook) Trigger

func (h *CapiHook) Trigger(ctx context.Context, events []Event) error

Trigger implements Hook (hook::trigger).

type CapiStatusSyncHook

type CapiStatusSyncHook struct {
	Paths  *config.Paths
	Runner execx.Runner
	Stdout io.Writer
	Stderr io.Writer

	// GitopsBootstrap is gitops::bootstrap (gitops.BootstrapHook in the
	// CLI). nil = the lib is not loaded: the bash `declare -f` probe then
	// skips BOTH the bootstrap and the gitops status patch.
	GitopsBootstrap func(ctx context.Context, domain, provider string) error
	// DeployApply is deploy::apply (deploy.Deployer.Apply in the CLI). nil =
	// not loaded → probed away, like the bash.
	DeployApply func(ctx context.Context, domain string) error
}

CapiStatusSyncHook bridges CAPI Cluster status → Capi CR status.

func (*CapiStatusSyncHook) Config

func (h *CapiStatusSyncHook) Config() string

Config implements Hook.

func (*CapiStatusSyncHook) Trigger

func (h *CapiStatusSyncHook) Trigger(ctx context.Context, events []Event) error

Trigger implements Hook (hook::trigger).

type Env

type Env struct {
	// HookDir is the hook tree (bash: RUNTIME_DIR / HOOK_DIR → PATH_LOK8S).
	HookDir string
	// StateDir is the reconcile state volume (bash: LOK8S_STATE_DIR →
	// PATH_BASE).
	StateDir string
	// KustomizePluginHome is KUSTOMIZE_PLUGIN_HOME (defaulted, never
	// overridden when set).
	KustomizePluginHome string
}

Env is the operator runtime layout (runtime.sh's framework-env block).

func ResolveEnv

func ResolveEnv() *Env

ResolveEnv reads the runtime layout from the process environment: PATH_LOK8S (the hook tree; the bash derived it from the hook file's own directory, which the Go binary — installed under /usr/local/bin — cannot), LOK8S_STATE_DIR and KUSTOMIZE_PLUGIN_HOME, each with the runtime.sh / Dockerfile default.

func (*Env) CapiTemplateDir

func (e *Env) CapiTemplateDir() string

CapiTemplateDir is where the image copies the CAPI templates for the capi-reconcile hook (Dockerfile: `COPY .lok8s/drivers/capi/cluster/ /hooks/capi-templates/`; bash: `${HOOK_DIR}/capi-templates`).

func (*Env) Export

func (e *Env) Export() error

Export mirrors runtime.sh's exports + mkdirs: the framework env every subprocess (kubectl, kustomize plugins, the lo driver's tools) and every ported lib reading os.Getenv sees. PATH_SECRETS is the runtime's flat store under the state volume (runtime.sh: `${PATH_BASE}/.secrets`).

func (*Env) Paths

func (e *Env) Paths() *config.Paths

Paths is the project layout the framework packages read, over the state volume (runtime.sh: PATH_BASE=state, PATH_CLUSTERS=state/clusters, PATH_LOK8S=hook dir). Bin points below the state dir — the image has no b-managed toolchain; execx falls back to PATH, where the image's tools live.

type Event

type Event struct {
	// Type is the event type: "Synchronization", "Schedule", "Event" — the
	// bash read `.type // "Event"`, so a missing type is an Event.
	Type string `json:"type"`
	// Binding is the binding name (schedule bindings carry it).
	Binding string `json:"binding"`
	// Object is the watched object (after the jqFilter, for kubernetes
	// bindings).
	Object json.RawMessage `json:"object"`
	// FilterResult is the jqFilter's output (capi-status-sync reads it).
	FilterResult json.RawMessage `json:"filterResult"`
}

Event is one entry of the shell-operator binding context. Only the fields the hooks read are decoded; the rest of the entry is ignored, as the jq paths ignored it.

func ReadBindingContext

func ReadBindingContext(stderr io.Writer, path string) ([]Event, error)

ReadBindingContext reads the events file shell-operator hands the hook. An unset BINDING_CONTEXT_PATH is the bash `set -u` abort (unbound variable, exit 1); an unreadable file is jq's "Could not open file" (exit 2); a non-JSON file is jq's parse error (exit 5). The message goes to stderr here and the error carries the status (ExitError).

func (Event) EventType

func (e Event) EventType() string

EventType is `.type // "Event"`.

type ExitError

type ExitError struct{ Code int }

ExitError carries the process exit status a bash hook would have ended with (its message is already on stderr): the `set -u` abort is 1, a jq failure is jq's own status — 2 for a usage/system error (file not found, a non-JSON --argjson), 5 for invalid JSON input.

func (*ExitError) Error

func (e *ExitError) Error() string

func (*ExitError) ExitCode

func (e *ExitError) ExitCode() int

ExitCode is the process exit status the error maps to.

type Hook

type Hook interface {
	// Config is the `--config` output (hook::config).
	Config() string
	// Trigger handles one binding-context batch (hook::trigger).
	Trigger(ctx context.Context, events []Event) error
}

Hook is one shell-operator hook.

type LoHook

type LoHook struct {
	Paths  *config.Paths
	Runner execx.Runner
	Stdout io.Writer
	Stderr io.Writer

	// Drivers overrides the driver lookup (nil → driver.Get). The bash
	// sourced drivers/lo/main unconditionally — the hook is the Lo
	// reconciler, so the kind is fixed: "lo".
	Drivers func(name string) (driver.Factory, bool)

	// BootstrapApply is bootstrap::apply (bootstrap.ApplyHook in the CLI).
	// nil = the lib is not loaded: the bash `driver::provision &&
	// bootstrap::apply` then fails on "command not found" and the CR lands
	// in ProvisionFailed — same here.
	BootstrapApply func(ctx context.Context, domain, clusterYAML, kubeconfig string) error
	// contains filtered or unexported fields
}

LoHook reconciles Lo CRs.

func (*LoHook) Config

func (h *LoHook) Config() string

Config implements Hook.

func (*LoHook) Reconcile

func (h *LoHook) Reconcile(ctx context.Context, object []byte) error

Reconcile converges one Lo object (full JSON) toward its spec (lo_hook::reconcile). It never fails: the bash body ran with errexit suspended and its last statement always returned 0 — the caller's "warn: reconcile failed" line is unreachable there too.

func (*LoHook) Trigger

func (h *LoHook) Trigger(ctx context.Context, events []Event) error

Trigger implements Hook (hook::trigger): Schedule/Synchronization events re-list everything, any other event reconciles its object.

Jump to

Keyboard shortcuts

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