From ffe952fee16f5faa0f4ca23400acd4456ebf736c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 17 Jul 2024 08:47:41 +0100 Subject: [PATCH] WIP: Support reading the server configuration from a git repo #31 --- crates/cli/src/main.rs | 28 ++++++- crates/config/src/api_token.rs | 2 +- crates/config/src/lib.rs | 2 + .../config/src/remote_repo_server_config.rs | 18 +++++ crates/server-actor/src/handlers/mod.rs | 1 + .../server-actor/src/handlers/watch_file.rs | 21 +++++ crates/server-actor/src/messages.rs | 7 +- crates/server/Cargo.toml | 3 + crates/server/src/lib.rs | 79 +++++++++++++++++-- 9 files changed, 148 insertions(+), 13 deletions(-) create mode 100644 crates/config/src/remote_repo_server_config.rs create mode 100644 crates/server-actor/src/handlers/watch_file.rs diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 6741850..9314303 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -6,6 +6,7 @@ mod tests; use std::path::PathBuf; use clap::Parser; +use git_next_server::{BranchName, Secret}; use kxio::{fs, network::Network}; #[derive(Parser, Debug)] @@ -23,7 +24,12 @@ enum Command { #[derive(Parser, Debug)] enum Server { Init, - Start, + Start { + repo_url: Option, + api_key: Option, + branch_name: Option, + config_file: Option, + }, } fn main() { @@ -40,9 +46,25 @@ fn main() { Server::Init => { git_next_server::init(fs); } - Server::Start => { + Server::Start { + repo_url, + api_key, + branch_name, + config_file, + } => { let sleep_duration = std::time::Duration::from_secs(10); - git_next_server::start(fs, net, repository_factory, sleep_duration); + git_next_server::start( + fs, + net, + repository_factory, + sleep_duration, + ( + repo_url, + api_key.map(Secret::new), + branch_name.map(BranchName::new), + config_file, + ), + ); } }, } diff --git a/crates/config/src/api_token.rs b/crates/config/src/api_token.rs index 42dc049..3bfa5d5 100644 --- a/crates/config/src/api_token.rs +++ b/crates/config/src/api_token.rs @@ -1,7 +1,7 @@ /// The API Token for the [user] /// ForgeJo: https://{hostname}/user/settings/applications /// Github: https://github.com/settings/tokens -#[derive(Clone, Debug, derive_more::Constructor)] +#[derive(Clone, Debug, derive_more::Constructor, derive_more::From)] pub struct ApiToken(secrecy::Secret); /// The API Token is in effect a password, so it must be explicitly exposed to access its value impl secrecy::ExposeSecret for ApiToken { diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 7f39a32..ab8fc5e 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -10,6 +10,7 @@ pub mod git_dir; mod host_name; mod newtype; mod registered_webhook; +mod remote_repo_server_config; mod remote_url; mod repo_alias; mod repo_branches; @@ -34,6 +35,7 @@ pub use git_dir::GitDir; pub use git_dir::StoragePathType; pub use host_name::Hostname; pub use registered_webhook::RegisteredWebhook; +pub use remote_repo_server_config::RemoteRepoServerConfig; pub use remote_url::RemoteUrl; pub use repo_alias::RepoAlias; pub use repo_branches::RepoBranches; diff --git a/crates/config/src/remote_repo_server_config.rs b/crates/config/src/remote_repo_server_config.rs new file mode 100644 index 0000000..019ec8a --- /dev/null +++ b/crates/config/src/remote_repo_server_config.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; + +use derive_more::Constructor; + +use crate::{BranchName, RemoteUrl}; + +#[derive(Clone, Debug, Constructor, PartialEq, Eq)] +pub struct RemoteRepoServerConfig { + repo_url: RemoteUrl, + api_key: String, + branch_name: BranchName, + config_file: PathBuf, +} +impl RemoteRepoServerConfig { + pub fn api_key(&self) -> secrecy::Secret { + secrecy::Secret::new(self.api_key.clone()) + } +} diff --git a/crates/server-actor/src/handlers/mod.rs b/crates/server-actor/src/handlers/mod.rs index c92aeeb..7da7c1a 100644 --- a/crates/server-actor/src/handlers/mod.rs +++ b/crates/server-actor/src/handlers/mod.rs @@ -2,3 +2,4 @@ mod file_updated; mod receive_server_config; mod receive_valid_server_config; mod shutdown; +mod watch_file; diff --git a/crates/server-actor/src/handlers/watch_file.rs b/crates/server-actor/src/handlers/watch_file.rs new file mode 100644 index 0000000..e1ef82b --- /dev/null +++ b/crates/server-actor/src/handlers/watch_file.rs @@ -0,0 +1,21 @@ +//- +use actix::prelude::*; +// use git_next_config::server::ServerConfig; + +// use crate::messages::ReceiveServerConfig; +use crate::{messages::WatchFile, Server}; + +impl Handler for Server { + type Result = (); + + fn handle(&mut self, _msg: WatchFile, _ctx: &mut Self::Context) -> Self::Result { + // let server_config = match ServerConfig::load(&self.fs) { + // Ok(server_config) => server_config, + // Err(err) => { + // tracing::error!("Failed to load config file. Error: {}", err); + // return; + // } + // }; + // self.do_send(ReceiveServerConfig::new(server_config), ctx); + } +} diff --git a/crates/server-actor/src/messages.rs b/crates/server-actor/src/messages.rs index adbf46e..6205cfc 100644 --- a/crates/server-actor/src/messages.rs +++ b/crates/server-actor/src/messages.rs @@ -1,7 +1,10 @@ //- use derive_more::Constructor; use git_next_actor_macros::message; -use git_next_config::server::{ServerConfig, ServerStorage}; +use git_next_config::{ + server::{ServerConfig, ServerStorage}, + RemoteRepoServerConfig, +}; use std::net::SocketAddr; // receive server config @@ -21,3 +24,5 @@ pub struct ValidServerConfig { message!(ReceiveValidServerConfig: ValidServerConfig: "Notification of validated server configuration."); message!(Shutdown: "Notification to shutdown the server actor"); + +message!(WatchFile: RemoteRepoServerConfig: "Start monitoring the repo for changes to the config file."); diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index a6115b9..263e24d 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -23,6 +23,9 @@ kxio = { workspace = true } actix = { workspace = true } actix-rt = { workspace = true } +# Security +secrecy = { workspace = true } + [dev-dependencies] # Testing assert2 = { workspace = true } diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index 2506ea4..0b125ee 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -1,12 +1,16 @@ // use actix::prelude::*; +use git_next_config::{RemoteRepoServerConfig, RemoteUrl}; use git_next_file_watcher_actor::{FileUpdated, FileWatcher}; use git_next_server_actor::Server; use kxio::{fs::FileSystem, network::Network}; +use secrecy::ExposeSecret; use std::path::PathBuf; use tracing::{error, info, level_filters::LevelFilter}; +pub use git_next_config::BranchName; pub use git_next_server_actor::{repository_factory, RepositoryFactory}; +pub use secrecy::Secret; pub fn init(fs: FileSystem) { let file_name = "git-next-server.toml"; @@ -35,23 +39,55 @@ pub fn start( net: Network, repo: Box, sleep_duration: std::time::Duration, + (repo_url, api_key, branch_name, config_file): ( + Option, + Option>, + Option, + Option, + ), ) { init_logging(); + let Ok(remote_config) = parse_remote_repo_config(repo_url, api_key, branch_name, config_file) + else { + tracing::error!("Failed to parse repo config for server settings."); + return; + }; + info!("Starting Server..."); let execution = async move { let server = Server::new(fs.clone(), net.clone(), repo, sleep_duration).start(); server.do_send(FileUpdated); - info!("Starting File Watcher..."); - let fw = match FileWatcher::new("git-next-server.toml".into(), server.clone().recipient()) { - Ok(fw) => fw, - Err(err) => { - error!(?err, "Failed to start file watcher"); - return; + match remote_config { + None => { + info!("Starting File Watcher..."); + let fw = match FileWatcher::new( + "git-next-server.toml".into(), + server.clone().recipient(), + ) { + Ok(fw) => fw, + Err(err) => { + error!(?err, "Failed to start file watcher"); + return; + } + }; + fw.start(); } - }; - fw.start(); + Some(_remote_repo_server_config) => { + info!("Starting Repo Watcher..."); + // TODO: start private repo actor and send watch file message + // let rw = + // match RepoWatcher::new(remote_repo_server_config, server.clone().recipient()) { + // Ok(rw) => rw, + // Err(err) => { + // error!(?err, "Failed to start repo watcher"); + // return; + // } + // }; + // rw.start(); + } + } info!("Server running - Press Ctrl-C to stop..."); let _ = actix_rt::signal::ctrl_c().await; @@ -85,3 +121,30 @@ pub fn init_logging() { .with(subscriber) .init(); } + +fn parse_remote_repo_config( + repo_url: Option, + api_key: Option>, + branch_name: Option, + config_file: Option, +) -> Result, ()> { + match (repo_url, api_key, branch_name, config_file) { + (Some(repo_url), Some(api_key), Some(branch_name), Some(config_file)) => { + let Some(url) = RemoteUrl::parse(repo_url) else { + tracing::error!("--repo-url is invalid"); + return Err(()); + }; + Ok(Some(RemoteRepoServerConfig::new( + url, + api_key.expose_secret().clone(), + branch_name, + config_file, + ))) + } + (None, None, None, None) => Ok(None), + (_, _, _, _) => { + tracing::error!("Server config requires all or none of 'repo-url', 'api-key', 'branch-name' and 'config-file', not all were provided"); + Err(()) + } + } +}