use std::sync::Arc;
use std::collections::HashMap;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct ArtifactId {
pub namespace: Option<String>,
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum ArtifactFormat {
Maven3,
Pip,
Rust,
Go,
Nuget,
Npm,
Gem,
Deb,
Rpm,
Helm,
Docker,
Conda,
Composer,
Znippy,
Raw,
}
impl ArtifactFormat {
pub fn znippy_type_id(&self) -> i8 {
use znippy_common::plugins::skeletons;
use znippy_common::plugin::ArchiveTypePlugin;
match self {
ArtifactFormat::Rust => 1,
ArtifactFormat::Pip => 2,
ArtifactFormat::Maven3 => 3,
ArtifactFormat::Go => skeletons::GoPlugin.type_id(),
ArtifactFormat::Nuget => skeletons::NugetPlugin.type_id(),
ArtifactFormat::Npm => skeletons::NpmPlugin.type_id(),
ArtifactFormat::Rpm => skeletons::RpmPlugin.type_id(),
ArtifactFormat::Deb => skeletons::DebPlugin.type_id(),
ArtifactFormat::Gem => skeletons::GemPlugin.type_id(),
ArtifactFormat::Docker => skeletons::DockerPlugin.type_id(),
ArtifactFormat::Helm => skeletons::HelmPlugin.type_id(),
ArtifactFormat::Conda => skeletons::CondaPlugin.type_id(),
ArtifactFormat::Composer => skeletons::ComposerPlugin.type_id(),
ArtifactFormat::Znippy => 100,
ArtifactFormat::Raw => 0,
}
}
pub fn from_format_str(s: &str) -> Option<Self> {
Some(match s.to_lowercase().as_str() {
"maven2" | "maven" | "maven3" | "java" => ArtifactFormat::Maven3,
"pip" | "pypi" | "python" => ArtifactFormat::Pip,
"rust" | "cargo" => ArtifactFormat::Rust,
"go" | "golang" => ArtifactFormat::Go,
"nuget" | "dotnet" => ArtifactFormat::Nuget,
"npm" | "node" => ArtifactFormat::Npm,
"gem" | "ruby" | "rubygems" => ArtifactFormat::Gem,
"deb" | "debian" | "apt" => ArtifactFormat::Deb,
"rpm" | "yum" | "dnf" => ArtifactFormat::Rpm,
"helm" | "chart" => ArtifactFormat::Helm,
"docker" | "oci" => ArtifactFormat::Docker,
"conda" | "anaconda" => ArtifactFormat::Conda,
"composer" | "php" => ArtifactFormat::Composer,
"znippy" | "snippy" => ArtifactFormat::Znippy,
"raw" => ArtifactFormat::Raw,
_ => return None,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum StorageType {
Znippy,
Rocksdb,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum RepositoryType {
Rust,
Pip,
Maven3,
Go,
Nuget,
Npm,
Gem,
Deb,
Rpm,
Helm,
Docker,
Conda,
Composer,
Znippy,
Raw,
}
impl RepositoryType {
pub fn endpoint_name(&self) -> &'static str {
match self {
RepositoryType::Rust => "rust",
RepositoryType::Pip => "pip",
RepositoryType::Maven3 => "maven3",
RepositoryType::Go => "go",
RepositoryType::Nuget => "nuget",
RepositoryType::Npm => "npm",
RepositoryType::Gem => "gem",
RepositoryType::Deb => "deb",
RepositoryType::Rpm => "rpm",
RepositoryType::Helm => "helm",
RepositoryType::Docker => "docker",
RepositoryType::Conda => "conda",
RepositoryType::Composer => "composer",
RepositoryType::Znippy => "znippy",
RepositoryType::Raw => "raw",
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ArchiveInfo {
pub file_count: u64,
pub total_uncompressed_bytes: u64,
pub archive_path: String,
}
#[async_trait]
pub trait RepositoryBackendTrait: Send + Sync {
fn name(&self) -> &str;
fn format(&self) -> ArtifactFormat;
fn is_writable(&self) -> bool;
fn fetch(&self, id: &ArtifactId) -> anyhow::Result<Option<Vec<u8>>>;
fn put(&self, id: &ArtifactId, data: &[u8]) -> anyhow::Result<()>;
fn archive_files(&self, prefix: Option<&str>) -> anyhow::Result<Vec<String>> {
let _ = prefix;
Ok(Vec::new())
}
fn archive_info(&self) -> anyhow::Result<ArchiveInfo> {
Ok(ArchiveInfo::default())
}
fn list(&self, name_filter: Option<&str>, limit: usize) -> anyhow::Result<Vec<ArtifactEntry>> {
let _ = (name_filter, limit);
Ok(Vec::new())
}
fn fetch_many_with_upstreams(
&self,
upstreams: &[Arc<dyn RepositoryBackendTrait>],
ids: &[ArtifactId],
) -> anyhow::Result<HashMap<ArtifactId, Vec<u8>>> {
let mut result = HashMap::new();
for id in ids {
if let Some(data) = self.fetch(id)? {
result.insert(id.clone(), data);
continue;
}
for up in upstreams {
if let Some(data) = up.fetch(id)? {
result.insert(id.clone(), data);
break;
}
}
}
Ok(result)
}
fn has_archive(&self) -> bool {
false
}
fn handle_http2_request(
&self,
method: &str,
suburl: &str,
body: &[u8],
) -> anyhow::Result<(u16, Vec<(String, String)>, Vec<u8>)>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteRepository {
pub name: String,
pub format: String,
pub repo_type: String,
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteAsset {
pub path: String,
pub download_url: String,
pub content_type: Option<String>,
pub size: Option<u64>,
}
#[async_trait]
pub trait ConnectorTrait: Send + Sync {
fn name(&self) -> &str;
async fn list_repositories(&self) -> anyhow::Result<Vec<RemoteRepository>>;
async fn list_assets(&self, repository: &str) -> anyhow::Result<Vec<RemoteAsset>>;
async fn download_asset(&self, asset: &RemoteAsset) -> anyhow::Result<Vec<u8>>;
async fn upload_asset(
&self,
repository: &str,
path: &str,
data: &[u8],
) -> anyhow::Result<()>;
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RepositoryInfo {
pub name: String,
pub repo_type: String,
pub writable: bool,
pub has_archive: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ArtifactEntry {
pub id: ArtifactId,
pub size_bytes: i64,
pub content_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Health {
pub status: String,
pub version: String,
pub uptime_seconds: i64,
}
#[async_trait]
pub trait HolgerObject: Send + Sync {
async fn fetch(&self, repository: &str, id: &ArtifactId) -> anyhow::Result<Option<Vec<u8>>>;
async fn put(&self, repository: &str, id: &ArtifactId, data: &[u8]) -> anyhow::Result<()>;
async fn list_repositories(&self) -> anyhow::Result<Vec<RepositoryInfo>>;
async fn list_artifacts(
&self,
repository: &str,
name_filter: Option<String>,
limit: u32,
page_token: Option<String>,
) -> anyhow::Result<(Vec<ArtifactEntry>, String)> {
let _ = (repository, name_filter, limit, page_token);
Ok((Vec::new(), String::new()))
}
async fn list_archive_files(
&self,
repository: &str,
prefix: Option<String>,
) -> anyhow::Result<Vec<String>> {
let _ = (repository, prefix);
Ok(Vec::new())
}
async fn archive_info(&self, repository: &str) -> anyhow::Result<ArchiveInfo> {
let _ = repository;
Ok(ArchiveInfo::default())
}
async fn health(&self) -> anyhow::Result<Health>;
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "testmatrix")]
fn fstatus(component: &str, check: &str, ok: bool, detail: &str) {
nornir_testmatrix::functional_status(component, check, ok, detail);
}
#[test]
fn znippy_type_ids_match_znippy_skeletons() {
assert_eq!(ArtifactFormat::Go.znippy_type_id(), 4);
assert_eq!(ArtifactFormat::Nuget.znippy_type_id(), 5);
assert_eq!(ArtifactFormat::Npm.znippy_type_id(), 6);
assert_eq!(ArtifactFormat::Rpm.znippy_type_id(), 8);
assert_eq!(ArtifactFormat::Deb.znippy_type_id(), 9);
assert_eq!(ArtifactFormat::Gem.znippy_type_id(), 11);
assert_eq!(ArtifactFormat::Docker.znippy_type_id(), 12);
assert_eq!(ArtifactFormat::Helm.znippy_type_id(), 13);
assert_eq!(ArtifactFormat::Conda.znippy_type_id(), 14);
assert_eq!(ArtifactFormat::Composer.znippy_type_id(), 17);
assert_eq!(ArtifactFormat::Rust.znippy_type_id(), 1);
assert_eq!(ArtifactFormat::Pip.znippy_type_id(), 2);
assert_eq!(ArtifactFormat::Maven3.znippy_type_id(), 3);
#[cfg(feature = "testmatrix")]
{
let ok = ArtifactFormat::Helm.znippy_type_id() == 13
&& ArtifactFormat::Rust.znippy_type_id() == 1
&& ArtifactFormat::Composer.znippy_type_id() == 17;
fstatus(
"traits",
"znippy_type_ids_match",
ok,
&format!(
"Rust=1 Pip=2 Maven3=3 Helm=13 Composer=17 (helm={})",
ArtifactFormat::Helm.znippy_type_id()
),
);
}
}
#[test]
fn format_str_roundtrips_aliases() {
assert_eq!(ArtifactFormat::from_format_str("cargo"), Some(ArtifactFormat::Rust));
assert_eq!(ArtifactFormat::from_format_str("golang"), Some(ArtifactFormat::Go));
assert_eq!(ArtifactFormat::from_format_str("dotnet"), Some(ArtifactFormat::Nuget));
assert_eq!(ArtifactFormat::from_format_str("oci"), Some(ArtifactFormat::Docker));
assert_eq!(ArtifactFormat::from_format_str("nope"), None);
#[cfg(feature = "testmatrix")]
{
let ok = ArtifactFormat::from_format_str("cargo") == Some(ArtifactFormat::Rust)
&& ArtifactFormat::from_format_str("oci") == Some(ArtifactFormat::Docker)
&& ArtifactFormat::from_format_str("nope").is_none();
fstatus(
"traits",
"format_str_aliases_roundtrip",
ok,
"cargo->Rust golang->Go dotnet->Nuget oci->Docker nope->None",
);
}
}
}