first working POC

This commit is contained in:
Antoine Martin 2021-03-29 17:10:53 +02:00
parent b37891af49
commit 2bd817099f
3 changed files with 150 additions and 26 deletions

View file

@ -1,13 +1,13 @@
use serde::{Deserialize, Serialize}; use serde::Deserialize;
#[derive(Deserialize, Serialize)] #[derive(Clone, Deserialize)]
pub(crate) struct Repository { pub(crate) struct Repository {
pub(crate) name: String, pub(crate) name: String,
pub(crate) full_name: String, pub(crate) full_name: String,
pub(crate) clone_url: String, pub(crate) clone_url: String,
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize)]
pub(crate) struct GiteaWebHook { pub(crate) struct GiteaWebHook {
pub(crate) repository: Repository, pub(crate) repository: Repository,
} }

View file

@ -1,12 +1,140 @@
pub struct Job { use std::os::unix::process::ExitStatusExt;
repo: String, use std::path::{Path, PathBuf};
use std::process::Command;
use std::str;
use anyhow::bail;
use log::info;
use crate::gitea::Repository;
pub(crate) struct Job {
repo: Repository,
local_path: Option<PathBuf>,
} }
// TODO: implement git operations with git2-rs where possible
impl Job { impl Job {
pub fn new(repo: String) -> Self { const REMOTES: &'static [&'static str] = &["github", "gitlab"];
Self { repo }
} pub(crate) fn new(repo: Repository) -> Self {
pub fn run(&self) -> anyhow::Result<()> { Self {
todo!() repo,
local_path: None,
}
}
fn repo_exists(&self) -> bool {
self.local_path
.as_ref()
.map(|p| p.is_dir())
.unwrap_or(false)
}
fn mirror_repo(&self) -> anyhow::Result<()> {
info!("Cloning repo {}...", self.repo.full_name);
let output = Command::new("git")
.arg("clone")
.arg("--mirror")
.arg(&self.repo.clone_url)
.arg(format!("{}", self.local_path.as_ref().unwrap().display()))
.output()?;
if !output.status.success() {
let error = str::from_utf8(&output.stderr)?;
let code = output
.status
.code()
.unwrap_or_else(|| output.status.signal().unwrap());
bail!(
"couldn't mirror repo: exit code {}, stderr:\n{}",
code,
error
);
}
// TODO: handle git LFS mirroring:
// https://github.com/git-lfs/git-lfs/issues/2342#issuecomment-310323647
Ok(())
}
fn update_repo(&self) -> anyhow::Result<()> {
info!("Updating repo {}...", self.repo.full_name);
let output = Command::new("git")
.arg("-C")
.arg(format!("{}", self.local_path.as_ref().unwrap().display()))
.arg("remote")
.arg("update")
.arg("origin")
// otherwise deleted tags and branches aren't updated on local copy
.arg("--prune")
.output()?;
if !output.status.success() {
let error = str::from_utf8(&output.stderr)?;
let code = output
.status
.code()
.unwrap_or_else(|| output.status.signal().unwrap());
bail!(
"couldn't update origin remote: exit code {}, stderr:\n{}",
code,
error
);
}
Ok(())
}
fn update_mirrors(&self) -> anyhow::Result<()> {
for remote in Self::REMOTES.iter() {
info!("Updating mirror {}:{}...", remote, self.repo.full_name);
let output = Command::new("git")
.arg("-C")
.arg(format!("{}", self.local_path.as_ref().unwrap().display()))
.arg("push")
.arg("--mirror")
.arg(format!("git@{}.com:{}", remote, self.repo.full_name))
.output()?;
if !output.status.success() {
let error = str::from_utf8(&output.stderr)?;
let code = output
.status
.code()
.unwrap_or_else(|| output.status.signal().unwrap());
bail!(
"couldn't update origin remote: exit code {}, stderr:\n{}",
code,
error
);
}
}
Ok(())
}
pub(crate) fn run(&mut self, homedir: &Path) -> anyhow::Result<()> {
let local_path = homedir.join(&self.repo.full_name);
println!("{}", local_path.display());
assert!(local_path.is_absolute());
self.local_path = Some(local_path);
if !self.repo_exists() {
self.mirror_repo()?;
} else {
self.update_repo()?;
}
self.update_mirrors()
} }
} }

View file

@ -1,13 +1,14 @@
#![feature(proc_macro_hygiene, decl_macro)] #![feature(proc_macro_hygiene, decl_macro)]
use std::path::{Path, PathBuf}; use std::env;
use std::path::PathBuf;
use std::sync::{ use std::sync::{
mpsc::{channel, Receiver, Sender}, mpsc::{channel, Receiver, Sender},
Mutex, Mutex,
}; };
use std::thread; use std::thread;
use rocket::{fairing::AdHoc, http::Status, post, routes, State}; use rocket::{http::Status, post, routes, State};
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use log::error; use log::error;
@ -18,26 +19,24 @@ use gitea::GiteaWebHook;
mod job; mod job;
use job::Job; use job::Job;
struct HomeDir(PathBuf);
struct JobSender(Mutex<Sender<Job>>); struct JobSender(Mutex<Sender<Job>>);
#[post("/", data = "<payload>")] #[post("/", data = "<payload>")]
fn gitea_webhook(payload: Json<GiteaWebHook>, sender: State<JobSender>) -> Status { fn gitea_webhook(payload: Json<GiteaWebHook>, sender: State<JobSender>) -> Status {
{ {
let sender = sender.0.lock().unwrap(); let sender = sender.0.lock().unwrap();
sender let repo = &payload.repository;
.send(Job::new(payload.repository.full_name.clone())) sender.send(Job::new(repo.clone())).unwrap();
.unwrap();
} }
Status::Ok Status::Ok
} }
fn repo_updater(rx: Receiver<Job>) { fn repo_updater(rx: Receiver<Job>, homedir: PathBuf) {
loop { loop {
let job = rx.recv().unwrap(); let mut job = rx.recv().unwrap();
if let Err(err) = job.run() { if let Err(err) = job.run(&homedir) {
error!("couldn't process job: {}", err); error!("couldn't process job: {}", err);
} }
} }
@ -46,19 +45,16 @@ fn repo_updater(rx: Receiver<Job>) {
fn main() { fn main() {
let (sender, receiver) = channel(); let (sender, receiver) = channel();
let homedir = env::var("LOHR_HOME").unwrap_or_else(|_| "./".to_string());
let homedir: PathBuf = homedir.into();
let homedir = homedir.canonicalize().expect("LOHR_HOME isn't valid!");
thread::spawn(move || { thread::spawn(move || {
repo_updater(receiver); repo_updater(receiver, homedir);
}); });
rocket::ignite() rocket::ignite()
.mount("/", routes![gitea_webhook]) .mount("/", routes![gitea_webhook])
.manage(JobSender(Mutex::new(sender))) .manage(JobSender(Mutex::new(sender)))
.attach(AdHoc::on_attach("Assets Config", |rocket| {
let home_dir = rocket.config().get_str("home").unwrap();
let home_dir = Path::new(home_dir).into();
Ok(rocket.manage(HomeDir(home_dir)))
}))
.launch(); .launch();
} }