This commit is contained in:
Ellie Huxtable 2024-01-18 19:17:48 +00:00
parent 1ada866058
commit 60dfc5fc05
14 changed files with 375 additions and 9 deletions

12
Cargo.lock generated
View File

@ -268,6 +268,18 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "atuin-config"
version = "0.0.1"
dependencies = [
"atuin-client",
"atuin-common",
"eyre",
"rmp",
"serde",
"uuid",
]
[[package]] [[package]]
name = "atuin-server" name = "atuin-server"
version = "17.2.1" version = "17.2.1"

View File

@ -6,6 +6,7 @@ members = [
"atuin-server-postgres", "atuin-server-postgres",
"atuin-server-database", "atuin-server-database",
"atuin-common", "atuin-common",
"atuin-config",
] ]
[workspace.package] [workspace.package]
@ -45,6 +46,7 @@ typed-builder = "0.18.0"
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
thiserror = "1.0" thiserror = "1.0"
rustix = {version = "0.38.30", features=["process", "fs"]} rustix = {version = "0.38.30", features=["process", "fs"]}
rmp = { version = "0.8.11" }
[workspace.dependencies.reqwest] [workspace.dependencies.reqwest]
version = "0.11" version = "0.11"

View File

@ -44,7 +44,7 @@ fs-err = { workspace = true }
sql-builder = "3" sql-builder = "3"
lazy_static = "1" lazy_static = "1"
memchr = "2.5" memchr = "2.5"
rmp = { version = "0.8.11" } rmp = { workspace= true }
typed-builder = { workspace = true } typed-builder = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
semver = { workspace = true } semver = { workspace = true }

22
atuin-config/Cargo.toml Normal file
View File

@ -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}

View File

@ -0,0 +1,2 @@
pub mod record;
pub mod store;

View File

@ -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);
}
}

View File

@ -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))
}
}

2
atuin-config/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod alias;

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin-server-postgres" name = "atuin-server-postgres"
edition = "2018" edition = "2021"
description = "server postgres database library for atuin" description = "server postgres database library for atuin"
version = { workspace = true } version = { workspace = true }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin-server" name = "atuin-server"
edition = "2018" edition = "2021"
description = "server library for atuin" description = "server library for atuin"
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -50,6 +50,9 @@ pub enum Cmd {
#[command(subcommand)] #[command(subcommand)]
Store(store::Cmd), Store(store::Cmd),
#[command(subcommand)]
Config(config::Cmd),
/// Print example configuration /// Print example configuration
#[command()] #[command()]
DefaultConfig, DefaultConfig,
@ -100,9 +103,10 @@ impl Cmd {
Self::Kv(kv) => kv.run(&settings, &sqlite_store).await, Self::Kv(kv) => kv.run(&settings, &sqlite_store).await,
Self::Store(store) => store.run(&settings, &db, sqlite_store).await, Self::Store(store) => store.run(&settings, &db, sqlite_store).await,
Self::Config(config) => config.run(&settings).await,
Self::DefaultConfig => { Self::DefaultConfig => {
config::run(); println!("{}", Settings::example_config());
Ok(()) Ok(())
} }
} }

View File

@ -1,5 +0,0 @@
use atuin_client::settings::Settings;
pub fn run() {
println!("{}", Settings::example_config());
}

View File

@ -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(())
}
}

View File

@ -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,
}
}
}