Skip to content

Latest commit

 

History

History
84 lines (68 loc) · 3.59 KB

File metadata and controls

84 lines (68 loc) · 3.59 KB

Writing a provider

Providers are the modular source side of envkit. There are two ways to add one, and both are deliberately small.

Tier 1 — built-in Go module (~60 lines + 1 registry line)

Implement three methods in a new package under internal/provider/<name>/:

type Provider interface {
    Name() string
    // Called at most once per run (memoized). MUST return promptly and MUST
    // NOT prompt or open a browser when opts.Interactive is false — a locked
    // vault is a Status{Reason, Hint}, not a hang.
    Probe(ctx context.Context, opts provider.ProbeOpts) provider.Status
    // Resolve one ref (everything after "name:" in the manifest's `from`).
    // You own the ref grammar. Return provider.ErrNotFound for a missing ref
    // at a healthy provider, provider.ErrUnavailable when the provider
    // itself is unusable.
    Resolve(ctx context.Context, ref string) (secret.Value, error)
}

Optional capabilities, detected by type assertion:

Interface Enables
BulkProvider.ResolveMap(ctx, ref) feeding [[envfile]] sources (many vars from one ref)
CheckProvider.Check(ctx, ref) device-bound presence checks (to = "check")
LoginProvider.Login(ctx) envkit login <name> (the ONLY place prompting is allowed)

Register it in internal/cli/root.go's provider.NewRegistry(...) call. That's the whole contribution surface. Ground rules:

  • Shell out through execx.Runner (never os/exec directly) so tests can script your binary with execx.NewFake() — see internal/provider/bw for the pattern, internal/provider/awssm for an SDK-based provider with a mockable API interface.
  • Never log secret material; secret.Value redacts itself under %v/%s, keep it that way by returning values only as secret.Value.
  • Configuration (profiles, folder allow-lists) comes in through your constructor from the repo's committed [providers.<name>] table (merged with envkit.local.toml overrides) — providers never read config files themselves.

Tier 2 — exec plugin (zero envkit changes)

Any executable named envkit-provider-<name> on PATH is a provider; manifests use from = "<name>:<ref>" exactly like a built-in. This is the right tier for org-internal providers that shouldn't live in this repo.

Protocol v1 (ENVKIT_PLUGIN_PROTOCOL=1):

stdin:  one JSON line   {"op":"probe"}  or  {"op":"resolve","ref":"<ref>"}
stdout: probe   → JSON  {"ready":bool,"reason":"…","hint":"…"}
        resolve → raw secret bytes (nothing else)
stderr: diagnostics only — never secret material
exit:   0 ok · 40 ref not found · anything else = provider unavailable

A complete plugin in shell:

#!/usr/bin/env bash
read -r req
case $req in
  *'"probe"'*)   echo '{"ready":true}' ;;
  *'"resolve"'*) ref=$(jq -r .ref <<<"$req")
                 pass show "$ref" | head -1 | tr -d '\n' || exit 40 ;;
esac

Seeded providers

Provider Ref grammar Notes
rbw folder/item[#field] Bitwarden via the rbw agent; [providers.rbw] folder-allow scoping
bw item[#field] official Bitwarden CLI; field = password (default), username, uri, totp, notes, or a custom field; session via $BW_SESSION
aws-sm secret-id[#json-key] AWS Secrets Manager; {repo} expands from git origin; bulk-capable for [[envfile]] sources; profile/region from [providers.aws-sm]
generate hex32|hex64|b64-32|uuid per-instance, generated once, never regenerated
detect tailscale-ip|git-name|git-email machine-local derivation
check dir=<path> device-bound login presence