Consul

Consul KV, with native watch via blocking queries, built on hashicorp/consul/api.

Schemeconsul://
Modulegithub.com/xavidop/mamori/providers/consul
Sensitiveno
Watchnative (blocking queries)
AuthCONSUL_HTTP_ADDR, CONSUL_HTTP_TOKEN

Install

go get github.com/xavidop/mamori/providers/consul
import _ "github.com/xavidop/mamori/providers/consul"

Using the ref

A consul:// ref points at one key in the Consul KV store, optionally selecting a field from a JSON value stored there.

consul://<kv-path>[#json-key]
PartRequiredWhat it means
<kv-path>yesThe Consul KV key path, e.g. config/service/endpoint. No leading slash.
#json-keynoWhen the stored value is a JSON object, return one field from it (via mamori.SelectKey).

Examples

  • consul://config/service/endpoint - reads the raw value stored at key config/service/endpoint.
  • consul://config/service/db#max_conns - reads the JSON object at config/service/db and returns its max_conns field.
  • consul://features/flags#dark_mode - returns the dark_mode field of the JSON at features/flags.
type Config struct {
	Endpoint string `source:"consul://config/service/endpoint"`
	// a JSON value stored at one key, then a field selected from it
	MaxConns int    `source:"consul://config/service/db#max_conns"`
}

Value.Version is the KV pair’s ModifyIndex, Consul’s native revision, so change detection is exact and cheap. Consul KV holds configuration rather than managed secrets, so values are non-sensitive; wrap a field in secret.String if you want redaction anyway.

Watch

Watch uses Consul blocking queries: it re-issues KV.Get with WaitIndex set to the last ModifyIndex, so the request parks on the server until the value changes (or a wait timeout elapses), then emits an Update. It handles index resets, backs off on transient errors, and closes on context cancellation.

Error classification

A missing key is not an SDK error: KV.Get returns a nil pair with a nil error for a 404, and the provider maps that directly to mamori.ErrNotFound - there is no not-found row below because the classifier never sees that case. Every other non-2xx response surfaces as an api.StatusError{Code, Body}, classified by HTTP status:

HTTP statusmamori kind
403permission_denied
401unauthenticated
429rate_limited
5xxunavailable
400invalid
anything elseunknown

403 (ACL token denied) is the confirmed common case for this endpoint. 401 and 429 are mapped on ordinary HTTP semantics rather than confirmed Consul KV behavior - whether they actually occur depends on cluster ACL/rate-limit configuration. The original api.StatusError stays reachable with errors.As.

Explicit configuration

import consulprov "github.com/xavidop/mamori/providers/consul"

mamori.WithProvider(consulprov.New(
	consulprov.WithAddress("consul.internal:8500"),
	consulprov.WithToken(os.Getenv("CONSUL_HTTP_TOKEN")),
))

Verified by unit tests and the conformance kit against an in-memory fake that reproduces blocking-query semantics, so the watch checks run for real. A live-Consul integration test is provided behind //go:build integration.