Expand description
Read dynamic-config configuration from a Firestore document.
Firestore’s REST API is plain HTTP, so this implements the blocking
RemoteSource trait: nothing here needs an async runtime, and neither
does using it.
use dynamic_config_firestore::{Auth, Firestore};
DbConfig::set_remote(
Firestore::new("my-project", "config/db")
// On GKE, Cloud Run or GCE, the workload's own identity.
.with_auth(Auth::metadata_server()),
);
// Fetching is explicit; the load that follows touches no network.
DbConfig::refresh_remote()?;§What it reads
One document, at a path like config/db — collection, then document. Its
fields become the configuration, wrapped under the section key, which is the
same shape dynamic-config-vault uses and for the same reason: Firestore
stores a map of named fields, so the natural unit is the field.
Firestore types map onto configuration the obvious way — stringValue,
integerValue, booleanValue, doubleValue, arrayValue, mapValue. A
timestampValue, bytesValue or referenceValue becomes its string form,
because a configuration file has no better answer for one either.
§Several documents as one section
One section can be split across several documents, and Keys says which:
// Merged in the order given — later wins — and all under the one section key.
let firestore = Firestore::new("my-project", Keys::several(["config/db", "overrides/db"]));A named list is one request, and that is Firestore’s own answer rather
than a loop wearing a batch’s name: :batchGet takes the documents the
caller named and returns them together. Two things follow from what the API
actually promises:
- The answer arrives in whatever order the service likes —
BatchGetsays so explicitly — so it is put back into call order here. The order a caller wrote is the precedence; the order a service replies in is not. - One request is not one snapshot. Without a transaction each document is read at its own time, and this asks for none: an open read-only transaction is state on the service that a configuration read would have to remember to release. So a write landing mid-request can still produce a section that never existed as a whole. One round trip is the win; atomicity is not.
Every document lands under the same section key, because that is what a Firestore document is here: the contents of a section, not a whole configuration file. So a list is layering — a shared document and an override.
There is deliberately no collection form. documents.list exists, so
the missing piece is not the protocol; it is the mapping. Folding a whole
collection into one section makes config/db and config/server collide
on host — the ordinary layout, refused — and naming a sub-section after
each document’s id would invent a convention no other store here has, and
would make a list of one document mean something different from one
document. A deployment that wants several sections installs one source per
section, which is what it did before.
Two consequences the multi-document form shares with the rest of the family:
- Provenance becomes store-grained. The merged section is one layer, so
source_ofnames the set rather than which document supplied a value. - One unreadable document fails the whole fetch. A section quietly missing half of itself is worse than a refresh that failed and left the last document serving.
A multi-document source cannot be watched, and refuses at
watch rather than pretending to: the updateTime it
compares belongs to one document, and a set of them has none of its own.
§Authenticating
| Method | Constructor | For |
|---|---|---|
| Workload identity | Auth::metadata_server | GKE, Cloud Run, GCE — no secret to distribute |
| An access token | Auth::access_token | anything that already has one, including gcloud auth print-access-token |
| None | Auth::Emulator | the Firestore emulator, which wants no credentials |
A service-account JSON key is deliberately not supported, and that is a
recommendation rather than a gap: signing one means an RS256 stack in a
configuration library, and Google’s own guidance is that a downloaded key is
the option of last resort. Workload identity covers GKE, Cloud Run, GCE and
Cloud Functions; for anything else, mint a token outside the process and
hand it over with Auth::access_token.
§Every failure branch of the watch loop, and what it reports
A watch is the half of a store dynamic-config cannot see, and
reporting_to is what lets it speak: the sink the
loop already holds is told about every attempt that came back with
nothing. Which attempts those are is a table rather than prose, because
the question an operator asks is which silence is deliberate.
Three rules decide the column, and they are the same three in all seven store crates:
- A failure the loop survives by retrying reports. That is the case the whole feature exists for: the stream is down, the last delivery is old, and nothing else would ever say so out loud.
- A recovery that worked stays silent. Only a delivery or a fetch
clears the streak, so reporting a five-minute token turning over on a
healthy cluster would drive
remote_upto zero and leave it there. - A refusal that never asked the store reports nowhere. No format, a
key shape that cannot be watched, material that will not build a
client:
RemoteStatus::reachable()is whether the store answered the last time it was asked, and these never ask. They are returned to the caller, who is the one holding the mistake — and a status cannot correct them, since it carries a kind and a path and no message.
| Branch | Reports |
|---|---|
| the source reads several documents, so it cannot be watched | no — rule 3: nothing has been asked of Firestore |
| the read fails — a blip, an expired token, a document briefly unreachable | yes, and the loop waits out the interval |
the document has no updateTime, so a change could never be detected | yes, and the watch ends |
| the first read, or an update time that has not moved | no — Firestore answered |
on_change refuses the document | no — Firestore answered; apply counted the delivery, and what the document did next is ConfigStatus’s half |
Structs§
- Firestore
- A document in Firestore, as a configuration source.
- TlsConfig
- A private certificate authority and a client certificate, as data.