Style: Automatische Formatierung & Clippy-Fixes
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 18s
Security Scans / Trivy & OSV-Scanner (push) Successful in 30s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m5s
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 18s
Security Scans / Trivy & OSV-Scanner (push) Successful in 30s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m5s
This commit is contained in:
+15
-4
@@ -59,7 +59,11 @@ impl AppConfig {
|
|||||||
/// Fügt ein Repository zur Konfiguration hinzu oder aktualisiert ein bestehendes.
|
/// Fügt ein Repository zur Konfiguration hinzu oder aktualisiert ein bestehendes.
|
||||||
pub fn add_or_update_repo(&mut self, mut repo: RepoConfig) {
|
pub fn add_or_update_repo(&mut self, mut repo: RepoConfig) {
|
||||||
repo.name = Self::normalize_repo_name(&repo.name);
|
repo.name = Self::normalize_repo_name(&repo.name);
|
||||||
if let Some(existing) = self.repositories.iter_mut().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&repo.name)) {
|
if let Some(existing) = self
|
||||||
|
.repositories
|
||||||
|
.iter_mut()
|
||||||
|
.find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&repo.name))
|
||||||
|
{
|
||||||
existing.name = repo.name;
|
existing.name = repo.name;
|
||||||
existing.include_prereleases = repo.include_prereleases;
|
existing.include_prereleases = repo.include_prereleases;
|
||||||
} else {
|
} else {
|
||||||
@@ -71,20 +75,27 @@ impl AppConfig {
|
|||||||
pub fn remove_repo(&mut self, repo_name: &str) -> bool {
|
pub fn remove_repo(&mut self, repo_name: &str) -> bool {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
let normalized = Self::normalize_repo_name(repo_name);
|
||||||
let before_len = self.repositories.len();
|
let before_len = self.repositories.len();
|
||||||
self.repositories.retain(|r| !Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized));
|
self.repositories
|
||||||
|
.retain(|r| !Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized));
|
||||||
self.repositories.len() < before_len
|
self.repositories.len() < before_len
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sucht ein Repository anhand des Namens.
|
/// Sucht ein Repository anhand des Namens.
|
||||||
pub fn find_repo(&self, repo_name: &str) -> Option<&RepoConfig> {
|
pub fn find_repo(&self, repo_name: &str) -> Option<&RepoConfig> {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
let normalized = Self::normalize_repo_name(repo_name);
|
||||||
self.repositories.iter().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized))
|
self.repositories
|
||||||
|
.iter()
|
||||||
|
.find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aktualisiert den zuletzt synchronisierten Tag für ein bestimmtes Repository.
|
/// Aktualisiert den zuletzt synchronisierten Tag für ein bestimmtes Repository.
|
||||||
pub fn update_last_synced_tag(&mut self, repo_name: &str, tag: String) {
|
pub fn update_last_synced_tag(&mut self, repo_name: &str, tag: String) {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
let normalized = Self::normalize_repo_name(repo_name);
|
||||||
if let Some(repo) = self.repositories.iter_mut().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)) {
|
if let Some(repo) = self
|
||||||
|
.repositories
|
||||||
|
.iter_mut()
|
||||||
|
.find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized))
|
||||||
|
{
|
||||||
repo.last_synced_tag = Some(tag);
|
repo.last_synced_tag = Some(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-7
@@ -1,5 +1,5 @@
|
|||||||
use crate::github::PackageType;
|
use crate::github::PackageType;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
|
|
||||||
@@ -12,7 +12,11 @@ pub struct GiteaConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GiteaConfig {
|
impl GiteaConfig {
|
||||||
pub fn new(base_url: impl Into<String>, token: impl Into<String>, owner: impl Into<String>) -> Self {
|
pub fn new(
|
||||||
|
base_url: impl Into<String>,
|
||||||
|
token: impl Into<String>,
|
||||||
|
owner: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
let mut base_url = base_url.into();
|
let mut base_url = base_url.into();
|
||||||
while base_url.ends_with('/') {
|
while base_url.ends_with('/') {
|
||||||
base_url.pop();
|
base_url.pop();
|
||||||
@@ -44,17 +48,29 @@ pub fn get_target_upload_urls(
|
|||||||
match pkg_type {
|
match pkg_type {
|
||||||
PackageType::Debian => {
|
PackageType::Debian => {
|
||||||
if prerelease {
|
if prerelease {
|
||||||
vec![format!("{}/api/packages/{}/debian/pool/testing/main/upload", base, owner)]
|
vec![format!(
|
||||||
|
"{}/api/packages/{}/debian/pool/testing/main/upload",
|
||||||
|
base, owner
|
||||||
|
)]
|
||||||
} else {
|
} else {
|
||||||
vec![
|
vec![
|
||||||
format!("{}/api/packages/{}/debian/pool/stable/main/upload", base, owner),
|
format!(
|
||||||
format!("{}/api/packages/{}/debian/pool/testing/main/upload", base, owner),
|
"{}/api/packages/{}/debian/pool/stable/main/upload",
|
||||||
|
base, owner
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"{}/api/packages/{}/debian/pool/testing/main/upload",
|
||||||
|
base, owner
|
||||||
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PackageType::Rpm => {
|
PackageType::Rpm => {
|
||||||
if prerelease {
|
if prerelease {
|
||||||
vec![format!("{}/api/packages/{}/rpm/testing/upload", base, owner)]
|
vec![format!(
|
||||||
|
"{}/api/packages/{}/rpm/testing/upload",
|
||||||
|
base, owner
|
||||||
|
)]
|
||||||
} else {
|
} else {
|
||||||
vec![format!("{}/api/packages/{}/rpm/stable/upload", base, owner)]
|
vec![format!("{}/api/packages/{}/rpm/stable/upload", base, owner)]
|
||||||
}
|
}
|
||||||
@@ -82,7 +98,10 @@ impl GiteaClient {
|
|||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
reqwest::header::USER_AGENT,
|
reqwest::header::USER_AGENT,
|
||||||
reqwest::header::HeaderValue::from_static(concat!("mirror-package/", env!("CARGO_PKG_VERSION"))),
|
reqwest::header::HeaderValue::from_static(concat!(
|
||||||
|
"mirror-package/",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
|
|||||||
+14
-7
@@ -91,7 +91,10 @@ impl GitHubClient {
|
|||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
reqwest::header::USER_AGENT,
|
reqwest::header::USER_AGENT,
|
||||||
reqwest::header::HeaderValue::from_static(concat!("mirror-package/", env!("CARGO_PKG_VERSION"))),
|
reqwest::header::HeaderValue::from_static(concat!(
|
||||||
|
"mirror-package/",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
reqwest::header::ACCEPT,
|
reqwest::header::ACCEPT,
|
||||||
@@ -111,7 +114,6 @@ impl GitHubClient {
|
|||||||
self.token.as_deref()
|
self.token.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Ruft Releases für ein angegebenes Repository ab (Format "owner/repo").
|
/// Ruft Releases für ein angegebenes Repository ab (Format "owner/repo").
|
||||||
///
|
///
|
||||||
/// Wenn `history` false ist, werden nur die neuesten Releases abgerufen.
|
/// Wenn `history` false ist, werden nur die neuesten Releases abgerufen.
|
||||||
@@ -139,7 +141,10 @@ impl GitHubClient {
|
|||||||
req = req.bearer_auth(token);
|
req = req.bearer_auth(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = req.send().await.with_context(|| format!("Failed to send request to {}", url))?;
|
let resp = req
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to send request to {}", url))?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
@@ -153,10 +158,12 @@ impl GitHubClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw_releases: Vec<GhApiRelease> = resp
|
let raw_releases: Vec<GhApiRelease> = resp.json().await.with_context(|| {
|
||||||
.json()
|
format!(
|
||||||
.await
|
"Failed to parse GitHub releases JSON for {}/{}",
|
||||||
.with_context(|| format!("Failed to parse GitHub releases JSON for {}/{}", owner, name))?;
|
owner, name
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if raw_releases.is_empty() {
|
if raw_releases.is_empty() {
|
||||||
break;
|
break;
|
||||||
|
|||||||
+59
-15
@@ -1,11 +1,11 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use logger_ctdra::{log, set_log_level, LogLevel};
|
use logger_ctdra::{LogLevel, log, set_log_level};
|
||||||
use mirror_package::cli::{Cli, Commands, ConfigAction};
|
use mirror_package::cli::{Cli, Commands, ConfigAction};
|
||||||
use mirror_package::config::{
|
use mirror_package::config::{
|
||||||
get_config_file_path, init_config_path, load_config, modify_config, AppConfig, RepoConfig,
|
AppConfig, RepoConfig, get_config_file_path, init_config_path, load_config, modify_config,
|
||||||
};
|
};
|
||||||
use mirror_package::pipeline::{sync_single_repository, SyncOptions, SyncReport};
|
use mirror_package::pipeline::{SyncOptions, SyncReport, sync_single_repository};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
@@ -65,7 +65,9 @@ async fn main() -> Result<()> {
|
|||||||
let mut report = SyncReport::default();
|
let mut report = SyncReport::default();
|
||||||
|
|
||||||
for repo in repos_to_sync {
|
for repo in repos_to_sync {
|
||||||
if let Err(e) = sync_single_repository(&repo, &app_config, &options, &mut report).await {
|
if let Err(e) =
|
||||||
|
sync_single_repository(&repo, &app_config, &options, &mut report).await
|
||||||
|
{
|
||||||
log(
|
log(
|
||||||
"sync",
|
"sync",
|
||||||
&format!("Failed to sync repository '{}': {:#}", repo.name, e),
|
&format!("Failed to sync repository '{}': {:#}", repo.name, e),
|
||||||
@@ -128,23 +130,38 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
log(
|
log(
|
||||||
"config",
|
"config",
|
||||||
&format!("Repository '{}' removed from configuration.", normalized_name),
|
&format!(
|
||||||
|
"Repository '{}' removed from configuration.",
|
||||||
|
normalized_name
|
||||||
|
),
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::List => {
|
Commands::List => {
|
||||||
println!("Configured repositories (Config file: {:?}):", get_config_file_path());
|
println!(
|
||||||
|
"Configured repositories (Config file: {:?}):",
|
||||||
|
get_config_file_path()
|
||||||
|
);
|
||||||
if app_config.repositories.is_empty() {
|
if app_config.repositories.is_empty() {
|
||||||
println!(" (None configured yet. Use 'mirror-package add <owner/repo>' to add one.)");
|
println!(
|
||||||
|
" (None configured yet. Use 'mirror-package add <owner/repo>' to add one.)"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("{:<40} {:<15} {:<20}", "REPOSITORY", "PRE-RELEASES", "LAST SYNCED TAG");
|
println!(
|
||||||
|
"{:<40} {:<15} {:<20}",
|
||||||
|
"REPOSITORY", "PRE-RELEASES", "LAST SYNCED TAG"
|
||||||
|
);
|
||||||
println!("{:-<40} {:-<15} {:-<20}", "", "", "");
|
println!("{:-<40} {:-<15} {:-<20}", "", "", "");
|
||||||
for repo in &app_config.repositories {
|
for repo in &app_config.repositories {
|
||||||
println!(
|
println!(
|
||||||
"{:<40} {:<15} {:<20}",
|
"{:<40} {:<15} {:<20}",
|
||||||
repo.name,
|
repo.name,
|
||||||
if repo.include_prereleases { "yes" } else { "no" },
|
if repo.include_prereleases {
|
||||||
|
"yes"
|
||||||
|
} else {
|
||||||
|
"no"
|
||||||
|
},
|
||||||
repo.last_synced_tag.as_deref().unwrap_or("-")
|
repo.last_synced_tag.as_deref().unwrap_or("-")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -154,17 +171,40 @@ async fn main() -> Result<()> {
|
|||||||
Commands::Config(config_args) => match config_args.action {
|
Commands::Config(config_args) => match config_args.action {
|
||||||
None | Some(ConfigAction::Show) => {
|
None | Some(ConfigAction::Show) => {
|
||||||
println!("Configuration location: {:?}", get_config_file_path());
|
println!("Configuration location: {:?}", get_config_file_path());
|
||||||
println!("Gitea URL: {}", app_config.gitea_url.as_deref().unwrap_or("(not configured)"));
|
println!(
|
||||||
println!("Registry Owner: {}", app_config.registry_owner.as_deref().unwrap_or("(not configured)"));
|
"Gitea URL: {}",
|
||||||
|
app_config
|
||||||
|
.gitea_url
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("(not configured)")
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"Registry Owner: {}",
|
||||||
|
app_config
|
||||||
|
.registry_owner
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("(not configured)")
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
"Gitea Token: {}",
|
"Gitea Token: {}",
|
||||||
if app_config.gitea_token.is_some() { "******** (set)" } else { "(not configured)" }
|
if app_config.gitea_token.is_some() {
|
||||||
|
"******** (set)"
|
||||||
|
} else {
|
||||||
|
"(not configured)"
|
||||||
|
}
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"GitHub Token: {}",
|
"GitHub Token: {}",
|
||||||
if app_config.github_token.is_some() { "******** (set)" } else { "(not configured, public access only)" }
|
if app_config.github_token.is_some() {
|
||||||
|
"******** (set)"
|
||||||
|
} else {
|
||||||
|
"(not configured, public access only)"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"Repositories: {} configured",
|
||||||
|
app_config.repositories.len()
|
||||||
);
|
);
|
||||||
println!("Repositories: {} configured", app_config.repositories.len());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ConfigAction::Set {
|
Some(ConfigAction::Set {
|
||||||
@@ -188,7 +228,11 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
log("config", "Configuration successfully updated.", LogLevel::Info);
|
log(
|
||||||
|
"config",
|
||||||
|
"Configuration successfully updated.",
|
||||||
|
LogLevel::Info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-22
@@ -1,11 +1,11 @@
|
|||||||
use crate::config::{modify_config, AppConfig, RepoConfig};
|
use crate::config::{AppConfig, RepoConfig, modify_config};
|
||||||
use crate::gitea::{get_target_upload_urls, GiteaClient, GiteaConfig, UploadStatus};
|
use crate::gitea::{GiteaClient, GiteaConfig, UploadStatus, get_target_upload_urls};
|
||||||
use crate::github::GitHubClient;
|
use crate::github::GitHubClient;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use logger_ctdra::{log, LogLevel};
|
use logger_ctdra::{LogLevel, log};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tokio::fs::{remove_file, File};
|
use tokio::fs::{File, remove_file};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
/// Zusammenfassende Statistiken eines Synchronisationsvorgangs.
|
/// Zusammenfassende Statistiken eines Synchronisationsvorgangs.
|
||||||
@@ -52,7 +52,9 @@ pub async fn sync_single_repository(
|
|||||||
let gitea_token = app_config.gitea_token.as_deref().unwrap_or_default();
|
let gitea_token = app_config.gitea_token.as_deref().unwrap_or_default();
|
||||||
let registry_owner = app_config.registry_owner.as_deref().unwrap_or_default();
|
let registry_owner = app_config.registry_owner.as_deref().unwrap_or_default();
|
||||||
|
|
||||||
if !options.dry_run && (gitea_url.is_empty() || gitea_token.is_empty() || registry_owner.is_empty()) {
|
if !options.dry_run
|
||||||
|
&& (gitea_url.is_empty() || gitea_token.is_empty() || registry_owner.is_empty())
|
||||||
|
{
|
||||||
bail!(
|
bail!(
|
||||||
"Missing Gitea configuration. Ensure --gitea-url, --gitea-token, and --registry-owner are provided or configured."
|
"Missing Gitea configuration. Ensure --gitea-url, --gitea-token, and --registry-owner are provided or configured."
|
||||||
);
|
);
|
||||||
@@ -61,12 +63,8 @@ pub async fn sync_single_repository(
|
|||||||
let github_client = GitHubClient::new(app_config.github_token.clone())
|
let github_client = GitHubClient::new(app_config.github_token.clone())
|
||||||
.context("Failed to initialize GitHub client")?;
|
.context("Failed to initialize GitHub client")?;
|
||||||
|
|
||||||
let gitea_client = GiteaClient::new(GiteaConfig::new(
|
let gitea_client = GiteaClient::new(GiteaConfig::new(gitea_url, gitea_token, registry_owner))
|
||||||
gitea_url,
|
.context("Failed to initialize Gitea client")?;
|
||||||
gitea_token,
|
|
||||||
registry_owner,
|
|
||||||
))
|
|
||||||
.context("Failed to initialize Gitea client")?;
|
|
||||||
|
|
||||||
let releases = github_client
|
let releases = github_client
|
||||||
.fetch_releases(repo_name, options.history, include_prereleases)
|
.fetch_releases(repo_name, options.history, include_prereleases)
|
||||||
@@ -97,7 +95,11 @@ pub async fn sync_single_repository(
|
|||||||
|
|
||||||
for release in &releases {
|
for release in &releases {
|
||||||
report.releases_processed += 1;
|
report.releases_processed += 1;
|
||||||
let release_type_str = if release.prerelease { "pre-release" } else { "stable release" };
|
let release_type_str = if release.prerelease {
|
||||||
|
"pre-release"
|
||||||
|
} else {
|
||||||
|
"stable release"
|
||||||
|
};
|
||||||
|
|
||||||
log(
|
log(
|
||||||
"sync",
|
"sync",
|
||||||
@@ -156,9 +158,10 @@ pub async fn sync_single_repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Asset in temporäre Datei herunterladen
|
// Asset in temporäre Datei herunterladen
|
||||||
let temp_file_path = download_to_temp_file(&github_client, &asset.download_url, &asset.name)
|
let temp_file_path =
|
||||||
.await
|
download_to_temp_file(&github_client, &asset.download_url, &asset.name)
|
||||||
.with_context(|| format!("Failed to download asset '{}'", asset.name))?;
|
.await
|
||||||
|
.with_context(|| format!("Failed to download asset '{}'", asset.name))?;
|
||||||
|
|
||||||
// Asset auf alle Ziel-Distributions-URLs hochladen
|
// Asset auf alle Ziel-Distributions-URLs hochladen
|
||||||
for url in &target_urls {
|
for url in &target_urls {
|
||||||
@@ -168,7 +171,10 @@ pub async fn sync_single_repository(
|
|||||||
LogLevel::Debug,
|
LogLevel::Debug,
|
||||||
);
|
);
|
||||||
|
|
||||||
match gitea_client.upload_file(&temp_file_path, url, options.dry_run).await {
|
match gitea_client
|
||||||
|
.upload_file(&temp_file_path, url, options.dry_run)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(UploadStatus::Uploaded) => {
|
Ok(UploadStatus::Uploaded) => {
|
||||||
log(
|
log(
|
||||||
"upload",
|
"upload",
|
||||||
@@ -220,14 +226,13 @@ pub async fn sync_single_repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Zuletzt synchronisierten Tag persistieren, wenn kein Dry-Run
|
// Zuletzt synchronisierten Tag persistieren, wenn kein Dry-Run
|
||||||
if !options.dry_run {
|
if !options.dry_run
|
||||||
if let Some(tag) = latest_synced_tag {
|
&& let Some(tag) = latest_synced_tag {
|
||||||
let name_copy = repo_name.to_string();
|
let name_copy = repo_name.to_string();
|
||||||
let _ = modify_config(move |cfg| {
|
let _ = modify_config(move |cfg| {
|
||||||
cfg.update_last_synced_tag(&name_copy, tag);
|
cfg.update_last_synced_tag(&name_copy, tag);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
report.repositories_processed += 1;
|
report.repositories_processed += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -254,7 +259,10 @@ async fn download_to_temp_file(
|
|||||||
|
|
||||||
log(
|
log(
|
||||||
"download",
|
"download",
|
||||||
&format!("Downloading '{}' to temporary path {:?}...", filename, temp_path),
|
&format!(
|
||||||
|
"Downloading '{}' to temporary path {:?}...",
|
||||||
|
filename, temp_path
|
||||||
|
),
|
||||||
LogLevel::Debug,
|
LogLevel::Debug,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -263,7 +271,11 @@ async fn download_to_temp_file(
|
|||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to create temp file {:?}", temp_path))?;
|
.with_context(|| format!("Failed to create temp file {:?}", temp_path))?;
|
||||||
|
|
||||||
while let Some(chunk) = response.chunk().await.context("Error reading download stream")? {
|
while let Some(chunk) = response
|
||||||
|
.chunk()
|
||||||
|
.await
|
||||||
|
.context("Error reading download stream")?
|
||||||
|
{
|
||||||
file.write_all(&chunk)
|
file.write_all(&chunk)
|
||||||
.await
|
.await
|
||||||
.context("Error writing chunk to temp file")?;
|
.context("Error writing chunk to temp file")?;
|
||||||
|
|||||||
+9
-9
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/// Sanitizes a string by removing surrounding quotes (single or double).
|
/// Sanitizes a string by removing surrounding quotes (single or double).
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -12,7 +11,10 @@ pub fn sanitize_string(input: &str) -> String {
|
|||||||
let trimmed = input.trim();
|
let trimmed = input.trim();
|
||||||
if let Some(stripped) = trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
|
if let Some(stripped) = trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
|
||||||
stripped.to_string()
|
stripped.to_string()
|
||||||
} else if let Some(stripped) = trimmed.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
|
} else if let Some(stripped) = trimmed
|
||||||
|
.strip_prefix('\'')
|
||||||
|
.and_then(|s| s.strip_suffix('\''))
|
||||||
|
{
|
||||||
stripped.to_string()
|
stripped.to_string()
|
||||||
} else {
|
} else {
|
||||||
trimmed.to_string()
|
trimmed.to_string()
|
||||||
@@ -25,7 +27,7 @@ pub fn sanitize_string(input: &str) -> String {
|
|||||||
///
|
///
|
||||||
/// * `env_vars` - A reference to a mutable map of environment variables.
|
/// * `env_vars` - A reference to a mutable map of environment variables.
|
||||||
pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap<String, String>) {
|
pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap<String, String>) {
|
||||||
for (_key, value) in env_vars.iter_mut() {
|
for value in env_vars.values_mut() {
|
||||||
*value = sanitize_string(value);
|
*value = sanitize_string(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,15 +37,13 @@ pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap<String, String
|
|||||||
/// und umgebende Schrägstriche entfernt werden.
|
/// und umgebende Schrägstriche entfernt werden.
|
||||||
pub fn clean_repo_input(input: &str) -> &str {
|
pub fn clean_repo_input(input: &str) -> &str {
|
||||||
let mut trimmed = input.trim();
|
let mut trimmed = input.trim();
|
||||||
if (trimmed.starts_with('"') && trimmed.ends_with('"'))
|
if ((trimmed.starts_with('"') && trimmed.ends_with('"'))
|
||||||
|| (trimmed.starts_with('\'') && trimmed.ends_with('\''))
|
|| (trimmed.starts_with('\'') && trimmed.ends_with('\'')))
|
||||||
{
|
&& trimmed.len() >= 2 {
|
||||||
if trimmed.len() >= 2 {
|
|
||||||
trimmed = trimmed[1..trimmed.len() - 1].trim();
|
trimmed = trimmed[1..trimmed.len() - 1].trim();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let without_query_or_fragment = match trimmed.find(|c| c == '?' || c == '#') {
|
let without_query_or_fragment = match trimmed.find(['?', '#']) {
|
||||||
Some(idx) => &trimmed[..idx],
|
Some(idx) => &trimmed[..idx],
|
||||||
None => trimmed,
|
None => trimmed,
|
||||||
};
|
};
|
||||||
|
|||||||
+46
-13
@@ -5,14 +5,23 @@ use std::collections::HashMap;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_sanitize_env_vars() {
|
fn test_sanitize_env_vars() {
|
||||||
let mut env_vars = HashMap::new();
|
let mut env_vars = HashMap::new();
|
||||||
env_vars.insert("GITEA_URL".to_string(), "\"https://gitea.example.com\"".to_string());
|
env_vars.insert(
|
||||||
|
"GITEA_URL".to_string(),
|
||||||
|
"\"https://gitea.example.com\"".to_string(),
|
||||||
|
);
|
||||||
env_vars.insert("GITEA_TOKEN".to_string(), "\"token123\"".to_string());
|
env_vars.insert("GITEA_TOKEN".to_string(), "\"token123\"".to_string());
|
||||||
env_vars.insert("REGISTRY_OWNER".to_string(), "\"owner\"".to_string());
|
env_vars.insert("REGISTRY_OWNER".to_string(), "\"owner\"".to_string());
|
||||||
env_vars.insert("GITHUB_TOKEN".to_string(), "\"github_token123\"".to_string());
|
env_vars.insert(
|
||||||
|
"GITHUB_TOKEN".to_string(),
|
||||||
|
"\"github_token123\"".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
sanitize_env_vars(&mut env_vars);
|
sanitize_env_vars(&mut env_vars);
|
||||||
|
|
||||||
assert_eq!(env_vars.get("GITEA_URL").unwrap(), "https://gitea.example.com");
|
assert_eq!(
|
||||||
|
env_vars.get("GITEA_URL").unwrap(),
|
||||||
|
"https://gitea.example.com"
|
||||||
|
);
|
||||||
assert_eq!(env_vars.get("GITEA_TOKEN").unwrap(), "token123");
|
assert_eq!(env_vars.get("GITEA_TOKEN").unwrap(), "token123");
|
||||||
assert_eq!(env_vars.get("REGISTRY_OWNER").unwrap(), "owner");
|
assert_eq!(env_vars.get("REGISTRY_OWNER").unwrap(), "owner");
|
||||||
assert_eq!(env_vars.get("GITHUB_TOKEN").unwrap(), "github_token123");
|
assert_eq!(env_vars.get("GITHUB_TOKEN").unwrap(), "github_token123");
|
||||||
@@ -31,7 +40,10 @@ fn test_load_config_with_env_vars() {
|
|||||||
|
|
||||||
// Only assert if the environment variables are set
|
// Only assert if the environment variables are set
|
||||||
if std::env::var("GITEA_URL").is_ok() {
|
if std::env::var("GITEA_URL").is_ok() {
|
||||||
assert_eq!(config.gitea_url, Some("https://gitea.example.com".to_string()));
|
assert_eq!(
|
||||||
|
config.gitea_url,
|
||||||
|
Some("https://gitea.example.com".to_string())
|
||||||
|
);
|
||||||
assert_eq!(config.gitea_token, Some("token123".to_string()));
|
assert_eq!(config.gitea_token, Some("token123".to_string()));
|
||||||
assert_eq!(config.registry_owner, Some("owner".to_string()));
|
assert_eq!(config.registry_owner, Some("owner".to_string()));
|
||||||
assert_eq!(config.github_token, Some("github_token123".to_string()));
|
assert_eq!(config.github_token, Some("github_token123".to_string()));
|
||||||
@@ -40,8 +52,14 @@ fn test_load_config_with_env_vars() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sanitize_string() {
|
fn test_sanitize_string() {
|
||||||
assert_eq!(sanitize_string("\"https://gitea.example.com\""), "https://gitea.example.com");
|
assert_eq!(
|
||||||
assert_eq!(sanitize_string("'https://gitea.example.com'"), "https://gitea.example.com");
|
sanitize_string("\"https://gitea.example.com\""),
|
||||||
|
"https://gitea.example.com"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sanitize_string("'https://gitea.example.com'"),
|
||||||
|
"https://gitea.example.com"
|
||||||
|
);
|
||||||
assert_eq!(sanitize_string("\"token123\""), "token123");
|
assert_eq!(sanitize_string("\"token123\""), "token123");
|
||||||
assert_eq!(sanitize_string("'token123'"), "token123");
|
assert_eq!(sanitize_string("'token123'"), "token123");
|
||||||
assert_eq!(sanitize_string("\"owner\""), "owner");
|
assert_eq!(sanitize_string("\"owner\""), "owner");
|
||||||
@@ -68,11 +86,23 @@ fn test_sanitize_string() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_clean_repo_input() {
|
fn test_clean_repo_input() {
|
||||||
assert_eq!(clean_repo_input("owner/repo"), "owner/repo");
|
assert_eq!(clean_repo_input("owner/repo"), "owner/repo");
|
||||||
assert_eq!(clean_repo_input("https://github.com/owner/repo"), "owner/repo");
|
assert_eq!(
|
||||||
assert_eq!(clean_repo_input("http://github.com/owner/repo"), "owner/repo");
|
clean_repo_input("https://github.com/owner/repo"),
|
||||||
|
"owner/repo"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clean_repo_input("http://github.com/owner/repo"),
|
||||||
|
"owner/repo"
|
||||||
|
);
|
||||||
assert_eq!(clean_repo_input("github.com/owner/repo"), "owner/repo");
|
assert_eq!(clean_repo_input("github.com/owner/repo"), "owner/repo");
|
||||||
assert_eq!(clean_repo_input("https://github.com/owner/repo.git"), "owner/repo");
|
assert_eq!(
|
||||||
assert_eq!(clean_repo_input("https://github.com/owner/repo/"), "owner/repo");
|
clean_repo_input("https://github.com/owner/repo.git"),
|
||||||
|
"owner/repo"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clean_repo_input("https://github.com/owner/repo/"),
|
||||||
|
"owner/repo"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file"),
|
clean_repo_input("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file"),
|
||||||
"raspberrypi/rpi-imager"
|
"raspberrypi/rpi-imager"
|
||||||
@@ -123,14 +153,14 @@ fn test_add_or_update_repo() {
|
|||||||
config.add_or_update_repo(repo.clone());
|
config.add_or_update_repo(repo.clone());
|
||||||
assert_eq!(config.repositories.len(), 1);
|
assert_eq!(config.repositories.len(), 1);
|
||||||
assert_eq!(config.repositories[0].name, "owner/repo");
|
assert_eq!(config.repositories[0].name, "owner/repo");
|
||||||
assert_eq!(config.repositories[0].include_prereleases, true);
|
assert!(config.repositories[0].include_prereleases);
|
||||||
|
|
||||||
// Updating existing repo with a URL with query param
|
// Updating existing repo with a URL with query param
|
||||||
let repo_updated = RepoConfig::new("https://github.com/owner/repo#readme", false);
|
let repo_updated = RepoConfig::new("https://github.com/owner/repo#readme", false);
|
||||||
config.add_or_update_repo(repo_updated);
|
config.add_or_update_repo(repo_updated);
|
||||||
assert_eq!(config.repositories.len(), 1);
|
assert_eq!(config.repositories.len(), 1);
|
||||||
assert_eq!(config.repositories[0].name, "owner/repo");
|
assert_eq!(config.repositories[0].name, "owner/repo");
|
||||||
assert_eq!(config.repositories[0].include_prereleases, false);
|
assert!(!config.repositories[0].include_prereleases);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -159,5 +189,8 @@ fn test_update_last_synced_tag() {
|
|||||||
let repo = RepoConfig::new("owner/repo", true);
|
let repo = RepoConfig::new("owner/repo", true);
|
||||||
config.add_or_update_repo(repo.clone());
|
config.add_or_update_repo(repo.clone());
|
||||||
config.update_last_synced_tag("owner/repo", "v1.0.0".to_string());
|
config.update_last_synced_tag("owner/repo", "v1.0.0".to_string());
|
||||||
assert_eq!(config.find_repo("owner/repo").unwrap().last_synced_tag, Some("v1.0.0".to_string()));
|
assert_eq!(
|
||||||
|
config.find_repo("owner/repo").unwrap().last_synced_tag,
|
||||||
|
Some("v1.0.0".to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
+20
-8
@@ -41,7 +41,10 @@ fn test_package_classification() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Nicht unterstützte Paketformate sollten ignoriert werden
|
// Nicht unterstützte Paketformate sollten ignoriert werden
|
||||||
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.AppImage"), None);
|
assert_eq!(
|
||||||
|
PackageType::from_filename("rpi-imager-1.8.5.AppImage"),
|
||||||
|
None
|
||||||
|
);
|
||||||
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.dmg"), None);
|
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.dmg"), None);
|
||||||
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.exe"), None);
|
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.exe"), None);
|
||||||
assert_eq!(PackageType::from_filename("source-code.tar.gz"), None);
|
assert_eq!(PackageType::from_filename("source-code.tar.gz"), None);
|
||||||
@@ -57,19 +60,25 @@ fn test_parse_repo_owner_name() {
|
|||||||
("raspberrypi", "rpi-imager")
|
("raspberrypi", "rpi-imager")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher").unwrap(),
|
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher")
|
||||||
|
.unwrap(),
|
||||||
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher.git").unwrap(),
|
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher.git")
|
||||||
|
.unwrap(),
|
||||||
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file").unwrap(),
|
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file")
|
||||||
|
.unwrap(),
|
||||||
("raspberrypi", "rpi-imager")
|
("raspberrypi", "rpi-imager")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file#install").unwrap(),
|
parse_repo_owner_name(
|
||||||
|
"https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file#install"
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
("raspberrypi", "rpi-imager")
|
("raspberrypi", "rpi-imager")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -86,14 +95,17 @@ async fn test_github_client_empty_token() {
|
|||||||
use mirror_package::github::GitHubClient;
|
use mirror_package::github::GitHubClient;
|
||||||
|
|
||||||
// Test that a client with an empty or whitespace token sanitizes it to None
|
// Test that a client with an empty or whitespace token sanitizes it to None
|
||||||
let client = GitHubClient::new(Some("".to_string())).expect("Failed to create client with empty token");
|
let client =
|
||||||
|
GitHubClient::new(Some("".to_string())).expect("Failed to create client with empty token");
|
||||||
assert_eq!(client.token(), None);
|
assert_eq!(client.token(), None);
|
||||||
|
|
||||||
let client_whitespace = GitHubClient::new(Some(" ".to_string())).expect("Failed to create client with whitespace token");
|
let client_whitespace = GitHubClient::new(Some(" ".to_string()))
|
||||||
|
.expect("Failed to create client with whitespace token");
|
||||||
assert_eq!(client_whitespace.token(), None);
|
assert_eq!(client_whitespace.token(), None);
|
||||||
|
|
||||||
// Test with a valid token
|
// Test with a valid token
|
||||||
let client_with_token = GitHubClient::new(Some("valid_token".to_string())).expect("Failed to create client with valid token");
|
let client_with_token = GitHubClient::new(Some("valid_token".to_string()))
|
||||||
|
.expect("Failed to create client with valid token");
|
||||||
assert_eq!(client_with_token.token(), Some("valid_token"));
|
assert_eq!(client_with_token.token(), Some("valid_token"));
|
||||||
|
|
||||||
// Test with no token
|
// Test with no token
|
||||||
|
|||||||
Reference in New Issue
Block a user