objectstore_options/lib.rs
1//! Runtime options for Objectstore, backed by [`sentry-options`].
2//!
3//! See the [`Options`] struct for details and usage instructions.
4//!
5//! [`sentry-options`]: https://crates.io/crates/sentry-options
6
7use std::collections::BTreeMap;
8
9use objectstore_typed_options::SentryOptions;
10use serde::{Deserialize, Serialize};
11
12pub use objectstore_typed_options::Error;
13
14/// Initializes options and spawns a background task that refreshes them periodically.
15///
16/// The refresh interval is 4 seconds, chosen to stay under the 5-second staleness
17/// threshold built into `sentry-options`.
18pub fn init() -> Result<(), Error> {
19 Options::init()?;
20
21 tokio::spawn(async {
22 let mut interval = tokio::time::interval(std::time::Duration::from_secs(4));
23 loop {
24 interval.tick().await;
25
26 // `spawn_blocking` propagates panics as a JoinError. No need to log them, since the
27 // global panic hook does that already.
28 let result = tokio::task::spawn_blocking(Options::refresh).await;
29 if let Ok(Err(ref err)) = result {
30 objectstore_log::error!(!!err, "failed to refresh options");
31 }
32 }
33 });
34
35 Ok(())
36}
37
38/// Runtime options for Objectstore, loaded from sentry-options.
39///
40/// Obtain a snapshot of the current options via [`Options::get`]. Before calling `get`,
41/// the global instance must be initialized with [`Options::init`].
42#[derive(Debug, SentryOptions)]
43#[sentry_options(namespace = "objectstore", path = "../../sentry-options")]
44pub struct Options {
45 /// Active killswitches that may disable access to specific object contexts.
46 killswitches: Vec<Killswitch>,
47}
48
49impl Options {
50 /// Returns the list of active killswitches.
51 pub fn killswitches(&self) -> &[Killswitch] {
52 &self.killswitches
53 }
54}
55
56/// A killswitch that may disable access to certain object contexts.
57///
58/// Note that at least one of the fields should be set, or else the killswitch will match all
59/// contexts and discard all requests.
60#[derive(Debug, Deserialize, Serialize, PartialEq)]
61pub struct Killswitch {
62 /// Optional usecase to match.
63 ///
64 /// If `None`, matches any usecase.
65 #[serde(default)]
66 pub usecase: Option<String>,
67
68 /// Scopes to match.
69 ///
70 /// If empty, matches any scopes. Additional scopes in the context are ignored, so a killswitch
71 /// matches if all of the specified scopes are present in the request with matching values.
72 #[serde(default)]
73 pub scopes: BTreeMap<String, String>,
74
75 /// Optional service glob pattern to match.
76 ///
77 /// If `None`, matches any service (or absence of service header).
78 /// If specified, the request must have a matching `x-downstream-service` header. The header
79 /// value is normalized before matching: any trailing Kubernetes ReplicaSet hash and pod
80 /// suffix are stripped, so patterns should match the base service name (e.g. `relay*`, not
81 /// `relay-7d8f9c5b6d-*`).
82 #[serde(default)]
83 pub service: Option<String>,
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 // Required for schema validation.
91 #[cfg(not(feature = "testing"))]
92 compile_error!("tests require the `testing` feature: run with `--features testing`");
93
94 #[test]
95 fn schema_is_valid() {
96 let _ = Options::get();
97 }
98}