Skip to main content

Authoring

Extension seams

Bring your own content without forking the repo.

docs/authoring/extensions.md
On this page

SnowOps Labs keeps a small, documented seam so custom or private builds can plug in behaviour without forking the engine. Everything in the open repository behaves identically whether or not anything is injected — no business logic ever enters the open engine.

This package is part of the public SDK and is CODEOWNERS-locked:

  • pkg/extension — where content comes from (resolvers) and what happens around lifecycle phases (hooks).

The entitlement seam and the OCI/pack resolver that used to live here were removed in v2 — see ADR-0001 and ADR-0008. Content distribution is now plain directories plus SNOWOPS_CONTENT_PATH.

Resolvers — where content comes from

type Resolver interface {
    CanResolve(ref string) bool
    Resolve(ctx context.Context, ref, destDir string) error
}

A Chain tries each resolver in order and uses the first that reports CanResolve. The built-in open resolvers are:

Resolver Handles
GitResolver https://…, git+https://…@ref, ssh URLs — clones a shallow content snapshot and drops .git
LocalResolver file://… and existing local directories — copies the tree

DefaultResolver() returns Chain{GitResolver{}, LocalResolver{}}.

To add a source, implement the interface and put it ahead of the chain:

chain := append(extension.Chain{myResolver{}}, extension.DefaultResolver()...)

GitResolver takes a GitRunner so tests can inject a stub instead of shelling out to real git.

Hooks — around lifecycle phases

type Hooks interface {
    PreStage(ctx context.Context, ev Event) error
    PostStage(ctx context.Context, ev Event) error
}

DefaultHooks() is a no-op. A Pre* hook returning an error aborts the phase, so a custom build can enforce policy without the engine knowing the policy exists. Hooks receive an Event naming the scenario and stage.

Rules for anything built on these seams

  • The open engine must behave identically with the default implementations.
  • Custom logic lives in a separate repository and is injected at construction — never merged into the open engine.
  • Changes to these interfaces follow the SDK & Schema Stability Policy.