diff options
author | Ellie Huxtable <ellie@elliehuxtable.com> | 2024-01-18 19:17:48 +0000 |
---|---|---|
committer | Ellie Huxtable <ellie@elliehuxtable.com> | 2024-01-18 19:17:48 +0000 |
commit | 60dfc5fc05a17abd7d69b0709d501b259325556b (patch) | |
tree | db17ec8fb6217485fdbc792920610badd5b3db82 | |
parent | 1ada8660581d93fecee5423949dc137fd82894a3 (diff) |
wipellie/config
-rw-r--r-- | Cargo.lock | 12 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | atuin-client/Cargo.toml | 2 | ||||
-rw-r--r-- | atuin-config/Cargo.toml | 22 | ||||
-rw-r--r-- | atuin-config/src/alias/mod.rs | 2 | ||||
-rw-r--r-- | atuin-config/src/alias/record.rs | 244 | ||||
-rw-r--r-- | atuin-config/src/alias/store.rs | 48 | ||||
-rw-r--r-- | atuin-config/src/lib.rs | 2 | ||||
-rw-r--r-- | atuin-server-postgres/Cargo.toml | 2 | ||||
-rw-r--r-- | atuin-server/Cargo.toml | 2 | ||||
-rw-r--r-- | atuin/src/command/client.rs | 6 | ||||
-rw-r--r-- | atuin/src/command/client/config.rs | 5 | ||||
-rw-r--r-- | atuin/src/command/client/config/alias.rs | 16 | ||||
-rw-r--r-- | atuin/src/command/client/config/mod.rs | 19 |
14 files changed, 375 insertions, 9 deletions
@@ -269,6 +269,18 @@ dependencies = [ ] [[package]] +name = "atuin-config" +version = "0.0.1" +dependencies = [ + "atuin-client", + "atuin-common", + "eyre", + "rmp", + "serde", + "uuid", +] + +[[package]] name = "atuin-server" version = "17.2.1" dependencies = [ @@ -6,6 +6,7 @@ members = [ "atuin-server-postgres", "atuin-server-database", "atuin-common", + "atuin-config", ] [workspace.package] @@ -45,6 +46,7 @@ typed-builder = "0.18.0" pretty_assertions = "1.3.0" thiserror = "1.0" rustix = {version = "0.38.30", features=["process", "fs"]} +rmp = { version = "0.8.11" } [workspace.dependencies.reqwest] version = "0.11" diff --git a/atuin-client/Cargo.toml b/atuin-client/Cargo.toml index cbb8d016..3ab991d0 100644 --- a/atuin-client/Cargo.toml +++ b/atuin-client/Cargo.toml @@ -44,7 +44,7 @@ fs-err = { workspace = true } sql-builder = "3" lazy_static = "1" memchr = "2.5" -rmp = { version = "0.8.11" } +rmp = { workspace= true } typed-builder = { workspace = true } tokio = { workspace = true } semver = { workspace = true } diff --git a/atuin-config/Cargo.toml b/atuin-config/Cargo.toml new file mode 100644 index 00000000..497a25ee --- /dev/null +++ b/atuin-config/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "atuin-config" +edition = "2021" +version = "0.0.1" +authors = {workspace = true} + +rust-version = { workspace = true } +license = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +readme.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +atuin-common = { path = "../atuin-common", version = "17.2.1" } +atuin-client = { path = "../atuin-client", version = "17.2.1" } + +uuid = { workspace = true } +serde = { workspace = true } +rmp = { workspace= true } +eyre = {workspace = true} diff --git a/atuin-config/src/alias/mod.rs b/atuin-config/src/alias/mod.rs new file mode 100644 index 00000000..b5f67df6 --- /dev/null +++ b/atuin-config/src/alias/mod.rs @@ -0,0 +1,2 @@ +pub mod record; +pub mod store; diff --git a/atuin-config/src/alias/record.rs b/atuin-config/src/alias/record.rs new file mode 100644 index 00000000..c74f462e --- /dev/null +++ b/atuin-config/src/alias/record.rs @@ -0,0 +1,244 @@ +use eyre::{bail, eyre, Result}; +use rmp::decode::Bytes; +use rmp::encode::RmpWrite; +use uuid::Uuid; + +pub const CONFIG_ALIAS_VERSION: &str = "v0"; +pub const CONFIG_ALIAS_TAG: &str = "config-alias"; + +use atuin_common::record::{DecryptedData, Host, HostId}; + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct AliasId(Uuid); + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Alias { + pub id: AliasId, + pub name: String, + pub definition: String, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum AliasRecord { + Create(Alias), // Create a history record + Delete(AliasId), // Delete a history record, identified by ID +} + +impl AliasRecord { + pub fn serialize(&self) -> Result<DecryptedData> { + use rmp::encode; + + let mut output = vec![]; + + // write a 0u8 for create, and a 1u8 for delete + // followed by the data + // we don't need to write an explicit version in here, because + // 1. it's stored in the overall record + // 2. we write the field count anyways + match self { + AliasRecord::Create(alias) => { + // write the type + encode::write_u8(&mut output, 0)?; + + // write how many fields we are writing + encode::write_array_len(&mut output, 3)?; + + // write the fields + let (most, least) = alias.id.0.as_u64_pair(); + encode::write_u64(&mut output, most)?; + encode::write_u64(&mut output, least)?; + + encode::write_str(&mut output, &alias.name)?; + encode::write_str(&mut output, &alias.definition)?; + } + AliasRecord::Delete(id) => { + // write the type + encode::write_u8(&mut output, 1)?; + + // write how many fields we are writing + encode::write_array_len(&mut output, 1)?; + + let (most, least) = id.0.as_u64_pair(); + encode::write_u64(&mut output, most)?; + encode::write_u64(&mut output, least)?; + } + } + + Ok(DecryptedData(output)) + } + + pub fn deserialize(data: &DecryptedData, version: &str) -> Result<AliasRecord> { + use rmp::decode; + + if version != CONFIG_ALIAS_VERSION { + bail!("Invalid version for AliasRecord::deserialize"); + } + + fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report { + eyre!("{err:?}") + } + + let mut bytes = Bytes::new(&data.0); + + // read the type + let record_type = decode::read_u8(&mut bytes).map_err(error_report)?; + + // read the number of fields + let field_count = decode::read_array_len(&mut bytes).map_err(error_report)?; + + match record_type { + 0 => { + // create + if field_count != 3 { + return Err(eyre::eyre!("Invalid field count for AliasRecord::Create")); + } + + let most = decode::read_u64(&mut bytes).map_err(error_report)?; + let least = decode::read_u64(&mut bytes).map_err(error_report)?; + let id = Uuid::from_u64_pair(most, least); + + let bytes = bytes.remaining_slice(); + let (name, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?; + let (definition, bytes) = + decode::read_str_from_slice(bytes).map_err(error_report)?; + + if !bytes.is_empty() { + bail!("trailing bytes in encoded history. malformed") + } + + let alias = Alias { + id: AliasId(id), + name: name.to_string(), + definition: definition.to_string(), + }; + + Ok(AliasRecord::Create(alias)) + } + 1 => { + // delete + if field_count != 1 { + return Err(eyre::eyre!("Invalid field count for AliasRecord::Delete")); + } + + let most = decode::read_u64(&mut bytes).map_err(error_report)?; + let least = decode::read_u64(&mut bytes).map_err(error_report)?; + let id = Uuid::from_u64_pair(most, least); + + let alias = AliasId(id); + + Ok(AliasRecord::Delete(alias)) + } + _ => Err(eyre::eyre!("Invalid record type")), + } + } +} + +#[cfg(test)] +mod tests { + use atuin_common::record::DecryptedData; + + use super::{Alias, AliasRecord}; + + #[test] + fn test_encode_alias_create() { + let snapshot = &[ + 204, 0, 147, 207, 1, 141, 29, 204, 218, 86, 127, 99, 207, 185, 95, 141, 153, 16, 161, + 141, 155, 164, 116, 101, 115, 116, 164, 116, 101, 115, 116, + ]; + + // write a test for encoding an alias + let alias = Alias { + id: super::AliasId(uuid::uuid!("018d1dccda567f63b95f8d9910a18d9b")), + name: "test".to_string(), + definition: "test".to_string(), + }; + let alias_record = super::AliasRecord::Create(alias); + + let encoded = alias_record.serialize().expect("failed to encode alias"); + + assert_eq!(encoded.0.len(), 31); + assert_eq!(encoded.0, snapshot); + } + + #[test] + fn test_decode_alias_create() { + let snapshot = vec![ + 204, 0, 147, 207, 1, 141, 29, 204, 218, 86, 127, 99, 207, 185, 95, 141, 153, 16, 161, + 141, 155, 164, 116, 101, 115, 116, 164, 116, 101, 115, 116, + ]; + + let alias = Alias { + id: super::AliasId(uuid::uuid!("018d1dccda567f63b95f8d9910a18d9b")), + name: "test".to_string(), + definition: "test".to_string(), + }; + + let decoded = AliasRecord::deserialize(&DecryptedData(snapshot), "v0") + .expect("failed to decode alias"); + + assert_eq!(decoded, AliasRecord::Create(alias)); + } + + #[test] + fn test_alias_encode_decode_create() { + let alias = Alias { + id: super::AliasId(uuid::uuid!("018d1dccda567f63b95f8d9910a18d9b")), + name: "test".to_string(), + definition: "test".to_string(), + }; + + let create = AliasRecord::Create(alias.clone()); + + let encoded = create.serialize().expect("failed to serialize"); + + let decoded = AliasRecord::deserialize(&encoded, "v0").expect("failed to deserialize"); + + assert_eq!(create, decoded); + } + + #[test] + fn test_alias_encode_delete() { + let snapshot = &[ + 204, 1, 145, 207, 1, 141, 29, 227, 138, 189, 120, 83, 207, 140, 223, 145, 3, 114, 55, + 127, 126, + ]; + + let record = AliasRecord::Delete(super::AliasId(uuid::uuid!( + "018d1de38abd78538cdf910372377f7e" + ))); + + let encoded = record.serialize().expect("failed to serialize"); + + assert_eq!(encoded.0.len(), 21); + assert_eq!(encoded.0, snapshot); + } + + #[test] + fn test_alias_decode_delete() { + let snapshot = vec![ + 204, 1, 145, 207, 1, 141, 29, 227, 138, 189, 120, 83, 207, 140, 223, 145, 3, 114, 55, + 127, 126, + ]; + let record = AliasRecord::Delete(super::AliasId(uuid::uuid!( + "018d1de38abd78538cdf910372377f7e" + ))); + + let decoded = AliasRecord::deserialize(&DecryptedData(snapshot), "v0") + .expect("failed to decode alias"); + + assert_eq!(decoded, record); + } + + #[test] + fn test_alias_encode_decode_delete() { + let delete = AliasRecord::Delete(super::AliasId(uuid::uuid!( + "018d1dccda567f63b95f8d9910a18d9b" + ))); + + let encoded = delete.serialize().expect("failed to serialize"); + + let decoded = AliasRecord::deserialize(&encoded, "v0").expect("failed to deserialize"); + + assert_eq!(delete, decoded); + } +} diff --git a/atuin-config/src/alias/store.rs b/atuin-config/src/alias/store.rs new file mode 100644 index 00000000..f7709a95 --- /dev/null +++ b/atuin-config/src/alias/store.rs @@ -0,0 +1,48 @@ +use eyre::{bail, eyre, Result}; + +use atuin_client::record::{encryption::PASETO_V4, sqlite_store::SqliteStore, store::Store}; +use atuin_common::record::{Host, HostId, Record, RecordId, RecordIdx}; + +use super::record::{AliasId, AliasRecord, CONFIG_ALIAS_TAG, CONFIG_ALIAS_VERSION}; + +#[derive(Debug)] +pub struct AliasStore { + pub store: SqliteStore, + pub host_id: HostId, + pub encryption_key: [u8; 32], +} + +impl AliasStore { + pub fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> Self { + AliasStore { + store, + host_id, + encryption_key, + } + } + + async fn push_record(&self, record: AliasRecord) -> Result<(RecordId, RecordIdx)> { + let bytes = record.serialize()?; + let idx = self + .store + .last(self.host_id, CONFIG_ALIAS_TAG) + .await? + .map_or(0, |p| p.idx + 1); + + let record = Record::builder() + .host(Host::new(self.host_id)) + .version(CONFIG_ALIAS_VERSION.to_string()) + .tag(CONFIG_ALIAS_TAG.to_string()) + .idx(idx) + .data(bytes) + .build(); + + let id = record.id; + + self.store + .push(&record.encrypt::<PASETO_V4>(&self.encryption_key)) + .await?; + + Ok((id, idx)) + } +} diff --git a/atuin-config/src/lib.rs b/atuin-config/src/lib.rs new file mode 100644 index 00000000..31e998cd --- /dev/null +++ b/atuin-config/src/lib.rs @@ -0,0 +1,2 @@ +pub mod alias; + diff --git a/atuin-server-postgres/Cargo.toml b/atuin-server-postgres/Cargo.toml index b1adcdd3..c00cf9f8 100644 --- a/atuin-server-postgres/Cargo.toml +++ b/atuin-server-postgres/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "atuin-server-postgres" -edition = "2018" +edition = "2021" description = "server postgres database library for atuin" version = { workspace = true } diff --git a/atuin-server/Cargo.toml b/atuin-server/Cargo.toml index 4464ee0e..1af263e9 100644 --- a/atuin-server/Cargo.toml +++ b/atuin-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "atuin-server" -edition = "2018" +edition = "2021" description = "server library for atuin" rust-version = { workspace = true } diff --git a/atuin/src/command/client.rs b/atuin/src/command/client.rs index 38048504..3313e878 100644 --- a/atuin/src/command/client.rs +++ b/atuin/src/command/client.rs @@ -50,6 +50,9 @@ pub enum Cmd { #[command(subcommand)] Store(store::Cmd), + #[command(subcommand)] + Config(config::Cmd), + /// Print example configuration #[command()] DefaultConfig, @@ -100,9 +103,10 @@ impl Cmd { Self::Kv(kv) => kv.run(&settings, &sqlite_store).await, Self::Store(store) => store.run(&settings, &db, sqlite_store).await, + Self::Config(config) => config.run(&settings).await, Self::DefaultConfig => { - config::run(); + println!("{}", Settings::example_config()); Ok(()) } } diff --git a/atuin/src/command/client/config.rs b/atuin/src/command/client/config.rs deleted file mode 100644 index f51e45c2..00000000 --- a/atuin/src/command/client/config.rs +++ /dev/null @@ -1,5 +0,0 @@ -use atuin_client::settings::Settings; - -pub fn run() { - println!("{}", Settings::example_config()); -} diff --git a/atuin/src/command/client/config/alias.rs b/atuin/src/command/client/config/alias.rs new file mode 100644 index 00000000..c700b625 --- /dev/null +++ b/atuin/src/command/client/config/alias.rs @@ -0,0 +1,16 @@ +use clap::Parser; +use eyre::Result; + +use atuin_client::settings::Settings; + +#[derive(Subcommand, Debug)] +pub enum Cmd { + Create, +} + +impl Cmd { + pub async fn run(&self, settings: &Settings) -> Result<()> { + println!("omg an alias"); + Ok(()) + } +} diff --git a/atuin/src/command/client/config/mod.rs b/atuin/src/command/client/config/mod.rs new file mode 100644 index 00000000..f12aba88 --- /dev/null +++ b/atuin/src/command/client/config/mod.rs @@ -0,0 +1,19 @@ +use clap::{Args, Subcommand}; +use eyre::Result; + +use atuin_client::settings::Settings; + +pub mod alias; + +#[derive(Subcommand, Debug)] +pub enum Cmd { + Alias(alias::Cmd), +} + +impl Cmd { + pub async fn run(self, settings: &Settings) -> Result<()> { + match self { + Cmd::Alias(cmd) => cmd.run(&settings).await, + } + } +} |