WIP: Support reading the server configuration from a git repo #31
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

This commit is contained in:
Paul Campbell 2024-07-17 08:47:41 +01:00
parent ba67b1ebcb
commit ffe952fee1
9 changed files with 148 additions and 13 deletions

View file

@ -6,6 +6,7 @@ mod tests;
use std::path::PathBuf; use std::path::PathBuf;
use clap::Parser; use clap::Parser;
use git_next_server::{BranchName, Secret};
use kxio::{fs, network::Network}; use kxio::{fs, network::Network};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -23,7 +24,12 @@ enum Command {
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
enum Server { enum Server {
Init, Init,
Start, Start {
repo_url: Option<String>,
api_key: Option<String>,
branch_name: Option<String>,
config_file: Option<PathBuf>,
},
} }
fn main() { fn main() {
@ -40,9 +46,25 @@ fn main() {
Server::Init => { Server::Init => {
git_next_server::init(fs); 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); 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,
),
);
} }
}, },
} }

View file

@ -1,7 +1,7 @@
/// The API Token for the [user] /// The API Token for the [user]
/// ForgeJo: https://{hostname}/user/settings/applications /// ForgeJo: https://{hostname}/user/settings/applications
/// Github: https://github.com/settings/tokens /// 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<String>); pub struct ApiToken(secrecy::Secret<String>);
/// The API Token is in effect a password, so it must be explicitly exposed to access its value /// The API Token is in effect a password, so it must be explicitly exposed to access its value
impl secrecy::ExposeSecret<String> for ApiToken { impl secrecy::ExposeSecret<String> for ApiToken {

View file

@ -10,6 +10,7 @@ pub mod git_dir;
mod host_name; mod host_name;
mod newtype; mod newtype;
mod registered_webhook; mod registered_webhook;
mod remote_repo_server_config;
mod remote_url; mod remote_url;
mod repo_alias; mod repo_alias;
mod repo_branches; mod repo_branches;
@ -34,6 +35,7 @@ pub use git_dir::GitDir;
pub use git_dir::StoragePathType; pub use git_dir::StoragePathType;
pub use host_name::Hostname; pub use host_name::Hostname;
pub use registered_webhook::RegisteredWebhook; pub use registered_webhook::RegisteredWebhook;
pub use remote_repo_server_config::RemoteRepoServerConfig;
pub use remote_url::RemoteUrl; pub use remote_url::RemoteUrl;
pub use repo_alias::RepoAlias; pub use repo_alias::RepoAlias;
pub use repo_branches::RepoBranches; pub use repo_branches::RepoBranches;

View file

@ -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<String> {
secrecy::Secret::new(self.api_key.clone())
}
}

View file

@ -2,3 +2,4 @@ mod file_updated;
mod receive_server_config; mod receive_server_config;
mod receive_valid_server_config; mod receive_valid_server_config;
mod shutdown; mod shutdown;
mod watch_file;

View file

@ -0,0 +1,21 @@
//-
use actix::prelude::*;
// use git_next_config::server::ServerConfig;
// use crate::messages::ReceiveServerConfig;
use crate::{messages::WatchFile, Server};
impl Handler<WatchFile> 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);
}
}

View file

@ -1,7 +1,10 @@
//- //-
use derive_more::Constructor; use derive_more::Constructor;
use git_next_actor_macros::message; 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; use std::net::SocketAddr;
// receive server config // receive server config
@ -21,3 +24,5 @@ pub struct ValidServerConfig {
message!(ReceiveValidServerConfig: ValidServerConfig: "Notification of validated server configuration."); message!(ReceiveValidServerConfig: ValidServerConfig: "Notification of validated server configuration.");
message!(Shutdown: "Notification to shutdown the server actor"); message!(Shutdown: "Notification to shutdown the server actor");
message!(WatchFile: RemoteRepoServerConfig: "Start monitoring the repo for changes to the config file.");

View file

@ -23,6 +23,9 @@ kxio = { workspace = true }
actix = { workspace = true } actix = { workspace = true }
actix-rt = { workspace = true } actix-rt = { workspace = true }
# Security
secrecy = { workspace = true }
[dev-dependencies] [dev-dependencies]
# Testing # Testing
assert2 = { workspace = true } assert2 = { workspace = true }

View file

@ -1,12 +1,16 @@
// //
use actix::prelude::*; use actix::prelude::*;
use git_next_config::{RemoteRepoServerConfig, RemoteUrl};
use git_next_file_watcher_actor::{FileUpdated, FileWatcher}; use git_next_file_watcher_actor::{FileUpdated, FileWatcher};
use git_next_server_actor::Server; use git_next_server_actor::Server;
use kxio::{fs::FileSystem, network::Network}; use kxio::{fs::FileSystem, network::Network};
use secrecy::ExposeSecret;
use std::path::PathBuf; use std::path::PathBuf;
use tracing::{error, info, level_filters::LevelFilter}; use tracing::{error, info, level_filters::LevelFilter};
pub use git_next_config::BranchName;
pub use git_next_server_actor::{repository_factory, RepositoryFactory}; pub use git_next_server_actor::{repository_factory, RepositoryFactory};
pub use secrecy::Secret;
pub fn init(fs: FileSystem) { pub fn init(fs: FileSystem) {
let file_name = "git-next-server.toml"; let file_name = "git-next-server.toml";
@ -35,16 +39,33 @@ pub fn start(
net: Network, net: Network,
repo: Box<dyn RepositoryFactory>, repo: Box<dyn RepositoryFactory>,
sleep_duration: std::time::Duration, sleep_duration: std::time::Duration,
(repo_url, api_key, branch_name, config_file): (
Option<String>,
Option<Secret<String>>,
Option<BranchName>,
Option<PathBuf>,
),
) { ) {
init_logging(); 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..."); info!("Starting Server...");
let execution = async move { let execution = async move {
let server = Server::new(fs.clone(), net.clone(), repo, sleep_duration).start(); let server = Server::new(fs.clone(), net.clone(), repo, sleep_duration).start();
server.do_send(FileUpdated); server.do_send(FileUpdated);
match remote_config {
None => {
info!("Starting File Watcher..."); info!("Starting File Watcher...");
let fw = match FileWatcher::new("git-next-server.toml".into(), server.clone().recipient()) { let fw = match FileWatcher::new(
"git-next-server.toml".into(),
server.clone().recipient(),
) {
Ok(fw) => fw, Ok(fw) => fw,
Err(err) => { Err(err) => {
error!(?err, "Failed to start file watcher"); error!(?err, "Failed to start file watcher");
@ -52,6 +73,21 @@ pub fn start(
} }
}; };
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..."); info!("Server running - Press Ctrl-C to stop...");
let _ = actix_rt::signal::ctrl_c().await; let _ = actix_rt::signal::ctrl_c().await;
@ -85,3 +121,30 @@ pub fn init_logging() {
.with(subscriber) .with(subscriber)
.init(); .init();
} }
fn parse_remote_repo_config(
repo_url: Option<String>,
api_key: Option<Secret<String>>,
branch_name: Option<BranchName>,
config_file: Option<PathBuf>,
) -> Result<Option<RemoteRepoServerConfig>, ()> {
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(())
}
}
}