refactor
All checks were successful
ci/woodpecker/push/build Pipeline was successful

This commit is contained in:
JMARyA 2024-12-29 09:09:54 +01:00
parent 376e4f4eb8
commit 6eb3678c1c
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
7 changed files with 252 additions and 134 deletions

View file

@ -13,4 +13,3 @@ steps:
username: jmarya username: jmarya
password: password:
from_secret: registry_token from_secret: registry_token

26
src/pkg/arch.rs Normal file
View file

@ -0,0 +1,26 @@
#![allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Architecture {
x86_64,
aarch64,
any,
}
impl Architecture {
pub fn parse(val: &str) -> Option<Architecture> {
match val {
"x86_64" => Some(Architecture::x86_64),
"aarch64" => Some(Architecture::aarch64),
"any" => Some(Architecture::any),
_ => None,
}
}
pub fn to_string(&self) -> String {
match self {
Architecture::x86_64 => "x86_64".to_string(),
Architecture::aarch64 => "aarch64".to_string(),
Architecture::any => "any".to_string(),
}
}
}

13
src/pkg/db.rs Normal file
View file

@ -0,0 +1,13 @@
// TODO : Implement repo.db.tar.gz parsing
pub struct RepoDB {
file: String,
}
impl RepoDB {
pub fn new(file: &str) -> Self {
Self {
file: file.to_string(),
}
}
}

9
src/pkg/mod.rs Normal file
View file

@ -0,0 +1,9 @@
// TODO : Read DB Info
// TODO : Read PKG Info + Content
pub mod repo;
pub use repo::Repository;
pub mod package;
pub use package::Package;
pub mod arch;
pub mod db;

View file

@ -1,131 +1,69 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub struct Repository { use super::{Repository, arch::Architecture};
pub name: String,
}
impl Repository {
pub fn list() -> Vec<String> {
let mut repos = vec![];
for entry in std::fs::read_dir("./data").unwrap() {
let path = entry.unwrap().path();
let file_name = path.file_name().unwrap().to_str().unwrap().to_string();
repos.push(file_name);
}
repos
}
pub fn create(name: &str) -> Repository {
let path = PathBuf::from("./data").join(name);
std::fs::create_dir_all(path).unwrap();
Repository::new(name).unwrap()
}
}
impl Repository {
pub fn new(name: &str) -> Option<Self> {
if PathBuf::from("./data").join(name).exists() {
Some(Repository {
name: name.to_string(),
})
} else {
None
}
}
pub fn arch(&self) -> Vec<String> {
let dir_path = PathBuf::from("./data").join(&self.name);
let mut arch = vec![];
if let Ok(entries) = std::fs::read_dir(dir_path) {
for entry in entries.filter_map(Result::ok) {
let file_name = entry.file_name().into_string().unwrap_or_default();
arch.push(file_name);
}
}
arch
}
pub fn base_path(&self, arch: &str) -> PathBuf {
PathBuf::from("./data").join(&self.name).join(arch)
}
pub fn db_content(&self, arch: &str) -> Option<Vec<u8>> {
std::fs::read(
self.base_path(arch)
.join(format!("{}.db.tar.gz", self.name)),
)
.ok()
}
pub fn sig_content(&self, arch: &str) -> Option<Vec<u8>> {
std::fs::read(
self.base_path(arch)
.join(format!("{}.db.tar.gz.sig", self.name)),
)
.ok()
}
pub fn extract_pkg_name(name: &str) -> (String, String, String, String) {
// "{}-{}-{}-{}.pkg.tar.zst"
let splitted: Vec<&str> = name.split('-').collect();
let name = splitted.get(0).unwrap();
let version = splitted.get(1).unwrap();
let rel = splitted.get(2).unwrap();
let arch = splitted.get(3).unwrap().trim_end_matches(".pkg.tar.zst");
(
name.to_string(),
version.to_string(),
rel.to_string(),
arch.to_string(),
)
}
pub fn get_pkg(&self, arch: &str, pkg_name: &str) -> Option<Package> {
let pkg_name = if pkg_name.ends_with(".sig") {
pkg_name.trim_end_matches(".sig").to_string()
} else {
pkg_name.to_string()
};
let (name, version, _, _) = Repository::extract_pkg_name(&pkg_name);
let pkg = Package::new(&self.name, &arch, &name, &version);
if pkg.exists() {
return Some(pkg);
} else {
None
}
}
}
/// General Package
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Package { pub struct Package {
/// Repository of the package
repo: String, repo: String,
arch: String, /// `Architecture` of the package
arch: Architecture,
/// Name of the package
name: String, name: String,
/// Version of the package
version: Option<String>, version: Option<String>,
} }
impl Package { impl Package {
pub fn new(repo: &str, arch: &str, pkg_name: &str, version: &str) -> Self { /// Create a new package
pub fn new(repo: &str, arch: Architecture, pkg_name: &str, version: &str) -> Self {
Package { Package {
repo: repo.to_string(), repo: repo.to_string(),
arch: arch.to_string(), arch: arch,
name: pkg_name.to_string(), name: pkg_name.to_string(),
version: Some(version.to_string()), version: Some(version.to_string()),
} }
} }
/// Extract values from a package filename
pub fn extract_pkg_name(file_name: &str) -> Option<(String, String, String, Architecture)> {
// Extract (assuming the filename is "<pkg_name>-<version>-<relation>-<arch>.pkg.tar.zst")
let mut splitted = file_name.split('-').collect::<Vec<&str>>();
let arch = splitted.pop()?;
assert!(arch.ends_with(".pkg.tar.zst"));
let arch = arch.trim_end_matches(".pkg.tar.zst");
let relation = splitted.pop()?;
let version = splitted.pop()?;
let pkg_name = splitted.join(" ");
return Some((
pkg_name,
version.to_string(),
relation.to_string(),
Architecture::parse(arch)?,
));
}
/// Parse a pkg filename
pub fn from_filename(repo: &str, file_name: &str) -> Package {
let (pkg_name, version, _, arch) = Package::extract_pkg_name(file_name).unwrap();
Self {
repo: repo.to_string(),
arch,
name: pkg_name,
version: Some(version.to_string()),
}
}
/// Find a package with latest version
pub fn find(repo: &str, arch: &str, pkg_name: &str) -> Self { pub fn find(repo: &str, arch: &str, pkg_name: &str) -> Self {
let mut base = Package { let mut base = Package {
repo: repo.to_string(), repo: repo.to_string(),
arch: arch.to_string(), arch: Architecture::parse(arch).unwrap(),
name: pkg_name.to_string(), name: pkg_name.to_string(),
version: None, version: None,
}; };
@ -138,6 +76,7 @@ impl Package {
base base
} }
/// Save a new package to repository
pub fn save(&self, pkg: Vec<u8>, sig: Option<Vec<u8>>) { pub fn save(&self, pkg: Vec<u8>, sig: Option<Vec<u8>>) {
let pkg_file = self.base_path().join(&self.file_name()); let pkg_file = self.base_path().join(&self.file_name());
let sig_file = self.base_path().join(format!("{}.sig", self.file_name())); let sig_file = self.base_path().join(format!("{}.sig", self.file_name()));
@ -149,64 +88,69 @@ impl Package {
let db_file = PathBuf::from("./data") let db_file = PathBuf::from("./data")
.join(&self.repo) .join(&self.repo)
.join(&self.arch) .join(&self.arch.to_string())
.join(format!("{}.db.tar.gz", self.repo)); .join(format!("{}.db.tar.gz", self.repo));
run_command(vec![ repo_add(db_file.to_str().unwrap(), pkg_file.to_str().unwrap());
"repo-add",
db_file.to_str().unwrap(),
pkg_file.to_str().unwrap(),
]);
if &self.arch == "any" { // Add to any arch repo db if `arch=any`
if self.arch == Architecture::any {
let archs = Repository::new(&self.repo).unwrap().arch(); let archs = Repository::new(&self.repo).unwrap().arch();
for arch in archs { for arch in archs {
if arch == "any" { if arch == Architecture::any {
continue; continue;
} }
let db_file = PathBuf::from("./data") let db_file = PathBuf::from("./data")
.join(&self.repo) .join(&self.repo)
.join(&arch) .join(&arch.to_string())
.join(format!("{}.db.tar.gz", self.repo)); .join(format!("{}.db.tar.gz", self.repo));
run_command(vec![ repo_add(db_file.to_str().unwrap(), pkg_file.to_str().unwrap());
"repo-add",
db_file.to_str().unwrap(),
pkg_file.to_str().unwrap(),
]);
} }
} }
} }
fn base_path(&self) -> PathBuf { fn base_path(&self) -> PathBuf {
Path::new("./data").join(&self.repo).join(&self.arch) // <repo>/<arch>/<pkg>/
let p = Path::new("./data")
.join(&self.repo)
.join(&self.arch.to_string())
.join(&self.name);
std::fs::create_dir_all(&p).unwrap();
p
} }
/// Switch the `Architecture` of the package
pub fn switch_arch(&self, arch: &str) -> Self { pub fn switch_arch(&self, arch: &str) -> Self {
let mut new = self.clone(); let mut new = self.clone();
new.arch = arch.to_string(); new.arch = Architecture::parse(arch).unwrap();
new new
} }
pub fn exists(&self) -> bool { /// Switch to a specific `version` of the package
let pkg_file = self.base_path().join(self.file_name());
pkg_file.exists()
}
pub fn get_version(&self, version: &str) -> Self { pub fn get_version(&self, version: &str) -> Self {
let mut new_pkg = self.clone(); let mut new_pkg = self.clone();
new_pkg.version = Some(version.to_string()); new_pkg.version = Some(version.to_string());
new_pkg new_pkg
} }
/// Check if the specific package exists
pub fn exists(&self) -> bool {
let pkg_file = self.base_path().join(self.file_name());
pkg_file.exists()
}
/// Checks if the package has a signature
pub fn is_signed(&self) -> bool { pub fn is_signed(&self) -> bool {
let signed_file = self.base_path().join(format!("{}.sig", self.file_name())); let signed_file = self.base_path().join(format!("{}.sig", self.file_name()));
signed_file.exists() signed_file.exists()
} }
/// Build a pkg filename from the packages values
pub fn file_name(&self) -> String { pub fn file_name(&self) -> String {
// TODO : pkgrel support
format!( format!(
"{}-{}-{}-{}.pkg.tar.zst", "{}-{}-{}-{}.pkg.tar.zst",
self.name, self.name,
@ -217,10 +161,11 @@ impl Package {
versions.first().unwrap().clone() versions.first().unwrap().clone()
}, },
1, 1,
self.arch, self.arch.to_string(),
) )
} }
/// Get all versions of the package
pub fn versions(&self) -> Vec<String> { pub fn versions(&self) -> Vec<String> {
let dir_path = self.base_path(); let dir_path = self.base_path();
let mut versions = vec![]; let mut versions = vec![];
@ -229,8 +174,7 @@ impl Package {
for entry in entries.filter_map(Result::ok) { for entry in entries.filter_map(Result::ok) {
let file_name = entry.file_name().into_string().unwrap_or_default(); let file_name = entry.file_name().into_string().unwrap_or_default();
if file_name.starts_with(&self.name) && file_name.ends_with(".pkg.tar.zst") { if file_name.starts_with(&self.name) && file_name.ends_with(".pkg.tar.zst") {
// Extract version (assuming the filename is "<pkg_name>-<version>-<relation>-<arch>.pkg.tar.zst") if let Some(version) = Package::from_filename(&self.repo, &file_name).version {
if let Some(version) = file_name.split('-').nth(1) {
versions.push(version.to_string()); versions.push(version.to_string());
} }
} }
@ -242,6 +186,7 @@ impl Package {
versions versions
} }
/// Get the content of the `.pkg.tar.zst`
pub fn pkg_content(&self) -> Option<Vec<u8>> { pub fn pkg_content(&self) -> Option<Vec<u8>> {
if self.exists() { if self.exists() {
return std::fs::read(self.base_path().join(self.file_name())).ok(); return std::fs::read(self.base_path().join(self.file_name())).ok();
@ -250,6 +195,7 @@ impl Package {
None None
} }
/// Get the content of the `.pkg.tar.zst.sig`
pub fn sig_content(&self) -> Option<Vec<u8>> { pub fn sig_content(&self) -> Option<Vec<u8>> {
if self.exists() { if self.exists() {
return std::fs::read(self.base_path().join(format!("{}.sig", &self.file_name()))).ok(); return std::fs::read(self.base_path().join(format!("{}.sig", &self.file_name()))).ok();
@ -267,3 +213,8 @@ pub fn run_command(cmd: Vec<&str>) {
.wait() .wait()
.unwrap(); .unwrap();
} }
/// Add a package file to a repo db
pub fn repo_add(db_file: &str, pkg_file: &str) {
run_command(vec!["repo-add", db_file, pkg_file]);
}

111
src/pkg/repo.rs Normal file
View file

@ -0,0 +1,111 @@
use std::path::PathBuf;
use super::{Package, arch::Architecture};
/// Package Repository
pub struct Repository {
pub name: String,
}
impl Repository {
/// Get a list of all package repositories
pub fn list() -> Vec<String> {
let mut repos = vec![];
for entry in std::fs::read_dir("./data").unwrap() {
let path = entry.unwrap().path();
let file_name = path.file_name().unwrap().to_str().unwrap().to_string();
repos.push(file_name);
}
repos
}
/// Create a new package repository with architectures from `arch`
pub fn create(name: &str, arch: Vec<Architecture>) -> Repository {
let path = PathBuf::from("./data").join(name);
std::fs::create_dir_all(&path).unwrap();
for arch in arch {
let np = path.join(arch.to_string());
std::fs::create_dir_all(np).unwrap();
}
Repository::new(name).unwrap()
}
}
impl Repository {
/// Get a repository if it exists
pub fn new(name: &str) -> Option<Self> {
if PathBuf::from("./data").join(name).exists() {
Some(Repository {
name: name.to_string(),
})
} else {
None
}
}
/// Get a list of architectures for this repository
pub fn arch(&self) -> Vec<Architecture> {
let dir_path = PathBuf::from("./data").join(&self.name);
let mut arch = vec![];
if let Ok(entries) = std::fs::read_dir(dir_path) {
for entry in entries.filter_map(Result::ok) {
let file_name = entry.file_name().into_string().unwrap_or_default();
if let Some(repo_arch) = Architecture::parse(&file_name) {
arch.push(repo_arch);
}
}
}
arch
}
/// Get the base path for the repository with `arch`.
pub fn base_path(&self, arch: Architecture) -> PathBuf {
PathBuf::from("./data")
.join(&self.name)
.join(&arch.to_string())
}
/// Get the `.db.tar.gz` content for the repository of `arch`
pub fn db_content(&self, arch: Architecture) -> Option<Vec<u8>> {
std::fs::read(
self.base_path(arch)
.join(format!("{}.db.tar.gz", self.name)),
)
.ok()
}
/// Get the `.db.tar.gz.sig` content for the repository of `arch`
pub fn sig_content(&self, arch: Architecture) -> Option<Vec<u8>> {
std::fs::read(
self.base_path(arch)
.join(format!("{}.db.tar.gz.sig", self.name)),
)
.ok()
}
pub fn get_pkg(&self, arch: Architecture, pkg_name: &str) -> Option<Package> {
// Normalize name
let pkg_name = if pkg_name.ends_with(".sig") {
pkg_name.trim_end_matches(".sig").to_string()
} else {
pkg_name.to_string()
};
// Find package
let (name, version, _, _) = Package::extract_pkg_name(&pkg_name).unwrap();
let pkg = Package::new(&self.name, arch, &name, &version);
// Return if exists
if pkg.exists() {
return Some(pkg);
} else {
None
}
}
}

View file

@ -5,6 +5,7 @@ use rocket::tokio::io::AsyncReadExt;
use rocket::{FromForm, get, post}; use rocket::{FromForm, get, post};
use serde_json::json; use serde_json::json;
use crate::pkg::arch::Architecture;
use crate::pkg::{Package, Repository}; use crate::pkg::{Package, Repository};
// /pkg/<repo>/<arch>/<pkg_name> // /pkg/<repo>/<arch>/<pkg_name>
@ -39,11 +40,17 @@ pub async fn upload_pkg(
upload: Form<PkgUpload<'_>>, upload: Form<PkgUpload<'_>>,
user: based::auth::APIUser, user: based::auth::APIUser,
) -> FallibleApiResponse { ) -> FallibleApiResponse {
// TODO : Permission System
if !user.0.is_admin() { if !user.0.is_admin() {
return Err(api_error("Forbidden")); return Err(api_error("Forbidden"));
} }
let pkg = Package::new(repo, &upload.arch, &upload.name, &upload.version); let pkg = Package::new(
repo,
Architecture::parse(&upload.arch).ok_or_else(|| api_error("Invalid architecture"))?,
&upload.name,
&upload.version,
);
pkg.save( pkg.save(
tmp_file_to_vec(&upload.pkg).await, tmp_file_to_vec(&upload.pkg).await,
@ -54,11 +61,13 @@ pub async fn upload_pkg(
}, },
); );
Ok(json!({"ok": 1})) Ok(json!({"ok": format!("Added '{}' to '{}'", pkg.file_name(), repo)}))
} }
#[get("/pkg/<repo>/<arch>/<pkg_name>")] #[get("/pkg/<repo>/<arch>/<pkg_name>")]
pub async fn pkg_route(repo: &str, arch: &str, pkg_name: &str, ctx: RequestContext) -> RawResponse { pub async fn pkg_route(repo: &str, arch: &str, pkg_name: &str, ctx: RequestContext) -> RawResponse {
let arch = Architecture::parse(arch).unwrap();
if let Some(repo) = Repository::new(repo) { if let Some(repo) = Repository::new(repo) {
if pkg_name.ends_with("db.tar.gz") if pkg_name.ends_with("db.tar.gz")
|| pkg_name.ends_with("db") || pkg_name.ends_with("db")