Skip to main content

Crate dynamic_config_firestore

Crate dynamic_config_firestore 

Source
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 likesBatchGet says 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_of names 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

MethodConstructorFor
Workload identityAuth::metadata_serverGKE, Cloud Run, GCE — no secret to distribute
An access tokenAuth::access_tokenanything that already has one, including gcloud auth print-access-token
NoneAuth::Emulatorthe 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:

  1. 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.
  2. 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_up to zero and leave it there.
  3. 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.
BranchReports
the source reads several documents, so it cannot be watchedno — rule 3: nothing has been asked of Firestore
the read fails — a blip, an expired token, a document briefly unreachableyes, and the loop waits out the interval
the document has no updateTime, so a change could never be detectedyes, and the watch ends
the first read, or an update time that has not movedno — Firestore answered
on_change refuses the documentno — 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.

Enums§

Auth
How to obtain an access token for the Firestore API.
Keys
What a source reads: one document, or several named ones.