v0.25.3
This commit is contained in:
1 parent
174389e5b1
commit
e6604e41fc
73 files changed
+1240
-201
No files matched your search
@@ -0,0 +1,96 @@
|
||||
use super::Store;
|
||||
use crate::config::Config;
|
||||
use anyhow::{ensure, Context, Result};
|
||||
use serde::Serialize;
|
||||
use sqlx::Row;
|
||||
|
||||
pub(super) const CONFIG_HISTORY_CAPACITY: i64 = 512;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ConfigurationRevision {
|
||||
pub revision: u64,
|
||||
pub time: i64,
|
||||
pub actor: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ConfigurationHistoryDetail {
|
||||
pub revision: u64,
|
||||
pub time: i64,
|
||||
pub actor: String,
|
||||
pub config: Config,
|
||||
pub previous_revision: Option<u64>,
|
||||
pub previous_config: Option<Config>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub async fn configuration_history(&self, limit: usize) -> Result<Vec<ConfigurationRevision>> {
|
||||
let limit = i64::try_from(limit.min(CONFIG_HISTORY_CAPACITY as usize))?;
|
||||
let rows = sqlx::query(
|
||||
"SELECT revision, time, actor FROM configuration_history ORDER BY revision DESC LIMIT ?",
|
||||
)
|
||||
.bind(limit)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
rows.into_iter()
|
||||
.map(|row| {
|
||||
Ok(ConfigurationRevision {
|
||||
revision: row.try_get::<i64, _>("revision")?.try_into()?,
|
||||
time: row.try_get("time")?,
|
||||
actor: row.try_get("actor")?,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn configuration_revision(
|
||||
&self,
|
||||
revision: u64,
|
||||
) -> Result<Option<ConfigurationHistoryDetail>> {
|
||||
let revision_i64: i64 = revision.try_into().context("revision overflow")?;
|
||||
let Some(row) = sqlx::query(
|
||||
"SELECT revision, time, actor, document FROM configuration_history WHERE revision = ?",
|
||||
)
|
||||
.bind(revision_i64)
|
||||
.fetch_optional(&self.pool)
|
||||
.await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let document: String = row.try_get("document")?;
|
||||
ensure!(
|
||||
document.len() <= crate::config::MAX_CONFIG_BYTES,
|
||||
"stored configuration history exceeds size limit"
|
||||
);
|
||||
let previous = sqlx::query(
|
||||
"SELECT revision, document FROM configuration_history WHERE revision < ? ORDER BY revision DESC LIMIT 1",
|
||||
)
|
||||
.bind(revision_i64)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
let (previous_revision, previous_config) = if let Some(previous) = previous {
|
||||
let previous_document: String = previous.try_get("document")?;
|
||||
ensure!(
|
||||
previous_document.len() <= crate::config::MAX_CONFIG_BYTES,
|
||||
"stored configuration history exceeds size limit"
|
||||
);
|
||||
(
|
||||
Some(previous.try_get::<i64, _>("revision")?.try_into()?),
|
||||
Some(serde_json::from_str(&previous_document).context(
|
||||
"invalid previous configuration history document",
|
||||
)?),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
Ok(Some(ConfigurationHistoryDetail {
|
||||
revision: row.try_get::<i64, _>("revision")?.try_into()?,
|
||||
time: row.try_get("time")?,
|
||||
actor: row.try_get("actor")?,
|
||||
config: serde_json::from_str(&document)
|
||||
.context("invalid configuration history document")?,
|
||||
previous_revision,
|
||||
previous_config,
|
||||
}))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,6 +36,28 @@ mod tests {
|
||||
assert_eq!(data.config.transform.max_inflight, 64);
|
||||
reopened.close().await;
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn configuration_history_keeps_revision_documents_and_actor() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let file = dir.path().join("history.sqlite3");
|
||||
let store = Store::open(&file, &Config::default()).await.unwrap();
|
||||
let mut first = Config::default();
|
||||
first.server_header.value = "first".into();
|
||||
assert!(store.replace_as(0, &first, "alice").await.unwrap());
|
||||
let mut second = first.clone();
|
||||
second.server_header.value = "second".into();
|
||||
assert!(store.replace_as(1, &second, "bob").await.unwrap());
|
||||
let history = store.configuration_history(10).await.unwrap();
|
||||
assert_eq!(history[0].revision, 2);
|
||||
assert_eq!(history[0].actor, "bob");
|
||||
assert_eq!(history[1].revision, 1);
|
||||
let detail = store.configuration_revision(2).await.unwrap().unwrap();
|
||||
assert_eq!(detail.config.server_header.value, "second");
|
||||
assert_eq!(detail.previous_revision, Some(1));
|
||||
assert_eq!(detail.previous_config.unwrap().server_header.value, "first");
|
||||
store.close().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn certificate_renewal_observations_survive_reopen_in_schema_v3() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in new issue
Block a user