use anyhow::Result;
use async_trait::async_trait;
use bytes::Bytes;
use futures_util::Stream;
use serde::{Deserialize, Serialize};
use std::{path::PathBuf, pin::Pin, time::SystemTime};
use warg_crypto::{
hash::{AnyHash, HashAlgorithm},
signing::{self, KeyID, PublicKey},
};
use warg_protocol::{
operator,
package::{self, PackageRecord, Permission, PACKAGE_RECORD_VERSION},
registry::{Checkpoint, PackageName, RecordId, RegistryIndex, TimestampedCheckpoint},
ProtoEnvelope, SerdeEnvelope, Version,
};
mod fs;
pub use fs::*;
#[async_trait]
pub trait RegistryStorage: Send + Sync {
async fn reset(&self, all_registries: bool) -> Result<()>;
async fn load_checkpoint(&self) -> Result<Option<SerdeEnvelope<TimestampedCheckpoint>>>;
async fn store_checkpoint(
&self,
ts_checkpoint: &SerdeEnvelope<TimestampedCheckpoint>,
) -> Result<()>;
async fn load_operator(&self) -> Result<Option<OperatorInfo>>;
async fn store_operator(&self, operator: OperatorInfo) -> Result<()>;
async fn load_packages(&self) -> Result<Vec<PackageInfo>>;
async fn load_package(&self, package: &PackageName) -> Result<Option<PackageInfo>>;
async fn store_package(&self, info: &PackageInfo) -> Result<()>;
async fn load_publish(&self) -> Result<Option<PublishInfo>>;
async fn store_publish(&self, info: Option<&PublishInfo>) -> Result<()>;
}
#[async_trait]
pub trait ContentStorage: Send + Sync {
async fn clear(&self) -> Result<()>;
fn content_location(&self, digest: &AnyHash) -> Option<PathBuf>;
async fn load_content(
&self,
digest: &AnyHash,
) -> Result<Option<Pin<Box<dyn Stream<Item = Result<Bytes>> + Send + Sync>>>>;
async fn store_content(
&self,
stream: Pin<Box<dyn Stream<Item = Result<Bytes>> + Send + Sync>>,
expected_digest: Option<&AnyHash>,
) -> Result<AnyHash>;
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct OperatorInfo {
#[serde(default)]
pub state: operator::LogState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_registry_index: Option<RegistryIndex>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_fetch_token: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PackageInfo {
#[serde(alias = "id")]
pub name: PackageName,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkpoint: Option<Checkpoint>,
#[serde(default)]
pub state: package::LogState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_registry_index: Option<RegistryIndex>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_fetch_token: Option<String>,
}
impl PackageInfo {
pub fn new(name: impl Into<PackageName>) -> Self {
Self {
name: name.into(),
checkpoint: None,
state: package::LogState::default(),
head_registry_index: None,
head_fetch_token: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum PublishEntry {
Init,
Release {
version: Version,
content: AnyHash,
},
Yank {
version: Version,
},
Grant {
key: PublicKey,
permissions: Vec<Permission>,
},
Revoke {
key_id: KeyID,
permissions: Vec<Permission>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublishInfo {
#[serde(alias = "id")]
pub name: PackageName,
pub head: Option<RecordId>,
pub entries: Vec<PublishEntry>,
}
impl PublishInfo {
pub fn initializing(&self) -> bool {
self.entries.iter().any(|e| matches!(e, PublishEntry::Init))
}
pub(crate) fn finalize(
self,
signing_key: &signing::PrivateKey,
) -> Result<ProtoEnvelope<PackageRecord>> {
let mut entries = Vec::with_capacity(self.entries.len());
for entry in self.entries {
match entry {
PublishEntry::Init => {
entries.push(package::PackageEntry::Init {
hash_algorithm: HashAlgorithm::Sha256,
key: signing_key.public_key(),
});
}
PublishEntry::Release { version, content } => {
entries.push(package::PackageEntry::Release { version, content });
}
PublishEntry::Yank { version } => {
entries.push(package::PackageEntry::Yank { version })
}
PublishEntry::Grant { key, permissions } => {
entries.push(package::PackageEntry::GrantFlat { key, permissions })
}
PublishEntry::Revoke {
key_id,
permissions,
} => entries.push(package::PackageEntry::RevokeFlat {
key_id,
permissions,
}),
}
}
let record = package::PackageRecord {
prev: self.head,
version: PACKAGE_RECORD_VERSION,
timestamp: SystemTime::now(),
entries,
};
Ok(ProtoEnvelope::signed_contents(signing_key, record)?)
}
}