Concepts

You annotate config struct fields with source: tags. mamori parses each tag into a ref (or a precedence chain of refs), a provider resolves each ref to a Value, and a reconciler applies validated results as monotonically versioned snapshots you can pin.

The mental model

  • Ref - a parsed pointer to a value in a provider, produced from a source tag by ParseRef. See the grammar below.
  • Provider - resolves a ref to a Value (bytes plus metadata). One provider per scheme (aws-sm, vault, env, file, …).
  • Reconciler - the goroutine behind every Watcher[T]. It watches every ref, decodes and validates the result, and publishes a new snapshot on each accepted update. Get, Status, Pin, and OnChange all read what it publishes.

The source ref grammar

A ref is produced from a source tag by ParseRef. The grammar is:

<scheme>://<path>[#<key>][?<opt>=<v>&...]

Opaque schemes such as env: and exec: take everything after the colon as the path (no //):

type Config struct {
	// whole secret string
	APIKey     secret.String `source:"aws-sm://prod/api-key"`
	// one key of a JSON secret
	DBPassword secret.String `source:"aws-sm://prod/db#password"`
	// a nested key, addressed with an RFC 6901 JSON Pointer fragment
	DBUser     string        `source:"aws-sm://prod/db#/credentials/user"`
	// provider option
	Leased     secret.String `source:"vault://kv/data/api#key?renew=true"`
	// opaque scheme
	LogLevel   string        `source:"env:LOG_LEVEL"`
	// absolute file path
	Cert       []byte        `source:"file:///etc/tls/tls.crt"`
}

ParseRef produces a Ref{Scheme, Path, Key, Opts, Raw}. #key selects one field from a structured (JSON) payload - either a literal top-level key, or, when the fragment begins with /, an RFC 6901 JSON Pointer addressing a value at any depth (see Ref grammar for the full rules). ?opts are provider-specific plus a small set of core-recognized options (debounce, optional, version).

Supplementary tags

Other struct tags refine how a field resolves:

TagMeaning
default:"..."Value used when the ref resolves to not-found (never on error).
validate:"..."Field validation (go-playground/validator syntax), evaluated on every update. See Validation.
flatten:"json|yaml|toml|env"Decode a single provider payload into a nested struct.
optional:"true"Not-found is tolerated with no default (field keeps its zero value).
onfail:"keeplast|default|fail"Policy for a chain error, not absence. Default keeplast. See Source chains and precedence.
?debounce=<dur>Per-field coalescing window override, e.g. ?debounce=0 for certs.

A struct field with a source and flatten decodes one payload into the sub-struct; a struct field with no source is a container mamori recurses into:

type Config struct {
	Redis RedisConfig `source:"aws-sm://prod/redis" flatten:"json"`
}

type RedisConfig struct {
	Addr     string        `mapstructure:"addr"`
	Password secret.String `mapstructure:"password"`
	DB       int           `mapstructure:"db"`
}

The Value type

Providers return a Value, not raw bytes. This is the keystone for change detection and lease-aware refresh:

type Value struct {
	Bytes     []byte
	Version   string            // provider revision: SM VersionId, Vault version, file mtime hash
	Sensitive bool              // drives redaction downstream
	NotAfter  time.Time         // zero if unknown; e.g. a Vault lease expiry schedules a refresh
	Metadata  map[string]string
}

Version gives cheap change detection (no byte comparison when the provider supplies a revision). NotAfter lets lease-based providers request a refresh before expiry rather than waiting for the next poll tick.

Next

See also