Skip to main content

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}