Documentation
¶
Overview ¶
Package extension implements lstk's Git-style extension mechanism: when a user runs `lstk <name>` and `<name>` is not a built-in command, lstk resolves and executes an external `lstk-<name>` executable, forwarding arguments, streams, and the exit code. This mirrors Git's `git-<name>` model and lstk's own IaC proxies, and is the only model that cleanly supports closed-source and third-party extensions written in any language: an extension is an opaque binary that never touches the core repository.
Index ¶
Constants ¶
const ( EnvAPIVersion = "LSTK_EXT_API_VERSION" EnvContext = "LSTK_EXT_CONTEXT" )
The runtime-context contract is conveyed to an extension through exactly two environment variables:
- EnvAPIVersion (LSTK_EXT_API_VERSION) — a flat integer kept outside the JSON payload so an extension can check contract compatibility before parsing.
- EnvContext (LSTK_EXT_CONTEXT) — a single JSON object (see Context) carrying the resolved config directory, auth token, non-interactive state, and the list of running emulators.
APIVersion is bumped ONLY on a breaking change (a field removed or repurposed); adding a field does not bump it. Extensions therefore detect additive fields by their presence in the JSON object — not via the version — and use the version only to refuse a contract generation they predate. So any field added after version 1 must be distinguishable when absent (omitempty / pointer / null) for that presence check to work.
const APIVersion = 1
APIVersion is the integer version of the LSTK_EXT_* runtime-context contract that this lstk implements. It is exposed to extensions as LSTK_EXT_API_VERSION. Bump it only when a variable is removed or repurposed; adding a new variable is an additive change that keeps the same version.
const DescriptionsFileName = "lstk-extensions.toml"
DescriptionsFileName is the static, hand-authored file shipped alongside the bundled extensions that maps a bundled extension's command name to a one-line description for help rendering. It is a single LocalStack-controlled file (not a per-extension manifest), version-locked to the bundled binaries and validated against them at release time. Its TOML body is a flat table of name = "description" entries, e.g.:
deploy = "Deploy your application to LocalStack"
const NamePrefix = "lstk-"
NamePrefix is the executable-name prefix that identifies an extension: an executable named "lstk-<name>" provides the "<name>" extension.
Variables ¶
var ErrNotFound = errors.New("extension not found")
ErrNotFound is returned by Resolve when no matching extension executable exists in the bundled directory or on PATH.
Functions ¶
func BundledDir ¶
BundledDir returns the directory in which lstk looks for bundled extensions: the directory containing the symlink-resolved lstk executable. Resolving symlinks is what makes this work through npm `.bin` links and Homebrew shims, where the invoked `lstk` is a link to the real binary living next to its bundled siblings. It returns "" when the executable path cannot be resolved.
func Invoke ¶
Invoke executes the resolved extension with args forwarded unmodified, connecting the child's stdin/stdout/stderr to lstk's own so the user's terminal is wired straight through. The runtime context is layered on the inherited host environment.
The invocation is wrapped in an OpenTelemetry span recording the extension name, whether it was bundled, and the exit code, so extension usage is visible when telemetry is enabled (LSTK_OTEL); when telemetry is disabled the global no-op tracer makes this free and emits nothing. lstk does not inject trace context into the extension process, so an extension's own spans do not yet nest under lstk's trace (deferred).
A non-zero exit from the extension is wrapped as a silent error carrying the *exec.ExitError, so the top-level handler propagates the child's exit code as lstk's own (via main.go's errors.As check) without printing an extra lstk-level error line over the extension's output. Modelled on the IaC proxies' exec path.
func LoadDescriptions ¶
LoadDescriptions reads the bundled descriptions file from dir and returns a map of extension command name to one-line description. A missing or unreadable file degrades to an empty map without error, so help rendering never fails on account of descriptions. dir is the bundled-extensions directory; an empty dir yields an empty map.
Types ¶
type Context ¶
type Context struct {
ConfigDir string `json:"configDir"`
AuthToken string `json:"authToken,omitempty"`
NonInteractive bool `json:"nonInteractive"`
JSON bool `json:"json"`
Emulators []Emulator `json:"emulators"`
}
Context is the resolved runtime context lstk conveys to an extension, rendered as the LSTK_EXT_CONTEXT JSON object. The command boundary populates it (resolving running emulators, config dir, auth token, interactivity, and the resolved --json flag) and Environ renders it. An empty AuthToken is omitted from the JSON; Emulators is always present, marshalling to [] when no emulator is running so an extension always decodes a list.
func (Context) Environ ¶
Environ layers the resolved contract on top of the inherited host environment base (typically os.Environ()), returning a new slice suitable for exec.Cmd.Env. The host environment is preserved so extensions inherit the user's PATH, locale, and tool configuration; only LSTK_EXT_API_VERSION and LSTK_EXT_CONTEXT are added. Any inherited LSTK_EXT_* is stripped first so a stray value cannot shadow lstk's resolved context.
type Emulator ¶
type Emulator struct {
Type string `json:"type"` // emulator type, e.g. "aws", "snowflake", "azure"
Endpoint string `json:"endpoint"` // full URL, e.g. "http://localhost:4566"
Port string `json:"port"` // resolved host port, e.g. "4566"
}
Emulator describes one running LocalStack emulator in the context payload.
type Extension ¶
Extension is a resolved extension executable: its command name (the part after the "lstk-" prefix) and the absolute path to the executable that provides it. Bundled reports whether it was resolved from the bundled-extensions directory (which ships with lstk and takes precedence over PATH) rather than from PATH.
func NewExtension ¶
NewExtension returns an Extension for the given command name and executable path.
type Resolver ¶
type Resolver struct {
BundledDir string
// contains filtered or unexported fields
}
Resolver discovers and resolves extension executables. It searches the bundled-extensions directory (BundledDir) before PATH, so a bundled extension wins over a same-named executable on PATH. A zero BundledDir disables the bundled search (used in tests that exercise only the PATH path).
func NewResolver ¶
NewResolver returns a Resolver whose bundled-extensions directory is derived from the symlink-resolved location of the running lstk executable, so it is found even when lstk is invoked through a symlink or package shim.