Compare commits

..

12 commits

Author SHA1 Message Date
1a932bcde8 refactor: rename forge as forge_config
Some checks failed
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline failed
ci/woodpecker/push/tag-created Pipeline was successful
2024-04-23 07:41:39 +01:00
17f5f62e61 feat: Clone repo when starting repo
Some checks failed
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline failed
2024-04-23 07:41:39 +01:00
fc1c79620c refactor: rename name to forge_name for consistency
All checks were successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
2024-04-23 06:53:34 +01:00
8b56a185d3 refactor: rename name to repo_alias for consistency
All checks were successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
2024-04-23 06:50:52 +01:00
32d54cfc1c refactor: rename config variables,etc as repo_config
All checks were successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
2024-04-21 19:36:37 +01:00
ee8b1e9fce fix(config): deserialise gitdir as PathBuf, but expose it as GitDir
All checks were successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
2024-04-21 19:33:42 +01:00
b71aece8c9 feat: add gitdir to RepoDetails type
All checks were successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
2024-04-21 18:50:30 +01:00
a024c3de5e feat: use GitDir in place of raw PathBuf
All checks were successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
2024-04-21 18:47:07 +01:00
56e253b545 feat(config): add GitDir type
All checks were successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
2024-04-21 18:38:47 +01:00
e10561f853 refactor(server/config): move tests into their own file
All checks were successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
2024-04-21 18:24:14 +01:00
18143c17fd refactor: use 'repo_path' use consistently
All checks were successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
2024-04-20 21:03:17 +01:00
1170510c44 refactor: use 'server_repo_config' consistently
All checks were successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
2024-04-20 21:03:12 +01:00
17 changed files with 308 additions and 232 deletions

View file

@ -1,6 +1,9 @@
[webhook] [webhook]
url = "https://localhost:8080/webhook" url = "https://localhost:8080/webhook"
[storage]
path = "./data"
[forge.default] [forge.default]
forge_type = "ForgeJo" forge_type = "ForgeJo"
hostname = "git.example.net" hostname = "git.example.net"

View file

@ -5,13 +5,14 @@ use crate::server::{config::RepoDetails, gitforge};
use super::{LoadedConfig, RepoActor}; use super::{LoadedConfig, RepoActor};
/// Loads the [RepoConfig] from the `.git-next.toml` file in the repository
pub async fn load(repo_details: RepoDetails, addr: Addr<RepoActor>, forge: gitforge::Forge) { pub async fn load(repo_details: RepoDetails, addr: Addr<RepoActor>, forge: gitforge::Forge) {
let config = match crate::server::config::load::load(&repo_details, &forge).await { let repo_config = match crate::server::config::load::load(&repo_details, &forge).await {
Ok(config) => config, Ok(repo_config) => repo_config,
Err(err) => { Err(err) => {
error!(?err, "Failed to load config"); error!(?err, "Failed to load config");
return; return;
} }
}; };
addr.do_send(LoadedConfig(config)); addr.do_send(LoadedConfig(repo_config));
} }

View file

@ -68,11 +68,26 @@ impl Actor for RepoActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct StartRepo; pub struct CloneRepo;
impl Handler<StartRepo> for RepoActor { impl Handler<CloneRepo> for RepoActor {
type Result = (); type Result = ();
fn handle(&mut self, _msg: StartRepo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result {
info!(%self.details, "Starting Repo"); info!(%self.details, "Clone/Update Repo");
let gitdir = self.details.gitdir.clone();
match self.forge.repo_clone(gitdir) {
Ok(_) => ctx.address().do_send(LoadConfigFromRepo),
Err(err) => warn!(?err, "Could not Clone repo"),
}
}
}
#[derive(Message)]
#[rtype(result = "()")]
pub struct LoadConfigFromRepo;
impl Handler<LoadConfigFromRepo> for RepoActor {
type Result = ();
fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result {
info!(%self.details, "Loading .git-next.toml from repo");
let details = self.details.clone(); let details = self.details.clone();
let addr = ctx.address(); let addr = ctx.address();
let forge = self.forge.clone(); let forge = self.forge.clone();
@ -88,9 +103,9 @@ struct LoadedConfig(pub RepoConfig);
impl Handler<LoadedConfig> for RepoActor { impl Handler<LoadedConfig> for RepoActor {
type Result = (); type Result = ();
fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result {
let config = msg.0; let repo_config = msg.0;
info!(%self.details, %config, "Config loaded"); info!(%self.details, %repo_config, "Config loaded");
self.details.config.replace(config); self.details.repo_config.replace(repo_config);
if self.webhook_id.is_none() { if self.webhook_id.is_none() {
webhook::register( webhook::register(
self.details.clone(), self.details.clone(),
@ -111,7 +126,7 @@ pub struct ValidateRepo;
impl Handler<ValidateRepo> for RepoActor { impl Handler<ValidateRepo> for RepoActor {
type Result = (); type Result = ();
fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result {
if let Some(repo_config) = self.details.config.clone() { if let Some(repo_config) = self.details.repo_config.clone() {
let forge = self.forge.clone(); let forge = self.forge.clone();
let addr = ctx.address(); let addr = ctx.address();
async move { forge.branches_validate_positions(repo_config, addr).await } async move { forge.branches_validate_positions(repo_config, addr).await }
@ -132,7 +147,7 @@ pub struct StartMonitoring {
impl Handler<StartMonitoring> for RepoActor { impl Handler<StartMonitoring> for RepoActor {
type Result = (); type Result = ();
fn handle(&mut self, msg: StartMonitoring, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: StartMonitoring, ctx: &mut Self::Context) -> Self::Result {
let Some(repo_config) = self.details.config.clone() else { let Some(repo_config) = self.details.repo_config.clone() else {
warn!("No config loaded"); warn!("No config loaded");
return; return;
}; };
@ -180,7 +195,7 @@ pub struct AdvanceMainTo(pub gitforge::Commit);
impl Handler<AdvanceMainTo> for RepoActor { impl Handler<AdvanceMainTo> for RepoActor {
type Result = (); type Result = ();
fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result {
let Some(repo_config) = self.details.config.clone() else { let Some(repo_config) = self.details.repo_config.clone() else {
warn!("No config loaded"); warn!("No config loaded");
return; return;
}; };

View file

@ -64,11 +64,11 @@ pub async fn unregister(
) { ) {
info!(?webhook_id, "unregister webhook"); info!(?webhook_id, "unregister webhook");
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = repo_details.repo; let repo_path = repo_details.repo_path;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let token = repo_details.forge.token.expose_secret(); let token = repo_details.forge.token.expose_secret();
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/hooks/{webhook_id}?token={token}" "https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
)); ));
let request = network::NetRequest::new( let request = network::NetRequest::new(
network::RequestMethod::Delete, network::RequestMethod::Delete,
@ -92,7 +92,7 @@ pub async fn register(
addr: actix::prelude::Addr<super::RepoActor>, addr: actix::prelude::Addr<super::RepoActor>,
net: network::Network, net: network::Network,
) { ) {
let Some(repo_config) = repo_details.config.clone() else { let Some(repo_config) = repo_details.repo_config.clone() else {
return; return;
}; };
@ -105,13 +105,13 @@ pub async fn register(
info!("Registering webhook"); info!("Registering webhook");
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = repo_details.repo; let repo_path = repo_details.repo_path;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let token = repo_details.forge.token.expose_secret(); let token = repo_details.forge.token.expose_secret();
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/hooks?token={token}" "https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
)); ));
let repo_alias = &repo_details.name; let repo_alias = &repo_details.repo_alias;
let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json"); let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json");
let authorisation = WebhookAuth::generate(); let authorisation = WebhookAuth::generate();
let body = json!({ let body = json!({
@ -153,12 +153,13 @@ async fn find_existing_webhooks(
) -> Vec<WebhookId> { ) -> Vec<WebhookId> {
let mut ids: Vec<WebhookId> = vec![]; let mut ids: Vec<WebhookId> = vec![];
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let repo_path = &repo_details.repo_path;
let mut page = 1; let mut page = 1;
loop { loop {
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let token = &repo_details.forge.token.expose_secret(); let token = &repo_details.forge.token.expose_secret();
let url = format!("https://{hostname}/api/v1/repos/{path}/hooks?page={page}&token={token}"); let url =
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?page={page}&token={token}");
let net_url = network::NetUrl::new(url); let net_url = network::NetUrl::new(url);
let request = network::NetRequest::new( let request = network::NetRequest::new(
network::RequestMethod::Get, network::RequestMethod::Get,
@ -213,7 +214,7 @@ impl Handler<WebhookMessage> for RepoActor {
match serde_json::from_str::<Push>(body) { match serde_json::from_str::<Push>(body) {
Err(err) => debug!(?err, %body, "Not a 'push'"), Err(err) => debug!(?err, %body, "Not a 'push'"),
Ok(push) => { Ok(push) => {
if let Some(config) = &self.details.config { if let Some(config) = &self.details.repo_config {
match push.branch(config.branches()) { match push.branch(config.branches()) {
None => warn!( None => warn!(
?push, ?push,

View file

@ -31,6 +31,10 @@ impl ServerConfig {
.map(|(name, forge)| (ForgeName(name.clone()), forge)) .map(|(name, forge)| (ForgeName(name.clone()), forge))
} }
pub const fn storage(&self) -> &ServerStorage {
&self.storage
}
pub const fn webhook(&self) -> &Webhook { pub const fn webhook(&self) -> &Webhook {
&self.webhook &self.webhook
} }
@ -259,7 +263,7 @@ impl secrecy::ExposeSecret<String> for ApiToken {
/// The derived information about a Forge, used to create interactions with it /// The derived information about a Forge, used to create interactions with it
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ForgeDetails { pub struct ForgeDetails {
pub name: ForgeName, pub forge_name: ForgeName,
pub forge_type: ForgeType, pub forge_type: ForgeType,
pub hostname: Hostname, pub hostname: Hostname,
pub user: User, pub user: User,
@ -270,7 +274,7 @@ pub struct ForgeDetails {
impl From<(&ForgeName, &ForgeConfig)> for ForgeDetails { impl From<(&ForgeName, &ForgeConfig)> for ForgeDetails {
fn from(forge: (&ForgeName, &ForgeConfig)) -> Self { fn from(forge: (&ForgeName, &ForgeConfig)) -> Self {
Self { Self {
name: forge.0.clone(), forge_name: forge.0.clone(),
forge_type: forge.1.forge_type.clone(), forge_type: forge.1.forge_type.clone(),
hostname: forge.1.hostname(), hostname: forge.1.hostname(),
user: forge.1.user(), user: forge.1.user(),
@ -319,30 +323,38 @@ impl Deref for BranchName {
/// The derived information about a repo, used to interact with it /// The derived information about a repo, used to interact with it
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RepoDetails { pub struct RepoDetails {
pub name: RepoAlias, pub repo_alias: RepoAlias,
pub repo: RepoPath, pub repo_path: RepoPath,
pub branch: BranchName, pub branch: BranchName,
pub forge: ForgeDetails, pub forge: ForgeDetails,
pub config: Option<RepoConfig>, pub repo_config: Option<RepoConfig>,
pub gitdir: GitDir,
} }
impl RepoDetails { impl RepoDetails {
pub fn new( pub fn new(
name: &RepoAlias, name: &RepoAlias,
repo: &ServerRepoConfig, server_repo_config: &ServerRepoConfig,
forge_name: &ForgeName, forge_name: &ForgeName,
forge: &ForgeConfig, forge_config: &ForgeConfig,
server_storage: &ServerStorage,
) -> Self { ) -> Self {
Self { Self {
name: name.clone(), repo_alias: name.clone(),
repo: RepoPath(repo.repo.clone()), repo_path: RepoPath(server_repo_config.repo.clone()),
config: repo.repo_config(), repo_config: server_repo_config.repo_config(),
branch: BranchName(repo.branch.clone()), branch: BranchName(server_repo_config.branch.clone()),
gitdir: GitDir(
server_storage
.path
.join(forge_name.to_string())
.join(name.to_string()),
),
forge: ForgeDetails { forge: ForgeDetails {
name: forge_name.clone(), forge_name: forge_name.clone(),
forge_type: forge.forge_type.clone(), forge_type: forge_config.forge_type.clone(),
hostname: forge.hostname(), hostname: forge_config.hostname(),
user: forge.user(), user: forge_config.user(),
token: forge.token(), token: forge_config.token(),
}, },
} }
} }
@ -350,11 +362,11 @@ impl RepoDetails {
let repo_details = self; let repo_details = self;
let user = &repo_details.forge.user; let user = &repo_details.forge.user;
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let repo_path = &repo_details.repo_path;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let expose_secret = &repo_details.forge.token; let expose_secret = &repo_details.forge.token;
let token = expose_secret.expose_secret(); let token = expose_secret.expose_secret();
let origin = format!("https://{user}:{token}@{hostname}/{path}.git"); let origin = format!("https://{user}:{token}@{hostname}/{repo_path}.git");
origin.into() origin.into()
} }
} }
@ -363,12 +375,12 @@ impl Display for RepoDetails {
write!( write!(
f, f,
"{}/{} ({}): {}:{}/{} @ {}", "{}/{} ({}): {}:{}/{} @ {}",
self.forge.name, self.forge.forge_name,
self.name, self.repo_alias,
self.forge.forge_type, self.forge.forge_type,
self.forge.hostname, self.forge.hostname,
self.forge.user, self.forge.user,
self.repo, self.repo_path,
self.branch, self.branch,
) )
} }
@ -392,142 +404,28 @@ impl Display for ForgeType {
} }
} }
#[cfg(feature = "forgejo")] #[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GitDir(PathBuf);
impl GitDir {
#[cfg(test)] #[cfg(test)]
mod tests { pub(crate) fn new(pathbuf: &std::path::Path) -> Self {
Self(pathbuf.to_path_buf())
use pretty_assertions::assert_eq; }
#[allow(dead_code)] // TODO:
use crate::filesystem::FileSystem; pub const fn pathbuf(&self) -> &PathBuf {
&self.0
use super::*; }
}
#[test] impl std::fmt::Display for GitDir {
fn load_should_parse_server_config() -> Result<(), OneOf<(std::io::Error, toml::de::Error)>> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let fs = FileSystem::new_temp().map_err(OneOf::new)?; write!(f, "{self:?}")
fs.write_file( }
"git-next-server.toml", }
r#" impl From<&str> for GitDir {
[webhook] fn from(value: &str) -> Self {
url = "http://localhost:9909/webhook" Self(value.into())
[storage]
path = "/opt/git-next/data"
[forge.default]
forge_type = "MockForge"
hostname = "git.example.net"
user = "Bob"
token = "API-Token"
[forge.default.repos]
hello = { repo = "user/hello", branch = "main", gitdir = "/opt/git/user/hello.git" }
world = { repo = "user/world", branch = "master", main = "main", next = "next", dev = "dev" }
[forge.default.repos.sam]
repo = "user/sam"
branch = "main"
main = "master"
next = "upcoming"
dev = "sam-dev"
"#,
)
.map_err(OneOf::new)?;
let config = ServerConfig::load(&fs)?;
let expected = ServerConfig {
webhook: Webhook {
url: "http://localhost:9909/webhook".to_string(),
},
storage: ServerStorage {
path: "/opt/git-next/data".into(),
},
forge: HashMap::from([(
"default".to_string(),
ForgeConfig {
forge_type: ForgeType::MockForge,
hostname: "git.example.net".to_string(),
user: "Bob".to_string(),
token: "API-Token".to_string(),
repos: HashMap::from([
(
"hello".to_string(),
ServerRepoConfig {
repo: "user/hello".to_string(),
branch: "main".to_string(),
gitdir: Some("/opt/git/user/hello.git".into()),
main: None,
next: None,
dev: None,
},
),
(
"world".to_string(),
ServerRepoConfig {
repo: "user/world".to_string(),
branch: "master".to_string(),
gitdir: None,
main: Some("main".to_string()),
next: Some("next".to_string()),
dev: Some("dev".to_string()),
},
),
(
"sam".to_string(),
ServerRepoConfig {
repo: "user/sam".to_string(),
branch: "main".to_string(),
gitdir: None,
main: Some("master".to_string()),
next: Some("upcoming".to_string()),
dev: Some("sam-dev".to_string()),
},
),
]),
},
)]),
};
assert_eq!(config, expected, "ServerConfig");
if let Some(forge) = config.forge.get("world") {
if let Some(repo) = forge.repos.get("sam") {
let repo_config = repo.repo_config();
let expected = Some(RepoConfig {
branches: RepoBranches {
main: "master".to_string(),
next: "upcoming".to_string(),
dev: "sam-dev".to_string(),
},
});
assert_eq!(repo_config, expected, "RepoConfig");
} }
} }
Ok(()) #[cfg(test)]
} mod tests;
#[test]
fn test_repo_config_load() -> Result<(), OneOf<(toml::de::Error,)>> {
let toml = r#"
[branches]
main = "main"
next = "next"
dev = "dev"
[options]
"#;
let config = RepoConfig::load(toml)?;
assert_eq!(
config,
RepoConfig {
branches: RepoBranches {
main: "main".to_string(),
next: "next".to_string(),
dev: "dev".to_string(),
},
}
);
Ok(())
}
}

134
src/server/config/tests.rs Normal file
View file

@ -0,0 +1,134 @@
use pretty_assertions::assert_eq;
use crate::filesystem::FileSystem;
use super::*;
#[test]
fn load_should_parse_server_config() -> Result<(), OneOf<(std::io::Error, toml::de::Error)>> {
let fs = FileSystem::new_temp().map_err(OneOf::new)?;
fs.write_file(
"git-next-server.toml",
r#"
[webhook]
url = "http://localhost:9909/webhook"
[storage]
path = "/opt/git-next/data"
[forge.default]
forge_type = "MockForge"
hostname = "git.example.net"
user = "Bob"
token = "API-Token"
[forge.default.repos]
hello = { repo = "user/hello", branch = "main", gitdir = "/opt/git/user/hello.git" }
world = { repo = "user/world", branch = "master", main = "main", next = "next", dev = "dev" }
[forge.default.repos.sam]
repo = "user/sam"
branch = "main"
main = "master"
next = "upcoming"
dev = "sam-dev"
"#,
)
.map_err(OneOf::new)?;
let config = ServerConfig::load(&fs)?;
let expected = ServerConfig {
webhook: Webhook {
url: "http://localhost:9909/webhook".to_string(),
},
storage: ServerStorage {
path: "/opt/git-next/data".into(),
},
forge: HashMap::from([(
"default".to_string(),
ForgeConfig {
forge_type: ForgeType::MockForge,
hostname: "git.example.net".to_string(),
user: "Bob".to_string(),
token: "API-Token".to_string(),
repos: HashMap::from([
(
"hello".to_string(),
ServerRepoConfig {
repo: "user/hello".to_string(),
branch: "main".to_string(),
gitdir: Some("/opt/git/user/hello.git".into()),
main: None,
next: None,
dev: None,
},
),
(
"world".to_string(),
ServerRepoConfig {
repo: "user/world".to_string(),
branch: "master".to_string(),
gitdir: None,
main: Some("main".to_string()),
next: Some("next".to_string()),
dev: Some("dev".to_string()),
},
),
(
"sam".to_string(),
ServerRepoConfig {
repo: "user/sam".to_string(),
branch: "main".to_string(),
gitdir: None,
main: Some("master".to_string()),
next: Some("upcoming".to_string()),
dev: Some("sam-dev".to_string()),
},
),
]),
},
)]),
};
assert_eq!(config, expected, "ServerConfig");
if let Some(forge) = config.forge.get("world") {
if let Some(repo) = forge.repos.get("sam") {
let repo_config = repo.repo_config();
let expected = Some(RepoConfig {
branches: RepoBranches {
main: "master".to_string(),
next: "upcoming".to_string(),
dev: "sam-dev".to_string(),
},
});
assert_eq!(repo_config, expected, "RepoConfig");
}
}
Ok(())
}
#[test]
fn test_repo_config_load() -> Result<(), OneOf<(toml::de::Error,)>> {
let toml = r#"
[branches]
main = "main"
next = "next"
dev = "dev"
[options]
"#;
let config = RepoConfig::load(toml)?;
assert_eq!(
config,
RepoConfig {
branches: RepoBranches {
main: "main".to_string(),
next: "next".to_string(),
dev: "dev".to_string(),
},
}
);
Ok(())
}

View file

@ -1,4 +1,4 @@
use crate::server::config::BranchName; use crate::server::config::{BranchName, GitDir};
#[derive(Debug)] #[derive(Debug)]
pub enum ForgeFileError { pub enum ForgeFileError {
@ -42,7 +42,7 @@ impl std::fmt::Display for ForgeBranchError {
#[derive(Debug)] #[derive(Debug)]
pub enum RepoCloneError { pub enum RepoCloneError {
InvalidGitDir(std::path::PathBuf), InvalidGitDir(GitDir),
Wait(std::io::Error), Wait(std::io::Error),
Spawn(std::io::Error), Spawn(std::io::Error),
} }

View file

@ -11,11 +11,11 @@ pub async fn get_all(
net: &Network, net: &Network,
) -> Result<Vec<gitforge::Branch>, ForgeBranchError> { ) -> Result<Vec<gitforge::Branch>, ForgeBranchError> {
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let repo_path = &repo_details.repo_path;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let token = repo_details.forge.token.expose_secret(); let token = repo_details.forge.token.expose_secret();
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/branches?token={token}" "https://{hostname}/api/v1/repos/{repo_path}/branches?token={token}"
)); ));
info!(%url, "Listing branches"); info!(%url, "Listing branches");

View file

@ -160,7 +160,7 @@ async fn get_commit_history(
net: &kxio::network::Network, net: &kxio::network::Network,
) -> Result<Vec<gitforge::Commit>, network::NetworkError> { ) -> Result<Vec<gitforge::Commit>, network::NetworkError> {
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let repo_path = &repo_details.repo_path;
let mut page = 1; let mut page = 1;
let limit = match find_commits.is_empty() { let limit = match find_commits.is_empty() {
@ -174,7 +174,7 @@ async fn get_commit_history(
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let token = api_token.expose_secret(); let token = api_token.expose_secret();
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/commits?sha={branch_name}&{options}&token={token}&page={page}&limit={limit}" "https://{hostname}/api/v1/repos/{repo_path}/commits?sha={branch_name}&{options}&token={token}&page={page}&limit={limit}"
)); ));
let request = network::NetRequest::new( let request = network::NetRequest::new(

View file

@ -13,12 +13,12 @@ pub(super) async fn contents_get(
file_path: &str, file_path: &str,
) -> Result<String, ForgeFileError> { ) -> Result<String, ForgeFileError> {
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let repo_path = &repo_details.repo_path;
let api_token = &repo_details.forge.token; let api_token = &repo_details.forge.token;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let token = api_token.expose_secret(); let token = api_token.expose_secret();
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/contents/{file_path}?ref={branch}&token={token}" "https://{hostname}/api/v1/repos/{repo_path}/contents/{file_path}?ref={branch}&token={token}"
)); ));
info!(%url, "Loading config"); info!(%url, "Loading config");

View file

@ -2,8 +2,6 @@ mod branch;
mod file; mod file;
mod repo; mod repo;
use std::path::PathBuf;
use actix::prelude::*; use actix::prelude::*;
use kxio::network::{self, Network}; use kxio::network::{self, Network};
@ -11,7 +9,7 @@ use tracing::{error, warn};
use crate::server::{ use crate::server::{
actors::repo::RepoActor, actors::repo::RepoActor,
config::{BranchName, RepoConfig, RepoDetails}, config::{BranchName, GitDir, RepoConfig, RepoDetails},
gitforge::{self, RepoCloneError}, gitforge::{self, RepoCloneError},
types::GitRef, types::GitRef,
}; };
@ -61,12 +59,12 @@ impl super::ForgeLike for ForgeJoEnv {
async fn commit_status(&self, commit: &gitforge::Commit) -> gitforge::CommitStatus { async fn commit_status(&self, commit: &gitforge::Commit) -> gitforge::CommitStatus {
let repo_details = &self.repo_details; let repo_details = &self.repo_details;
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let repo_path = &repo_details.repo_path;
let api_token = &repo_details.forge.token; let api_token = &repo_details.forge.token;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let token = api_token.expose_secret(); let token = api_token.expose_secret();
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/commits/{commit}/status?token={token}" "https://{hostname}/api/v1/repos/{repo_path}/commits/{commit}/status?token={token}"
)); ));
let request = network::NetRequest::new( let request = network::NetRequest::new(
@ -102,7 +100,7 @@ impl super::ForgeLike for ForgeJoEnv {
} }
} }
fn repo_clone(&self, gitdir: PathBuf) -> Result<(), RepoCloneError> { fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError> {
repo::clone(&self.repo_details, gitdir) repo::clone(&self.repo_details, gitdir)
} }
} }

View file

@ -1,13 +1,11 @@
use std::path::PathBuf;
use tracing::{info, warn}; use tracing::{info, warn};
use crate::server::{config::RepoDetails, gitforge::RepoCloneError}; use crate::server::{
config::{GitDir, RepoDetails},
pub fn clone(repo_details: &RepoDetails, gitdir: PathBuf) -> Result<(), RepoCloneError> { gitforge::RepoCloneError,
let Some(gitdir) = gitdir.to_str() else {
return Err(RepoCloneError::InvalidGitDir(gitdir));
}; };
pub fn clone(repo_details: &RepoDetails, gitdir: GitDir) -> Result<(), RepoCloneError> {
let origin = repo_details.origin(); let origin = repo_details.origin();
// INFO: never log the command as it contains the API token within the 'origin' // INFO: never log the command as it contains the API token within the 'origin'
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
@ -17,7 +15,7 @@ pub fn clone(repo_details: &RepoDetails, gitdir: PathBuf) -> Result<(), RepoClon
gitdir gitdir
) )
.into(); .into();
let repo_name = &repo_details.name; let repo_name = &repo_details.repo_alias;
info!("Cloning {repo_name} to {gitdir}"); info!("Cloning {repo_name} to {gitdir}");
match gix::command::prepare(command.expose_secret()) match gix::command::prepare(command.expose_secret())
.with_shell_allow_argument_splitting() .with_shell_allow_argument_splitting()

View file

@ -1,8 +1,6 @@
use std::path::PathBuf;
use crate::server::{ use crate::server::{
actors::repo::RepoActor, actors::repo::RepoActor,
config::{BranchName, RepoConfig}, config::{BranchName, GitDir, RepoConfig},
gitforge::{self, RepoCloneError}, gitforge::{self, RepoCloneError},
types::GitRef, types::GitRef,
}; };
@ -54,7 +52,7 @@ impl super::ForgeLike for MockForgeEnv {
todo!() todo!()
} }
fn repo_clone(&self, _gitdir: PathBuf) -> Result<(), RepoCloneError> { fn repo_clone(&self, _gitdir: GitDir) -> Result<(), RepoCloneError> {
todo!() todo!()
} }
} }

View file

@ -1,7 +1,5 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::path::PathBuf;
use kxio::network::Network; use kxio::network::Network;
#[cfg(feature = "forgejo")] #[cfg(feature = "forgejo")]
@ -19,7 +17,7 @@ mod errors;
pub use errors::*; pub use errors::*;
use crate::server::{ use crate::server::{
config::{BranchName, RepoConfig, RepoDetails}, config::{BranchName, GitDir, RepoConfig, RepoDetails},
types::GitRef, types::GitRef,
}; };
@ -57,7 +55,7 @@ pub trait ForgeLike {
async fn commit_status(&self, commit: &Commit) -> CommitStatus; async fn commit_status(&self, commit: &Commit) -> CommitStatus;
/// Clones a repo to disk. /// Clones a repo to disk.
fn repo_clone(&self, gitdir: PathBuf) -> Result<(), RepoCloneError>; fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError>;
} }
#[derive(Clone)] #[derive(Clone)]

View file

@ -1,11 +1,11 @@
use crate::server::config::{ use crate::server::config::{
ApiToken, BranchName, ForgeDetails, ForgeName, ForgeType, Hostname, RepoAlias, RepoBranches, ApiToken, BranchName, ForgeDetails, ForgeName, ForgeType, GitDir, Hostname, RepoAlias,
RepoConfig, RepoDetails, RepoPath, User, RepoBranches, RepoConfig, RepoDetails, RepoPath, User,
}; };
pub fn forge_details(n: u32, forge_type: ForgeType) -> ForgeDetails { pub fn forge_details(n: u32, forge_type: ForgeType) -> ForgeDetails {
ForgeDetails { ForgeDetails {
name: forge_name(n), forge_name: forge_name(n),
forge_type, forge_type,
hostname: hostname(n), hostname: hostname(n),
user: user(n), user: user(n),
@ -28,13 +28,19 @@ pub fn hostname(n: u32) -> Hostname {
pub fn forge_name(n: u32) -> ForgeName { pub fn forge_name(n: u32) -> ForgeName {
ForgeName(format!("forge-name-{}", n)) ForgeName(format!("forge-name-{}", n))
} }
pub fn repo_details(n: u32, forge: ForgeDetails, config: Option<RepoConfig>) -> RepoDetails { pub fn repo_details(
n: u32,
forge: ForgeDetails,
repo_config: Option<RepoConfig>,
gitdir: GitDir,
) -> RepoDetails {
RepoDetails { RepoDetails {
name: repo_alias(n), repo_alias: repo_alias(n),
repo: repo_path(n), repo_path: repo_path(n),
gitdir,
branch: branch_name(n), branch: branch_name(n),
forge, forge,
config, repo_config,
} }
} }
@ -50,7 +56,7 @@ pub fn repo_alias(n: u32) -> RepoAlias {
RepoAlias(format!("repo-alias-{}", n)) RepoAlias(format!("repo-alias-{}", n))
} }
pub fn config(n: u32) -> RepoConfig { pub fn repo_config(n: u32) -> RepoConfig {
RepoConfig::new(RepoBranches::new( RepoConfig::new(RepoBranches::new(
format!("main-{n}"), format!("main-{n}"),
format!("next-{n}"), format!("next-{n}"),

View file

@ -8,11 +8,15 @@ use super::*;
#[test] #[test]
fn test_name() { fn test_name() {
let Ok(fs) = kxio::filesystem::FileSystem::new_temp() else {
panic!("fs")
};
let net = Network::new_mock(); let net = Network::new_mock();
let repo_details = common::repo_details( let repo_details = common::repo_details(
1, 1,
common::forge_details(1, ForgeType::MockForge), common::forge_details(1, ForgeType::MockForge),
Some(common::config(1)), Some(common::repo_config(1)),
GitDir::new(fs.cwd()),
); );
let forge = Forge::new_forgejo(repo_details, net); let forge = Forge::new_forgejo(repo_details, net);
assert_eq!(forge.name(), "forgejo"); assert_eq!(forge.name(), "forgejo");
@ -20,6 +24,9 @@ fn test_name() {
#[test_log::test(tokio::test)] #[test_log::test(tokio::test)]
async fn test_branches_get() { async fn test_branches_get() {
let Ok(fs) = kxio::filesystem::FileSystem::new_temp() else {
panic!("fs")
};
let mut net = MockNetwork::new(); let mut net = MockNetwork::new();
let hostname = common::hostname(1); let hostname = common::hostname(1);
let path = common::repo_path(1); let path = common::repo_path(1);
@ -34,7 +41,8 @@ async fn test_branches_get() {
let repo_details = common::repo_details( let repo_details = common::repo_details(
1, 1,
common::forge_details(1, ForgeType::MockForge), common::forge_details(1, ForgeType::MockForge),
Some(common::config(1)), Some(common::repo_config(1)),
GitDir::new(fs.cwd()),
); );
let forge = Forge::new_forgejo(repo_details, net.clone()); let forge = Forge::new_forgejo(repo_details, net.clone());

View file

@ -15,7 +15,7 @@ use crate::{
filesystem::FileSystem, filesystem::FileSystem,
server::{ server::{
actors::webhook, actors::webhook,
config::{ForgeConfig, ForgeName, RepoAlias, Webhook}, config::{ForgeConfig, ForgeName, RepoAlias, ServerStorage, Webhook},
}, },
}; };
@ -55,9 +55,13 @@ pub async fn start(fs: FileSystem, net: Network) {
let webhook_router = webhook::WebhookRouter::new().start(); let webhook_router = webhook::WebhookRouter::new().start();
let webhook = server_config.webhook(); let webhook = server_config.webhook();
let server_storage = server_config.storage();
server_config server_config
.forges() .forges()
.flat_map(|(forge_name, forge)| create_forge_repos(forge, forge_name, webhook, &net)) .flat_map(|(forge_name, forge_config)| {
create_forge_repos(forge_config, forge_name, server_storage, webhook, &net)
})
.map(start_actor) .map(start_actor)
.map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient())) .map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient()))
.for_each(|msg| webhook_router.do_send(msg)); .for_each(|msg| webhook_router.do_send(msg));
@ -68,35 +72,49 @@ pub async fn start(fs: FileSystem, net: Network) {
} }
fn create_forge_repos( fn create_forge_repos(
forge: &ForgeConfig, forge_config: &ForgeConfig,
forge_name: ForgeName, forge_name: ForgeName,
server_storage: &ServerStorage,
webhook: &Webhook, webhook: &Webhook,
net: &Network, net: &Network,
) -> Vec<(ForgeName, RepoAlias, RepoActor)> { ) -> Vec<(ForgeName, RepoAlias, RepoActor)> {
let forge = forge.clone(); let span = tracing::info_span!("Forge", %forge_name, %forge_config);
let span = tracing::info_span!("Forge", %forge_name, %forge);
let _guard = span.enter(); let _guard = span.enter();
info!("Creating Forge"); info!("Creating Forge");
forge forge_config
.repos() .repos()
.map(create_actor(forge_name, forge.clone(), webhook, net)) .map(create_actor(
forge_name,
forge_config.clone(),
server_storage,
webhook,
net,
))
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
fn create_actor( fn create_actor(
forge_name: ForgeName, forge_name: ForgeName, // TODO: check we need this, is it not in forge_config?
forge: config::ForgeConfig, forge_config: config::ForgeConfig, // TODO: rename forge as forge_config
server_storage: &ServerStorage,
webhook: &Webhook, webhook: &Webhook,
net: &Network, net: &Network,
) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> (ForgeName, RepoAlias, RepoActor) { ) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> (ForgeName, RepoAlias, RepoActor) {
let server_storage = server_storage.clone();
let webhook = webhook.clone(); let webhook = webhook.clone();
let net = net.clone(); let net = net.clone();
move |(repo_name, repo)| { move |(repo_name, server_repo_config)| {
let span = tracing::info_span!("Repo", %repo_name, %repo); let span = tracing::info_span!("Repo", %repo_name, %server_repo_config);
let _guard = span.enter(); let _guard = span.enter();
info!("Creating Repo"); info!("Creating Repo");
let actor = actors::repo::RepoActor::new( let actor = actors::repo::RepoActor::new(
config::RepoDetails::new(&repo_name, repo, &forge_name, &forge), config::RepoDetails::new(
&repo_name,
server_repo_config,
&forge_name,
&forge_config,
&server_storage,
),
webhook.clone(), webhook.clone(),
net.clone(), net.clone(),
); );
@ -113,7 +131,7 @@ fn start_actor(
let _guard = span.enter(); let _guard = span.enter();
info!("Starting"); info!("Starting");
let addr = actor.start(); let addr = actor.start();
addr.do_send(actors::repo::StartRepo); addr.do_send(actors::repo::CloneRepo);
info!("Started"); info!("Started");
(repo_alias, addr) (repo_alias, addr)
} }