hook

package
v1.0.44 Latest Latest
Warning

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

Go to latest
Published: May 29, 2026 License: MIT Imports: 10 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func ComposeWrappers

func ComposeWrappers(ws []platform.Wrapper) platform.Wrapper

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

func Emit(ctx context.Context, reg *Registry, event platform.LifecycleEvent, lastErr error) error

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

func SetStderrForTesting(w io.Writer) (restore func())

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 NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty Registry.

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).

type WrapperEntry

type WrapperEntry struct {
	Name     string
	Selector platform.Selector
	Fn       platform.Wrapper
}

WrapperEntry stores one Wrapper registration. Wrappers compose in registration order; the outermost (registered first) runs first.

Jump to

Keyboard shortcuts

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