An unqualified `[`open_or_create`]` in the module-level doc does not resolve once the `kv` feature is on, which is the only configuration where the module is compiled at all — so `docs-rustdoc` failed in CI while a default-feature `cargo doc` passed locally. Measured both ways: kv off documents clean, kv on errors `no item named open_or_create in scope`. Qualifying the path fixes it without widening any visibility, which is the rule that check exists to protect.
67 lines
2.8 KiB
Rust
67 lines
2.8 KiB
Rust
//! The hive-status KV bucket: its name, and the shape it is created with.
|
|
//!
|
|
//! Two processes touch this bucket from opposite ends — a hive writes its
|
|
//! own key, the swarm controller reads every key — and they live in
|
|
//! different crates. That is the whole reason this module exists rather
|
|
//! than a `const` on each side: **the two ends must agree, and a literal
|
|
//! repeated across crates is an agreement nothing checks.**
|
|
//!
|
|
//! The name is the obvious half. The sharper half is the *config*: both
|
|
//! ends open the bucket with [`crate::status::open_or_create`], because either may
|
|
//! arrive first on a fresh swarm and neither can assume the other has
|
|
//! run. If the two ends passed different `Config`s, whichever created it
|
|
//! would win and the other's `get_key_value` would succeed against a
|
|
//! bucket it did not ask for — no error, no log, just a retention policy
|
|
//! nobody chose. Sharing the constructor makes the race have one outcome
|
|
//! instead of two.
|
|
//!
|
|
//! Feature-gated (`kv`) so the crate's other consumer, the auth-callout
|
|
//! responder, still pulls neither `jetstream` nor `kv`: it speaks the
|
|
//! connect and nothing else.
|
|
|
|
use crate::Error;
|
|
|
|
/// The KV bucket hives publish their status snapshots into, one key per
|
|
/// hive keyed by `hiveName`.
|
|
///
|
|
/// A constant and not an option: reader and writer must name the same
|
|
/// bucket, and an option is a way for two deployments to disagree about
|
|
/// which one that is. Nothing about a bucket name is site-specific.
|
|
pub const BUCKET: &str = "hive-status";
|
|
|
|
/// Open the status bucket, creating it if nothing has yet.
|
|
///
|
|
/// `history: 1` is the shape: every consumer reads *the last thing each
|
|
/// hive said*, and retaining more would be storage bought for a query
|
|
/// nobody makes.
|
|
///
|
|
/// Creating rather than requiring a provisioning step is deliberate — the
|
|
/// controller and the hives come up in no particular order, and a bucket
|
|
/// that must pre-exist turns "the swarm was deployed in the wrong order"
|
|
/// into a permanent, silent absence of data.
|
|
pub async fn open_or_create(
|
|
client: &async_nats::Client,
|
|
) -> Result<async_nats::jetstream::kv::Store, Error> {
|
|
let js = async_nats::jetstream::new(client.clone());
|
|
match js.get_key_value(BUCKET).await {
|
|
Ok(store) => Ok(store),
|
|
Err(e) => {
|
|
tracing::info!(
|
|
bucket = BUCKET,
|
|
reason = %e,
|
|
"status bucket not available, creating it"
|
|
);
|
|
js.create_key_value(async_nats::jetstream::kv::Config {
|
|
bucket: BUCKET.to_owned(),
|
|
description: "Last status snapshot offered by each hive".to_owned(),
|
|
history: 1,
|
|
..Default::default()
|
|
})
|
|
.await
|
|
.map_err(|source| Error::CreateBucket {
|
|
bucket: BUCKET,
|
|
source,
|
|
})
|
|
}
|
|
}
|
|
}
|