Documentation
¶
Overview ¶
Package hook is the internal Hook dispatch implementation. It owns:
- Registry the in-memory data store mapping (Stage|Event) -> registered hooks for fast dispatch
- Install(root, …) the entry point that wraps every command's RunE so Before/After Observers and Wrap chains fire around the command's business logic, including the denial guard that physically isolates pruned commands from Wrap.
- Emit(event, …) the lifecycle event firing helper used by the Bootstrap pipeline.
Plugins NEVER import this package -- they only ever see extension/platform. The Registrar contract is implemented inside internal/platform, which delegates to this Registry after validating the plugin's calls (staging + atomic commit).
Index ¶
- func ComposeWrappers(ws []platform.Wrapper) platform.Wrapper
- func Emit(ctx context.Context, reg *Registry, event platform.LifecycleEvent, ...) error
- func Install(root *cobra.Command, reg *Registry, snapshot CommandViewSource)
- func SetStderrForTesting(w io.Writer) (restore func())
- type CommandViewSource
- type LifecycleEntry
- type LifecycleError
- type ObserverEntry
- type Registry
- func (r *Registry) AddLifecycle(e LifecycleEntry)
- func (r *Registry) AddObserver(e ObserverEntry)
- func (r *Registry) AddWrapper(e WrapperEntry)
- func (r *Registry) LifecycleHandlers(event platform.LifecycleEvent) []LifecycleEntry
- func (r *Registry) Lifecycles() []LifecycleEntry
- func (r *Registry) MatchingObservers(cmd platform.CommandView, when platform.When) []ObserverEntry
- func (r *Registry) MatchingWrappers(cmd platform.CommandView) []WrapperEntry
- func (r *Registry) Observers() []ObserverEntry
- func (r *Registry) Wrappers() []WrapperEntry
- type WrapperEntry
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ComposeWrappers ¶
ComposeWrappers folds a slice of Wrappers into a single Wrapper that applies them in registration order (outermost first). Empty slice returns the identity Wrapper (next as-is). Inspired by grpc.ChainUnaryInterceptor.
func Emit ¶
Emit fires every LifecycleHandler registered for event in registration order. lastErr is propagated to handlers via LifecycleContext.Err (typical use: Shutdown handlers see the error the command exited with).
Behaviour by event:
Startup: any handler returning a non-nil error aborts the bootstrap (caller decides whether to fail-closed). The first such error is returned as *LifecycleError.
Shutdown: handler errors are logged but do not affect the returned error; the framework also caps the total time at shutdownDeadline.
func Install ¶
func Install(root *cobra.Command, reg *Registry, snapshot CommandViewSource)
Install wraps every runnable command's RunE so the hook chain fires around it. The wrapper is:
Before observers (always run, panic-safe)
denial guard:
if cmd is denied -> denyStub returns its CommandDeniedError
else -> compose(matched Wrappers)(originalRunE) runs
After observers (always run, panic-safe, sees inv.Err)
Critical invariants enforced here (constraint #2):
**Denied commands NEVER reach the Wrap chain.** The guard runs denyStub directly so no plugin Wrapper can suppress or rewrite the denial. Observers still fire (audit must see the attempted call), but Wrap is physically out of the path.
**After observers always fire**, even when RunE returned an error. Wrap short-circuits via AbortError get converted to *output.ExitError so cmd/root.go emits the right envelope.
**Denial layer / source are populated from cobra annotations before any hook fires.** populateInvocationDenial reads the annotations attached by cmdpolicy.Apply and strictModeStubFrom, avoiding an import cycle between hook and cmdpolicy.
Install must be called once during the Bootstrap pipeline after policy pruning has finished. Calling it twice on the same tree is a bug (each command's RunE would be wrapped multiple times).
func SetStderrForTesting ¶
SetStderrForTesting redirects the hook layer's warning output to a custom writer and returns a restore function the caller MUST defer (or pass to `t.Cleanup`). Without the restore step, a later test in the same binary would inherit the override and either race on a shared bytes.Buffer or write user-visible garbage into a real test stderr.
Production code never calls this; the default writer is os.Stderr via defaultStderr.
Types ¶
type CommandViewSource ¶
type CommandViewSource interface {
View(cmd *cobra.Command) platform.CommandView
}
CommandViewSource resolves a *cobra.Command into a CommandView. The default implementation returns a live view over the cobra node; strict-mode's replacement stubs (cmd/prune.go) carry the original command's annotations forward so the view keeps reporting accurate Risk / Identities / Domain after replacement.
type LifecycleEntry ¶
type LifecycleEntry struct {
Name string
Event platform.LifecycleEvent
Fn platform.LifecycleHandler
}
LifecycleEntry stores one lifecycle handler. Selector is unused (lifecycle events are global), but Name is preserved for diagnostics.
type LifecycleError ¶
type LifecycleError struct {
Event platform.LifecycleEvent
HookName string
Panic bool
Cause error
}
LifecycleError is the typed failure returned by Emit for non-Shutdown events when a LifecycleHandler returns an error or panics. Callers can errors.As to extract HookName, Event, and the Panic discriminator (panic vs returned error) so the envelope writer can produce distinct reason_code values:
- Panic == false -> reason_code = "lifecycle_failed"
- Panic == true -> reason_code = "lifecycle_panic"
Shutdown handler failures are logged inside emitShutdown and never returned through this type (Shutdown is non-recoverable; the contract is "best effort, never block exit").
func (*LifecycleError) Error ¶
func (e *LifecycleError) Error() string
func (*LifecycleError) Unwrap ¶
func (e *LifecycleError) Unwrap() error
type ObserverEntry ¶
type ObserverEntry struct {
Name string
When platform.When
Selector platform.Selector
Fn platform.Observer
}
ObserverEntry stores one Observer registration. The full hook name (already namespaced with plugin prefix by the caller) lets diagnostic output point at the responsible plugin.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds all registered hooks. The framework constructs one Registry per binary execution; concurrent reads after Install commits are safe because the maps are not mutated thereafter. Writes (during Install) are serialised by the internalplatform.
func (*Registry) AddLifecycle ¶
func (r *Registry) AddLifecycle(e LifecycleEntry)
AddLifecycle registers a LifecycleHandler.
func (*Registry) AddObserver ¶
func (r *Registry) AddObserver(e ObserverEntry)
AddObserver registers an Observer. Caller is responsible for namespacing (the platformhost does this). Nil fn is silently skipped -- the staging Registrar should reject invalid registrations before this layer.
func (*Registry) AddWrapper ¶
func (r *Registry) AddWrapper(e WrapperEntry)
AddWrapper registers a Wrapper.
func (*Registry) LifecycleHandlers ¶
func (r *Registry) LifecycleHandlers(event platform.LifecycleEvent) []LifecycleEntry
LifecycleHandlers returns handlers for a given event in registration order.
func (*Registry) Lifecycles ¶
func (r *Registry) Lifecycles() []LifecycleEntry
Lifecycles returns a snapshot of all registered lifecycle handlers.
func (*Registry) MatchingObservers ¶
func (r *Registry) MatchingObservers(cmd platform.CommandView, when platform.When) []ObserverEntry
MatchingObservers returns the observers whose selector matches the command at the given When stage. Result is a slice (not a generator) so callers can iterate without holding the registry lock.
func (*Registry) MatchingWrappers ¶
func (r *Registry) MatchingWrappers(cmd platform.CommandView) []WrapperEntry
MatchingWrappers returns the wrappers whose selector matches the command. Order matches registration order.
func (*Registry) Observers ¶
func (r *Registry) Observers() []ObserverEntry
Observers returns a snapshot of all registered observers. Order is registration order. Diagnostic commands (config plugins show) call this to enumerate every hook attached to the binary.
func (*Registry) Wrappers ¶
func (r *Registry) Wrappers() []WrapperEntry
Wrappers returns a snapshot of all registered wrappers. Order is registration order (outermost first).