rm sled migration, sqlite migration
This commit is contained in:
Generated
+18
-121
@@ -233,12 +233,6 @@ version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
@@ -475,24 +469,6 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
@@ -514,10 +490,10 @@ version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"document-features",
|
||||
"parking_lot 0.12.5",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"winapi",
|
||||
]
|
||||
@@ -747,16 +723,6 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
@@ -813,7 +779,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"lock_api",
|
||||
"parking_lot 0.12.5",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -862,15 +828,6 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -1309,15 +1266,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.0"
|
||||
@@ -1470,7 +1418,7 @@ version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"libc",
|
||||
"plain",
|
||||
"redox_syscall 0.7.4",
|
||||
@@ -1617,7 +1565,7 @@ version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"libc",
|
||||
"log",
|
||||
"netlink-packet-core",
|
||||
@@ -1656,7 +1604,7 @@ version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -1668,7 +1616,7 @@ version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -1859,17 +1807,6 @@ version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -1877,21 +1814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core 0.9.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2189,22 +2112,13 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2213,7 +2127,7 @@ version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2394,7 +2308,7 @@ version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -2557,7 +2471,7 @@ version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -2696,7 +2610,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.5",
|
||||
"parking_lot",
|
||||
"scc",
|
||||
"serial_test_derive",
|
||||
]
|
||||
@@ -2775,22 +2689,6 @@ version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "sled"
|
||||
version = "0.34.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"fs2",
|
||||
"fxhash",
|
||||
"libc",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
@@ -2923,7 +2821,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crc",
|
||||
@@ -2966,7 +2864,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"crc",
|
||||
"dotenvy",
|
||||
@@ -3399,7 +3297,7 @@ version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -3759,7 +3657,6 @@ dependencies = [
|
||||
"reqwest 0.13.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sled",
|
||||
"sqlx",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -3932,7 +3829,7 @@ version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.14.0",
|
||||
"semver",
|
||||
@@ -4437,7 +4334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags",
|
||||
"indexmap 2.14.0",
|
||||
"log",
|
||||
"serde",
|
||||
|
||||
@@ -146,7 +146,7 @@ Relative paths in config are resolved under `data_dir`.
|
||||
Legacy sled state can be imported explicitly:
|
||||
|
||||
```sh
|
||||
wakey-control-plane import-sled-state --from-sled-state /var/lib/wakey-control-plane/state.db --to-state-file /var/lib/wakey-control-plane/state.sqlite3
|
||||
wakey-control-plane migrate-sqlite-state --from /var/lib/wakey-control-plane/legacy.sqlite3 --to /var/lib/wakey-control-plane/state.sqlite3
|
||||
```
|
||||
|
||||
Enroll tokens are now expiring and revocable. Issuance returns `expires_at_unix`.
|
||||
|
||||
@@ -20,7 +20,7 @@ reqwest = { version = "0.13", default-features = false, features = [
|
||||
] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sled = "0.34"
|
||||
|
||||
sqlx = { version = "0.8", features = [
|
||||
"runtime-tokio",
|
||||
"sqlite",
|
||||
|
||||
@@ -43,8 +43,8 @@ pub enum Command {
|
||||
RevokeAgent(RevokeAgentArgs),
|
||||
/// Print state backend stats.
|
||||
StateStats(StateStatsArgs),
|
||||
/// Import a legacy sled state directory into a SQLite state file.
|
||||
ImportSledState(ImportSledStateArgs),
|
||||
/// Migrate a legacy sqlite state into a new SQLite state file.
|
||||
MigrateSqliteState(MigrateSqliteStateArgs),
|
||||
/// Send SIGHUP to an already-running daemon.
|
||||
Reload(ReloadArgs),
|
||||
}
|
||||
@@ -253,14 +253,13 @@ pub struct StateStatsArgs {
|
||||
pub target: AdminTargetArgs,
|
||||
}
|
||||
|
||||
// not the greatest thing ive used, this and the migration code gets deleted shortly anyways
|
||||
#[derive(Args)]
|
||||
pub struct ImportSledStateArgs {
|
||||
pub struct MigrateSqliteStateArgs {
|
||||
#[arg(long, visible_alias = "from")]
|
||||
pub from_sled_state: PathBuf,
|
||||
pub from_sqlite_db: PathBuf,
|
||||
|
||||
#[arg(long, visible_alias = "to")]
|
||||
pub to_state_file: PathBuf,
|
||||
pub to_sqlite_db: PathBuf,
|
||||
|
||||
#[arg(long)]
|
||||
pub force: bool,
|
||||
|
||||
@@ -61,9 +61,9 @@ async fn main() -> Result<()> {
|
||||
tracing::init(cli.verbose, &config::TelemetryConfig::default())?;
|
||||
runtime::state_stats(args).await
|
||||
}
|
||||
Command::ImportSledState(args) => {
|
||||
Command::MigrateSqliteState(args) => {
|
||||
tracing::init(cli.verbose, &config::TelemetryConfig::default())?;
|
||||
runtime::import_sled_state(args).await
|
||||
runtime::migrate_sqlite_state(args).await
|
||||
}
|
||||
Command::Reload(args) => {
|
||||
tracing::init(cli.verbose, &config::TelemetryConfig::default())?;
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::{Context, Result};
|
||||
|
||||
use crate::api;
|
||||
use crate::cli::{
|
||||
ImportSledStateArgs, IssueEnrollTokenArgs, ListEnrollTokensArgs, RevokeAgentArgs,
|
||||
IssueEnrollTokenArgs, ListEnrollTokensArgs, MigrateSqliteStateArgs, RevokeAgentArgs,
|
||||
RevokeEnrollTokenArgs, StateStatsArgs,
|
||||
};
|
||||
use crate::config;
|
||||
@@ -277,10 +277,11 @@ pub async fn state_stats(args: StateStatsArgs) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_sled_state(args: ImportSledStateArgs) -> Result<()> {
|
||||
state::Store::import_sled_state(&args.from_sled_state, &args.to_state_file, args.force).await?;
|
||||
println!("from_sled_state={}", args.from_sled_state.display());
|
||||
println!("to_state_file={}", args.to_state_file.display());
|
||||
println!("imported=true");
|
||||
pub async fn migrate_sqlite_state(args: MigrateSqliteStateArgs) -> Result<()> {
|
||||
state::Store::migrate_sqlite_state(&args.from_sqlite_db, &args.to_sqlite_db, args.force)
|
||||
.await?;
|
||||
println!("from_sqlite_db={}", args.from_sqlite_db.display());
|
||||
println!("to_sqlite_db={}", args.to_sqlite_db.display());
|
||||
println!("migrated=true");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ mod admin;
|
||||
mod process;
|
||||
pub use admin::revoke_agent;
|
||||
pub use admin::{
|
||||
import_sled_state, issue_enroll_token, list_enroll_tokens, revoke_enroll_token, state_stats,
|
||||
issue_enroll_token, list_enroll_tokens, migrate_sqlite_state, revoke_enroll_token, state_stats,
|
||||
};
|
||||
pub use process::reload_daemon;
|
||||
use process::{remove_pid_file, write_pid_file};
|
||||
|
||||
@@ -29,11 +29,10 @@ mod db;
|
||||
mod devices;
|
||||
mod enrollment;
|
||||
mod helpers;
|
||||
mod import_sled;
|
||||
mod migrate_sqlite;
|
||||
|
||||
use helpers::alerts_audit::*;
|
||||
use helpers::core::*;
|
||||
use helpers::legacy::*;
|
||||
use helpers::rows::*;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -588,65 +587,4 @@ mod tests {
|
||||
|
||||
let _ = fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn import_sled_state_copies_core_records() {
|
||||
let dir =
|
||||
std::env::temp_dir().join(format!("wakey-cp-store-test-{}", uuid::Uuid::new_v4()));
|
||||
let sled_path = dir.join("legacy-state.db");
|
||||
let sqlite_path = dir.join("state.sqlite3");
|
||||
fs::create_dir_all(&dir).expect("dir should exist");
|
||||
|
||||
let legacy = sled::open(&sled_path).expect("legacy db should open");
|
||||
let meta = legacy.open_tree("meta").expect("meta should open");
|
||||
meta.insert(
|
||||
super::SCHEMA_VERSION_KEY.as_bytes(),
|
||||
&super::SCHEMA_VERSION.to_le_bytes(),
|
||||
)
|
||||
.expect("schema should insert");
|
||||
let enroll = legacy
|
||||
.open_tree("enroll_tokens")
|
||||
.expect("enroll tree should open");
|
||||
enroll
|
||||
.insert(b"enr-import-test", &(i64::MAX as u64).to_le_bytes())
|
||||
.expect("token should insert");
|
||||
let agents = legacy.open_tree("agents").expect("agents should open");
|
||||
agents
|
||||
.insert(b"agent-import", b"tok-import")
|
||||
.expect("agent should insert");
|
||||
let agent_meta = legacy.open_tree("agent_meta").expect("meta should open");
|
||||
agent_meta
|
||||
.insert(b"agent-import", b"imported-router")
|
||||
.expect("nickname should insert");
|
||||
legacy.flush().expect("legacy flush should succeed");
|
||||
drop(agent_meta);
|
||||
drop(agents);
|
||||
drop(enroll);
|
||||
drop(meta);
|
||||
drop(legacy);
|
||||
|
||||
Store::import_sled_state(&sled_path, &sqlite_path, false)
|
||||
.await
|
||||
.expect("import should succeed");
|
||||
let store = Store::load_or_init(&sqlite_path, Vec::new(), Duration::from_secs(60))
|
||||
.await
|
||||
.expect("sqlite should load");
|
||||
|
||||
assert!(store.verify_agent_token("agent-import", "tok-import").await);
|
||||
let agents = store.list_agents_with_nicknames().await;
|
||||
assert!(agents.iter().any(|(id, nickname)| {
|
||||
id == "agent-import" && nickname.as_deref() == Some("imported-router")
|
||||
}));
|
||||
let tokens = store
|
||||
.list_enroll_tokens()
|
||||
.await
|
||||
.expect("tokens should list");
|
||||
assert!(
|
||||
tokens
|
||||
.iter()
|
||||
.any(|token| token.enroll_token == "enr-import-test")
|
||||
);
|
||||
|
||||
let _ = fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ impl Store {
|
||||
) -> Result<Self> {
|
||||
if path.is_dir() {
|
||||
anyhow::bail!(
|
||||
"state_file {} is a directory, which looks like a legacy sled store; run `wakey-control-plane import-sled-state --from-sled-state {} --to-state-file <sqlite-file>` and update state_file",
|
||||
"state_file {} is a directory, which looks like a legacy sled store; sled state is no longer supported",
|
||||
path.display(),
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::*;
|
||||
|
||||
pub(super) mod alerts_audit;
|
||||
pub(super) mod core;
|
||||
pub(super) mod legacy;
|
||||
pub(super) mod rows;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_helpers;
|
||||
|
||||
@@ -1,38 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
pub async fn insert_audit_event(pool: &SqlitePool, key: &str, event: &AuditEvent) -> Result<()> {
|
||||
let metadata_json =
|
||||
serde_json::to_string(&event.metadata).context("failed to encode audit metadata")?;
|
||||
let ts_unix = i64::try_from(event.ts_unix).context("audit timestamp overflow")?;
|
||||
let latency_ms = event
|
||||
.latency_ms
|
||||
.map(i64::try_from)
|
||||
.transpose()
|
||||
.context("audit latency overflow")?;
|
||||
sqlx::query!(
|
||||
"INSERT OR REPLACE INTO audit_events
|
||||
(event_key, event_id, ts_unix, actor_type, actor_id, agent_id, request_id,
|
||||
event_type, outcome, latency_ms, message, metadata_json)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
||||
key,
|
||||
event.event_id,
|
||||
ts_unix,
|
||||
event.actor_type,
|
||||
event.actor_id,
|
||||
event.agent_id,
|
||||
event.request_id,
|
||||
event.event_type,
|
||||
event.outcome,
|
||||
latency_ms,
|
||||
event.message,
|
||||
metadata_json
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.context("failed persisting audit event")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_active_alert(
|
||||
tx: &mut Transaction<'_, Sqlite>,
|
||||
alert: &AlertState,
|
||||
@@ -66,36 +33,6 @@ pub async fn insert_active_alert(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_active_alert_pool(pool: &SqlitePool, alert: &AlertState) -> Result<()> {
|
||||
let metadata_json =
|
||||
serde_json::to_string(&alert.metadata).context("failed to encode active alert metadata")?;
|
||||
let alert_value = i64::try_from(alert.value).context("active alert value overflow")?;
|
||||
let alert_threshold =
|
||||
i64::try_from(alert.threshold).context("active alert threshold overflow")?;
|
||||
let last_seen_unix =
|
||||
i64::try_from(alert.last_seen_unix).context("active alert timestamp overflow")?;
|
||||
sqlx::query!(
|
||||
"INSERT OR REPLACE INTO active_alerts
|
||||
(alert_id, kind, severity, status, agent_id, message, value, threshold,
|
||||
last_seen_unix, metadata_json)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
||||
alert.alert_id,
|
||||
alert.kind,
|
||||
alert.severity,
|
||||
alert.status,
|
||||
alert.agent_id,
|
||||
alert.message,
|
||||
alert_value,
|
||||
alert_threshold,
|
||||
last_seen_unix,
|
||||
metadata_json
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.context("failed writing active alert snapshot")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_alert_transition(
|
||||
tx: &mut Transaction<'_, Sqlite>,
|
||||
key: &str,
|
||||
@@ -126,36 +63,6 @@ pub async fn insert_alert_transition(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_alert_transition_pool(
|
||||
pool: &SqlitePool,
|
||||
key: &str,
|
||||
transition: &AlertTransition,
|
||||
) -> Result<()> {
|
||||
let metadata_json = serde_json::to_string(&transition.metadata)
|
||||
.context("failed to encode alert transition metadata")?;
|
||||
let ts_unix = i64::try_from(transition.ts_unix).context("alert timestamp overflow")?;
|
||||
sqlx::query!(
|
||||
"INSERT OR REPLACE INTO alert_transitions
|
||||
(transition_key, transition_id, ts_unix, alert_id, kind, agent_id,
|
||||
from_status, to_status, message, metadata_json)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
||||
key,
|
||||
transition.transition_id,
|
||||
ts_unix,
|
||||
transition.alert_id,
|
||||
transition.kind,
|
||||
transition.agent_id,
|
||||
transition.from_status,
|
||||
transition.to_status,
|
||||
transition.message,
|
||||
metadata_json
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.context("failed persisting alert transition")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn audit_event_from_row(row: sqlx::sqlite::SqliteRow) -> Result<AuditEvent> {
|
||||
let ts_unix: i64 = row.try_get("ts_unix")?;
|
||||
let latency_ms: Option<i64> = row.try_get("latency_ms")?;
|
||||
|
||||
@@ -29,15 +29,6 @@ pub fn now_unix() -> u64 {
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn decode_expiry(raw: &[u8]) -> Result<u64> {
|
||||
if raw.len() != 8 {
|
||||
anyhow::bail!("invalid token expiry length {}", raw.len());
|
||||
}
|
||||
let mut arr = [0u8; 8];
|
||||
arr.copy_from_slice(raw);
|
||||
Ok(u64::from_le_bytes(arr))
|
||||
}
|
||||
|
||||
pub fn decode_schema(raw: &[u8]) -> Result<u32> {
|
||||
if raw.len() != 4 {
|
||||
anyhow::bail!("invalid schema version length {}", raw.len());
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
pub async fn import_tree_raw(
|
||||
pool: &SqlitePool,
|
||||
legacy: &sled::Db,
|
||||
sled_tree: &str,
|
||||
sql_table: &str,
|
||||
key_col: &str,
|
||||
value_col: &str,
|
||||
) -> Result<()> {
|
||||
let tree = legacy
|
||||
.open_tree(sled_tree)
|
||||
.with_context(|| format!("failed to open legacy {sled_tree} tree"))?;
|
||||
let sql =
|
||||
format!("INSERT OR REPLACE INTO {sql_table} ({key_col}, {value_col}) VALUES (?1, ?2)");
|
||||
for item in tree.iter() {
|
||||
let (key, value) =
|
||||
item.with_context(|| format!("failed reading legacy {sled_tree} tree"))?;
|
||||
let key = String::from_utf8(key.to_vec())
|
||||
.with_context(|| format!("legacy {sled_tree} key is not utf-8"))?;
|
||||
sqlx::query(&sql)
|
||||
.bind(key)
|
||||
.bind(value.to_vec())
|
||||
.execute(pool)
|
||||
.await
|
||||
.with_context(|| format!("failed importing legacy {sled_tree} row"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_enroll_tokens(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
|
||||
let tree = legacy
|
||||
.open_tree("enroll_tokens")
|
||||
.context("failed to open legacy enroll_tokens tree")?;
|
||||
for item in tree.iter() {
|
||||
let (token, expiry) = item.context("failed reading legacy enroll token")?;
|
||||
let token =
|
||||
String::from_utf8(token.to_vec()).context("legacy enroll token key is not utf-8")?;
|
||||
let expires_at_unix = i64::try_from(decode_expiry(expiry.as_ref())?)
|
||||
.context("legacy token expiry overflow")?;
|
||||
sqlx::query!(
|
||||
"INSERT OR REPLACE INTO enroll_tokens (token, expires_at_unix) VALUES (?1, ?2)",
|
||||
token,
|
||||
expires_at_unix
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.context("failed importing legacy enroll token")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_agents(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
|
||||
let tree = legacy
|
||||
.open_tree("agents")
|
||||
.context("failed to open legacy agents tree")?;
|
||||
for item in tree.iter() {
|
||||
let (agent_id, agent_token) = item.context("failed reading legacy agent")?;
|
||||
let agent_id =
|
||||
String::from_utf8(agent_id.to_vec()).context("legacy agent id is not utf-8")?;
|
||||
let agent_token =
|
||||
String::from_utf8(agent_token.to_vec()).context("legacy agent token is not utf-8")?;
|
||||
sqlx::query!(
|
||||
"INSERT OR REPLACE INTO agents (agent_id, agent_token) VALUES (?1, ?2)",
|
||||
agent_id,
|
||||
agent_token
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.context("failed importing legacy agent")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_agent_meta(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
|
||||
let tree = legacy
|
||||
.open_tree("agent_meta")
|
||||
.context("failed to open legacy agent_meta tree")?;
|
||||
for item in tree.iter() {
|
||||
let (agent_id, nickname) = item.context("failed reading legacy agent metadata")?;
|
||||
let agent_id =
|
||||
String::from_utf8(agent_id.to_vec()).context("legacy agent id is not utf-8")?;
|
||||
let nickname =
|
||||
String::from_utf8(nickname.to_vec()).context("legacy nickname is not utf-8")?;
|
||||
sqlx::query!(
|
||||
"INSERT OR REPLACE INTO agent_meta (agent_id, nickname) VALUES (?1, ?2)",
|
||||
agent_id,
|
||||
nickname
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.context("failed importing legacy agent metadata")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_audit_events(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
|
||||
let tree = legacy
|
||||
.open_tree("audit_events")
|
||||
.context("failed to open legacy audit_events tree")?;
|
||||
for item in tree.iter() {
|
||||
let (key, value) = item.context("failed reading legacy audit event")?;
|
||||
let key = String::from_utf8(key.to_vec()).context("legacy audit key is not utf-8")?;
|
||||
let event: AuditEvent =
|
||||
serde_json::from_slice(value.as_ref()).context("failed decoding legacy audit event")?;
|
||||
insert_audit_event(pool, &key, &event)
|
||||
.await
|
||||
.context("failed importing legacy audit event")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_active_alerts(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
|
||||
let tree = legacy
|
||||
.open_tree("active_alerts")
|
||||
.context("failed to open legacy active_alerts tree")?;
|
||||
for item in tree.iter() {
|
||||
let (_, value) = item.context("failed reading legacy active alert")?;
|
||||
let alert: AlertState = serde_json::from_slice(value.as_ref())
|
||||
.context("failed decoding legacy active alert")?;
|
||||
insert_active_alert_pool(pool, &alert)
|
||||
.await
|
||||
.context("failed importing legacy active alert")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_alert_transitions(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
|
||||
let tree = legacy
|
||||
.open_tree("alert_transitions")
|
||||
.context("failed to open legacy alert_transitions tree")?;
|
||||
for item in tree.iter() {
|
||||
let (key, value) = item.context("failed reading legacy alert transition")?;
|
||||
let key =
|
||||
String::from_utf8(key.to_vec()).context("legacy alert transition key is not utf-8")?;
|
||||
let transition: AlertTransition = serde_json::from_slice(value.as_ref())
|
||||
.context("failed decoding legacy alert transition")?;
|
||||
insert_alert_transition_pool(pool, &key, &transition)
|
||||
.await
|
||||
.context("failed importing legacy alert transition")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Store {
|
||||
pub async fn import_sled_state(
|
||||
from_sled_state: &Path,
|
||||
to_state_file: &Path,
|
||||
force: bool,
|
||||
) -> Result<()> {
|
||||
if !from_sled_state.is_dir() {
|
||||
anyhow::bail!(
|
||||
"legacy sled state {} does not exist or is not a directory",
|
||||
from_sled_state.display()
|
||||
);
|
||||
}
|
||||
if to_state_file.is_dir() {
|
||||
anyhow::bail!(
|
||||
"target state file {} is a directory",
|
||||
to_state_file.display()
|
||||
);
|
||||
}
|
||||
if to_state_file.exists()
|
||||
&& to_state_file
|
||||
.metadata()
|
||||
.with_context(|| format!("failed to stat {}", to_state_file.display()))?
|
||||
.len()
|
||||
> 0
|
||||
{
|
||||
if !force {
|
||||
anyhow::bail!(
|
||||
"target SQLite state file {} already exists and is non-empty; re-run with --force to overwrite",
|
||||
to_state_file.display()
|
||||
);
|
||||
}
|
||||
std::fs::remove_file(to_state_file)
|
||||
.with_context(|| format!("failed to remove {}", to_state_file.display()))?;
|
||||
}
|
||||
|
||||
let legacy = sled::open(from_sled_state).with_context(|| {
|
||||
format!(
|
||||
"failed to open legacy sled state {}",
|
||||
from_sled_state.display()
|
||||
)
|
||||
})?;
|
||||
let store = Store::load_or_init(to_state_file, Vec::new(), Duration::from_secs(1)).await?;
|
||||
|
||||
import_tree_raw(&store.pool, &legacy, "meta", "meta", "key", "value").await?;
|
||||
import_enroll_tokens(&store.pool, &legacy).await?;
|
||||
import_agents(&store.pool, &legacy).await?;
|
||||
import_agent_meta(&store.pool, &legacy).await?;
|
||||
import_audit_events(&store.pool, &legacy).await?;
|
||||
import_active_alerts(&store.pool, &legacy).await?;
|
||||
import_alert_transitions(&store.pool, &legacy).await?;
|
||||
|
||||
info!(
|
||||
from = %from_sled_state.display(),
|
||||
to = %to_state_file.display(),
|
||||
"imported legacy sled state into SQLite"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use super::*;
|
||||
|
||||
impl Store {
|
||||
pub async fn migrate_sqlite_state(
|
||||
from_sqlite_state: &Path,
|
||||
to_state_file: &Path,
|
||||
force: bool,
|
||||
) -> Result<()> {
|
||||
if !from_sqlite_state.is_file() {
|
||||
anyhow::bail!(
|
||||
"legacy sqlite state {} does not exist or is not a file",
|
||||
from_sqlite_state.display()
|
||||
);
|
||||
}
|
||||
if to_state_file.is_dir() {
|
||||
anyhow::bail!(
|
||||
"target state file {} is a directory",
|
||||
to_state_file.display()
|
||||
);
|
||||
}
|
||||
let is_same_file = match (
|
||||
std::fs::canonicalize(from_sqlite_state),
|
||||
std::fs::canonicalize(to_state_file),
|
||||
) {
|
||||
(Ok(from_canon), Ok(to_canon)) => from_canon == to_canon,
|
||||
_ => from_sqlite_state == to_state_file,
|
||||
};
|
||||
|
||||
let actual_from_path = if is_same_file {
|
||||
let mut bak = to_state_file.to_path_buf();
|
||||
let mut file_name = bak.file_name().unwrap_or_default().to_os_string();
|
||||
file_name.push(".bak");
|
||||
bak.set_file_name(file_name);
|
||||
|
||||
if bak.exists() {
|
||||
if !force {
|
||||
anyhow::bail!(
|
||||
"backup file {} already exists; re-run with --force to overwrite",
|
||||
bak.display()
|
||||
);
|
||||
}
|
||||
std::fs::remove_file(&bak).with_context(|| {
|
||||
format!("failed to remove existing backup {}", bak.display())
|
||||
})?;
|
||||
}
|
||||
|
||||
std::fs::rename(from_sqlite_state, &bak)
|
||||
.with_context(|| "failed to rename legacy state for in-place migration")?;
|
||||
bak
|
||||
} else {
|
||||
if to_state_file.exists()
|
||||
&& to_state_file
|
||||
.metadata()
|
||||
.with_context(|| format!("failed to stat {}", to_state_file.display()))?
|
||||
.len()
|
||||
> 0
|
||||
{
|
||||
if !force {
|
||||
anyhow::bail!(
|
||||
"target SQLite state file {} already exists and is non-empty; re-run with --force to overwrite",
|
||||
to_state_file.display()
|
||||
);
|
||||
}
|
||||
std::fs::remove_file(to_state_file)
|
||||
.with_context(|| format!("failed to remove {}", to_state_file.display()))?;
|
||||
}
|
||||
from_sqlite_state.to_path_buf()
|
||||
};
|
||||
|
||||
let store = Store::load_or_init(to_state_file, Vec::new(), Duration::from_secs(1)).await?;
|
||||
|
||||
let from_path_str = actual_from_path
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid path"))?;
|
||||
|
||||
sqlx::query(&format!("ATTACH DATABASE '{}' AS legacy", from_path_str))
|
||||
.execute(&store.pool)
|
||||
.await
|
||||
.with_context(|| "failed to attach legacy database")?;
|
||||
|
||||
let tables = [
|
||||
"meta",
|
||||
"enroll_tokens",
|
||||
"agents",
|
||||
"agent_meta",
|
||||
"known_devices",
|
||||
"device_identifiers",
|
||||
"audit_events",
|
||||
"active_alerts",
|
||||
"alert_transitions",
|
||||
];
|
||||
|
||||
for table in tables {
|
||||
let q = format!("INSERT INTO {} SELECT * FROM legacy.{}", table, table);
|
||||
sqlx::query(&q)
|
||||
.execute(&store.pool)
|
||||
.await
|
||||
.with_context(|| format!("failed to migrate table {}", table))?;
|
||||
}
|
||||
|
||||
sqlx::query("DETACH DATABASE legacy")
|
||||
.execute(&store.pool)
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
from = %from_sqlite_state.display(),
|
||||
to = %to_state_file.display(),
|
||||
"migrated legacy sqlite state into new sqlite state"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user