feat(server/forgejo): load repo config
This commit is contained in:
parent
a7a64f5a07
commit
b093c002d4
9 changed files with 212 additions and 36 deletions
|
@ -2,7 +2,7 @@ mod init;
|
|||
mod server;
|
||||
|
||||
use clap::Parser;
|
||||
use kxio::filesystem;
|
||||
use kxio::{filesystem, network::Network};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version = clap::crate_version!(), author = clap::crate_authors!(), about = clap::crate_description!())]
|
||||
|
@ -23,6 +23,7 @@ enum Server {
|
|||
}
|
||||
fn main() {
|
||||
let fs = filesystem::FileSystem::new_real(None);
|
||||
let net = Network::new_real();
|
||||
let commands = Commands::parse();
|
||||
|
||||
match commands.command {
|
||||
|
@ -34,7 +35,7 @@ fn main() {
|
|||
server::init(fs);
|
||||
}
|
||||
Server::Start => {
|
||||
server::start(fs);
|
||||
server::start(fs, net);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
use actix::prelude::*;
|
||||
use tracing::info;
|
||||
|
||||
use crate::server::config::RepoDetails;
|
||||
|
||||
pub struct RepoActor {
|
||||
details: RepoDetails,
|
||||
}
|
||||
impl RepoActor {
|
||||
pub(crate) const fn new(details: RepoDetails) -> Self {
|
||||
Self { details }
|
||||
}
|
||||
}
|
||||
impl Actor for RepoActor {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct StartRepo;
|
||||
impl Handler<StartRepo> for RepoActor {
|
||||
type Result = ();
|
||||
fn handle(&mut self, _msg: StartRepo, _ctx: &mut Self::Context) -> Self::Result {
|
||||
info!(%self.details, "Starting Repo");
|
||||
}
|
||||
}
|
22
src/server/actors/repo/config.rs
Normal file
22
src/server/actors/repo/config.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use actix::prelude::*;
|
||||
use kxio::network::Network;
|
||||
use tracing::error;
|
||||
|
||||
use crate::server::{
|
||||
config::{ForgeType, RepoDetails},
|
||||
forge,
|
||||
};
|
||||
|
||||
use super::{LoadedConfig, RepoActor};
|
||||
|
||||
pub async fn load(details: RepoDetails, addr: Addr<RepoActor>, net: Network) {
|
||||
let config = match details.forge.forge_type {
|
||||
ForgeType::ForgeJo => forge::forgejo::config::load(details, &net).await,
|
||||
};
|
||||
match config {
|
||||
Ok(config) => addr.do_send(LoadedConfig(config)),
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to load config");
|
||||
}
|
||||
}
|
||||
}
|
51
src/server/actors/repo/mod.rs
Normal file
51
src/server/actors/repo/mod.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
mod config;
|
||||
|
||||
use actix::prelude::*;
|
||||
use kxio::network::Network;
|
||||
use tracing::info;
|
||||
|
||||
use crate::server::config::{RepoConfig, RepoDetails};
|
||||
|
||||
pub struct RepoActor {
|
||||
details: RepoDetails,
|
||||
config: Option<RepoConfig>, // INFO: if [None] then send [StartRepo] to populate it
|
||||
net: Network,
|
||||
}
|
||||
impl RepoActor {
|
||||
pub(crate) const fn new(details: RepoDetails, net: Network) -> Self {
|
||||
Self {
|
||||
details,
|
||||
config: None,
|
||||
net,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Actor for RepoActor {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct StartRepo;
|
||||
impl Handler<StartRepo> for RepoActor {
|
||||
type Result = ();
|
||||
fn handle(&mut self, _msg: StartRepo, ctx: &mut Self::Context) -> Self::Result {
|
||||
info!(%self.details, "Starting Repo");
|
||||
let details = self.details.clone();
|
||||
let addr = ctx.address();
|
||||
let net = self.net.clone();
|
||||
config::load(details, addr, net).into_actor(self).wait(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
struct LoadedConfig(pub RepoConfig);
|
||||
impl Handler<LoadedConfig> for RepoActor {
|
||||
type Result = ();
|
||||
fn handle(&mut self, msg: LoadedConfig, _ctx: &mut Self::Context) -> Self::Result {
|
||||
let config = msg.0;
|
||||
info!(%self.details, %config, "Config loaded");
|
||||
self.config.replace(config);
|
||||
}
|
||||
}
|
|
@ -35,12 +35,22 @@ impl RepoConfig {
|
|||
toml::from_str(toml).map_err(OneOf::new)
|
||||
}
|
||||
}
|
||||
impl Display for RepoConfig {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.branches)
|
||||
}
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
pub struct RepoBranches {
|
||||
main: String,
|
||||
next: String,
|
||||
dev: String,
|
||||
}
|
||||
impl Display for RepoBranches {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
pub struct Forge {
|
||||
|
|
112
src/server/forge/forgejo/config.rs
Normal file
112
src/server/forge/forgejo/config.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
use base64::Engine;
|
||||
use kxio::network::{self, Network};
|
||||
use terrors::OneOf;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::server::config::{RepoConfig, RepoDetails};
|
||||
|
||||
pub async fn load(
|
||||
details: RepoDetails,
|
||||
net: &Network,
|
||||
) -> Result<
|
||||
RepoConfig,
|
||||
OneOf<(
|
||||
RepoConfigFileNotFound,
|
||||
RepoConfigIsNotFile,
|
||||
RepoConfigDecodeError,
|
||||
RepoConfigParseError,
|
||||
RepoConfigUnknownError,
|
||||
)>,
|
||||
> {
|
||||
let hostname = details.forge.hostname;
|
||||
let path = details.repo;
|
||||
let filepath = ".git-next.toml";
|
||||
let branch = details.branch;
|
||||
let token = details.forge.token;
|
||||
let url = network::NetUrl::new(format!(
|
||||
"https://{hostname}/api/v1/repos/{path}/contents/{filepath}?ref={branch}&token={token}"
|
||||
));
|
||||
|
||||
info!(%url, "Loading config");
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Get,
|
||||
url,
|
||||
network::NetRequestHeaders::new(),
|
||||
network::RequestBody::None,
|
||||
network::ResponseType::Json,
|
||||
None,
|
||||
network::NetRequestLogging::Both,
|
||||
);
|
||||
let result = net.get::<ForgeContentsResponse>(request).await;
|
||||
let response = result.map_err(|e| match e {
|
||||
network::NetworkError::RequestFailed(_, status_code, _)
|
||||
| network::NetworkError::RequestError(_, status_code, _) => match status_code {
|
||||
network::StatusCode::NOT_FOUND => OneOf::new(RepoConfigFileNotFound),
|
||||
_ => OneOf::new(RepoConfigUnknownError(status_code)),
|
||||
},
|
||||
_ => OneOf::new(RepoConfigUnknownError(
|
||||
network::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)),
|
||||
})?;
|
||||
let status = response.status_code();
|
||||
match response.response_body() {
|
||||
Some(body) => {
|
||||
// we need to decode (see encoding field) the value of 'content' from the response
|
||||
match body.content_type {
|
||||
ForgeContentsType::File => decode_config(body).map_err(OneOf::broaden),
|
||||
_ => Err(OneOf::new(RepoConfigIsNotFile)),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!(%status, "Failed to fetch repo config file");
|
||||
Err(OneOf::new(RepoConfigUnknownError(status)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_config(
|
||||
body: ForgeContentsResponse,
|
||||
) -> Result<RepoConfig, OneOf<(RepoConfigDecodeError, RepoConfigParseError)>> {
|
||||
match body.encoding.as_str() {
|
||||
"base64" => {
|
||||
let decoded = base64::engine::general_purpose::STANDARD
|
||||
.decode(body.content)
|
||||
.map_err(|_| OneOf::new(RepoConfigDecodeError))?;
|
||||
let decoded =
|
||||
String::from_utf8(decoded).map_err(|_| OneOf::new(RepoConfigDecodeError))?;
|
||||
let config = toml::from_str(&decoded).map_err(|_| OneOf::new(RepoConfigParseError))?;
|
||||
Ok(config)
|
||||
}
|
||||
_ => Err(OneOf::new(RepoConfigDecodeError)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RepoConfigFileNotFound;
|
||||
#[derive(Debug)]
|
||||
pub struct RepoConfigIsNotFile;
|
||||
#[derive(Debug)]
|
||||
pub struct RepoConfigDecodeError;
|
||||
#[derive(Debug)]
|
||||
pub struct RepoConfigParseError;
|
||||
#[derive(Debug)]
|
||||
pub struct RepoConfigUnknownError(pub network::StatusCode);
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
struct ForgeContentsResponse {
|
||||
#[serde(rename = "type")]
|
||||
pub content_type: ForgeContentsType,
|
||||
pub content: String,
|
||||
pub encoding: String,
|
||||
}
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
enum ForgeContentsType {
|
||||
#[serde(rename = "file")]
|
||||
File,
|
||||
#[serde(rename = "dir")]
|
||||
Dir,
|
||||
#[serde(rename = "symlink")]
|
||||
Symlink,
|
||||
#[serde(rename = "submodule")]
|
||||
Submodule,
|
||||
}
|
1
src/server/forge/forgejo/mod.rs
Normal file
1
src/server/forge/forgejo/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod config;
|
1
src/server/forge/mod.rs
Normal file
1
src/server/forge/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod forgejo;
|
|
@ -1,7 +1,9 @@
|
|||
mod actors;
|
||||
mod config;
|
||||
pub mod forge;
|
||||
|
||||
use actix::prelude::*;
|
||||
use kxio::network::Network;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -32,7 +34,7 @@ pub fn init(fs: FileSystem) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn start(fs: FileSystem) {
|
||||
pub fn start(fs: FileSystem, net: Network) {
|
||||
let Ok(_) = init_logging() else {
|
||||
eprintln!("Failed to initialize logging.");
|
||||
return;
|
||||
|
@ -49,7 +51,7 @@ pub fn start(fs: FileSystem) {
|
|||
info!("Config loaded");
|
||||
let actors = config
|
||||
.forges()
|
||||
.flat_map(|(forge_name, forge)| create_forge_repos(forge, forge_name))
|
||||
.flat_map(|(forge_name, forge)| create_forge_repos(forge, forge_name, &net))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
actix::System::new().block_on(async {
|
||||
|
@ -63,6 +65,7 @@ pub fn start(fs: FileSystem) {
|
|||
fn create_forge_repos(
|
||||
forge: &Forge,
|
||||
forge_name: ForgeName,
|
||||
net: &Network,
|
||||
) -> Vec<(ForgeName, RepoName, RepoActor)> {
|
||||
let forge = forge.clone();
|
||||
let span = tracing::info_span!("Forge", %forge_name, %forge);
|
||||
|
@ -70,24 +73,24 @@ fn create_forge_repos(
|
|||
info!("Creating Forge");
|
||||
forge
|
||||
.repos()
|
||||
.map(create_actor(forge_name, forge.clone()))
|
||||
.map(create_actor(forge_name, forge.clone(), net))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn create_actor(
|
||||
forge_name: ForgeName,
|
||||
forge: config::Forge,
|
||||
net: &Network,
|
||||
) -> impl Fn((RepoName, &Repo)) -> (ForgeName, RepoName, RepoActor) {
|
||||
let net = net.clone();
|
||||
move |(repo_name, repo)| {
|
||||
let span = tracing::info_span!("Repo", %repo_name, %repo);
|
||||
let _guard = span.enter();
|
||||
info!("Creating Repo");
|
||||
let actor = actors::repo::RepoActor::new(config::RepoDetails::new(
|
||||
&repo_name,
|
||||
repo,
|
||||
&forge_name,
|
||||
&forge,
|
||||
));
|
||||
let actor = actors::repo::RepoActor::new(
|
||||
config::RepoDetails::new(&repo_name, repo, &forge_name, &forge),
|
||||
net.clone(),
|
||||
);
|
||||
info!("Created Repo");
|
||||
(forge_name.clone(), repo_name, actor)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue