extension

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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"
View Source
const NamePrefix = "lstk-"

NamePrefix is the executable-name prefix that identifies an extension: an executable named "lstk-<name>" provides the "<name>" extension.

Variables

View Source
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

func BundledDir(logger log.Logger) string

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

func Invoke(ctx context.Context, ext *Extension, args []string, runCtx Context) error

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

func LoadDescriptions(dir string, logger log.Logger) map[string]string

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

func (c Context) Environ(base []string) ([]string, error)

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

type Extension struct {
	Name    string
	Path    string
	Bundled bool
}

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

func NewExtension(name, path string, bundled bool) *Extension

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

func NewResolver(logger log.Logger) *Resolver

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.

func (*Resolver) List

func (r *Resolver) List() []Extension

List returns the extensions resolvable from the bundled directory and PATH, de-duplicated by command name with bundled-then-PATH precedence (so a bundled extension shadows a same-named PATH executable), sorted by command name. It never executes an extension.

func (*Resolver) Resolve

func (r *Resolver) Resolve(name string) (*Extension, error)

Resolve returns the extension for the given command name, searching the bundled directory first and then PATH. It returns ErrNotFound when no matching executable exists anywhere.

Jump to

Keyboard shortcuts

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