Start of port from web to CLI

This commit is contained in:
Bill Thiede 2022-12-08 20:24:32 -08:00
parent 11b08785ed
commit 46b28e8bdd
6 changed files with 172 additions and 97 deletions

1
Cargo.lock generated
View File

@ -134,6 +134,7 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
name = "aocsync"
version = "0.1.0"
dependencies = [
"anyhow",
"glog",
"log 0.4.17",
"rocket 0.5.0-rc.2",

View File

@ -13,3 +13,4 @@ serde = { version = "1.0.148", features = ["serde_derive"] }
rocket_anyhow = "0.1.1"
thiserror = "1.0.37"
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] }
anyhow = "1.0.66"

View File

@ -8,8 +8,7 @@ port = 9346
# Uncomment to make it production like.
#log_level = "critical"
source_root = "/net/nasx/mnt/storage/aoc/github/"
build_root = "/tmp/aocsync/"
www_root = "/tmp/aocsync-serve/"
repos = { akramer = { name = "aoc2022", branch = "main" }, ggriffiniii = { name = "aoc2022", branch = "main" }, wathiede = { name = "advent/2022", branch = "master", first_commit = "bc3feabb4669b7ad8167398a4d37eefe68ea0565" } }
repos = { akramer = { url = "https://github.com/akramer/aoc2022", name = "aoc2022", branch = "main" }, ggriffiniii = { url = "https://github.com/ggriffiniii/aoc2022", name = "aoc2022", branch = "main" }, wathiede = { url = "https://github.com/wathiede/advent", name = "advent/2022", branch = "master", first_commit = "bc3feabb4669b7ad8167398a4d37eefe68ea0565", subdir = "2022" } }

34
src/bin/aocpoller.rs Normal file
View File

@ -0,0 +1,34 @@
use std::process::Command;
use aocsync::{logging_run, Config};
use glog::Flags;
use log::info;
fn main() -> anyhow::Result<()> {
glog::new()
.init(Flags {
colorlogtostderr: true,
//alsologtostderr: true, // use logtostderr to only write to stderr and not to files
logtostderr: true,
..Default::default()
})
.unwrap();
let config = rocket::Config::figment()
.extract::<Config>()
.expect("Couldn't parse config");
info!("{:#?}", config);
for (name, repo) in config.repos.iter() {
let ts = logging_run(
Command::new("git")
.arg("ls-remote")
.arg(&repo.url)
.arg("-q")
.arg(&repo.name)
.arg("-h")
.arg(&repo.branch),
)?;
let out = String::from_utf8_lossy(&ts.output.stdout);
let head = &out[..out.find('\t').expect("No tab in output")];
info!("HEAD: '{}'", head);
}
Ok(())
}

93
src/lib.rs Normal file
View File

@ -0,0 +1,93 @@
use std::{
collections::HashMap,
io,
path::{Path, PathBuf},
process::{Command, Output},
time::{Duration, Instant},
};
use log::info;
use rocket::response::Responder;
use serde::Deserialize;
use thiserror::Error;
#[derive(Error, Debug, Responder)]
pub enum SyncError {
#[error("IO error")]
IoError(#[from] io::Error),
}
#[derive(Debug, Deserialize)]
pub struct Config {
pub build_root: PathBuf,
pub www_root: PathBuf,
pub repos: HashMap<String, Repo>,
}
#[derive(Debug, Deserialize)]
pub struct Repo {
pub name: String,
pub url: String,
pub branch: String,
pub first_commit: Option<String>,
pub subdir: Option<String>,
}
pub fn indent_output(bytes: &[u8]) -> String {
let s = String::from_utf8_lossy(bytes);
let s = if s.is_empty() { "<NO OUTPUT>" } else { &s };
s.split('\n')
.map(|l| format!("\t{l}"))
.collect::<Vec<_>>()
.join("\n")
}
pub fn format_task_status(ts: &TaskStatus) -> String {
format!(
"Status {} ({}) {}s:\nStdout:\n{}\nStderr:\n{}",
ts.command,
ts.output.status,
ts.duration.as_secs_f32(),
indent_output(&ts.output.stdout),
indent_output(&ts.output.stderr)
)
}
pub struct TaskStatus {
pub command: String,
pub output: Output,
pub duration: Duration,
}
pub fn logging_run(cmd: &mut Command) -> Result<TaskStatus, SyncError> {
info!(
"{}$ {} {}",
cmd.get_current_dir()
.unwrap_or(Path::new("<NO CWD>"))
.display(),
cmd.get_program().to_string_lossy(),
cmd.get_args()
.map(|a| a.to_string_lossy())
.collect::<Vec<_>>()
.join(" "),
);
let start = Instant::now();
let o = cmd.output()?;
let ts = TaskStatus {
output: o,
command: format!("{:?}", cmd),
duration: start.elapsed(),
};
info!("{}", format_task_status(&ts));
Ok(ts)
}
pub fn git_checkout(build_path: &Path, rev: &str) -> Result<TaskStatus, SyncError> {
logging_run(
Command::new("git")
.current_dir(&build_path)
.arg("checkout")
.arg("-q")
.arg(rev),
)
}

View File

@ -7,90 +7,32 @@ use std::{
io,
path::{Path, PathBuf},
process::{Command, Output},
time::{Duration, Instant},
};
use aocsync::{format_task_status, git_checkout, logging_run, Config, SyncError, TaskStatus};
use glog::Flags;
use log::{error, info};
use rocket::{fairing::AdHoc, fs::FileServer, response::Responder, State};
use rocket_dyn_templates::{context, Template};
use serde::Deserialize;
use thiserror::Error;
#[derive(Error, Debug, Responder)]
pub enum SyncError {
#[error("IO error")]
IoError(#[from] io::Error),
}
// For testing
#[get("/-/reload/<repo>")]
fn get_reload(config: &State<Config>, repo: &str) -> Result<String, SyncError> {
reload(config, repo)
fn get_reload(
lock: &State<ReloadLock>,
config: &State<Config>,
repo: &str,
) -> Result<String, SyncError> {
reload(lock, config, repo)
}
#[post("/-/reload/<repo>")]
fn post_reload(config: &State<Config>, repo: &str) -> Result<String, SyncError> {
reload(config, repo)
}
fn indent_output(bytes: &[u8]) -> String {
let s = String::from_utf8_lossy(bytes);
let s = if s.is_empty() { "<NO OUTPUT>" } else { &s };
s.split('\n')
.map(|l| format!("\t{l}"))
.collect::<Vec<_>>()
.join("\n")
}
fn format_task_status(ts: &TaskStatus) -> String {
format!(
"Status {} ({}) {}s:\nStdout:\n{}\nStderr:\n{}",
ts.command,
ts.output.status,
ts.duration.as_secs_f32(),
indent_output(&ts.output.stdout),
indent_output(&ts.output.stderr)
)
}
struct TaskStatus {
command: String,
output: Output,
duration: Duration,
}
fn logging_run(cmd: &mut Command) -> Result<TaskStatus, SyncError> {
info!(
"{}$ {} {}",
cmd.get_current_dir()
.unwrap_or(Path::new("<NO CWD>"))
.display(),
cmd.get_program().to_string_lossy(),
cmd.get_args()
.map(|a| a.to_string_lossy())
.collect::<Vec<_>>()
.join(" "),
);
let start = Instant::now();
let o = cmd.output()?;
let ts = TaskStatus {
output: o,
command: format!("{:?}", cmd),
duration: start.elapsed(),
};
info!("{}", format_task_status(&ts));
Ok(ts)
}
fn git_checkout(build_path: &Path, rev: &str) -> Result<TaskStatus, SyncError> {
logging_run(
Command::new("git")
.current_dir(&build_path)
.arg("checkout")
.arg("-q")
.arg(rev),
)
fn post_reload(
lock: &State<ReloadLock>,
config: &State<Config>,
repo: &str,
) -> Result<String, SyncError> {
reload(lock, config, repo)
}
fn bench_at_commit(
@ -113,7 +55,14 @@ fn bench_at_commit(
Ok(output)
}
fn reload(config: &State<Config>, name: &str) -> Result<String, SyncError> {
use std::sync::{Arc, Mutex};
fn reload(
lock: &State<ReloadLock>,
config: &State<Config>,
name: &str,
) -> Result<String, SyncError> {
let _locked = lock.0.lock().unwrap();
let repo = &config.repos[name];
info!("Need to reload '{}': {:?}\n{:#?}", name, config, repo);
@ -122,12 +71,15 @@ fn reload(config: &State<Config>, name: &str) -> Result<String, SyncError> {
let www_root = config.www_root.join(name);
let target_root = config.build_root.join("target");
let source_path = config.source_root.join(name).join(&repo.name);
let build_path = git_root.join(&repo.name);
let checkout_path = git_root.join(&repo.name);
let build_path = if let Some(subdir) = &repo.subdir {
git_root.join(&repo.name).join(subdir)
} else {
git_root.join(&repo.name)
};
let target_path = target_root.join(name);
dbg!(
&source_path,
&build_path,
&target_path,
&commits_root,
@ -152,28 +104,35 @@ fn reload(config: &State<Config>, name: &str) -> Result<String, SyncError> {
fs::create_dir_all(&www_root)?;
}
let mut output = vec![logging_run(Command::new("git").arg("version"))?];
let needs_clone = !build_path.exists();
let needs_clone = !checkout_path.exists();
if needs_clone {
output.push(logging_run(
Command::new("git")
.current_dir(&git_root)
.arg("clone")
.arg(&source_path),
.arg(&repo.url)
.arg(&checkout_path),
)?);
}
output.push(logging_run(
Command::new("git")
.current_dir(&build_path)
.current_dir(&checkout_path)
.arg("checkout")
.arg("-f")
.arg(&repo.branch),
)?);
dbg!(&build_path);
output.push(logging_run(
Command::new("git")
.current_dir(&checkout_path)
.arg("checkout")
.arg("HEAD"),
)?);
dbg!(&checkout_path);
// Make sure buildable clone is up to date
output.push(logging_run(
Command::new("git").current_dir(&build_path).arg("fetch"),
Command::new("git").current_dir(&checkout_path).arg("pull"),
)?);
let commits = logging_run(Command::new("git").current_dir(&build_path).args([
let commits = logging_run(Command::new("git").current_dir(&checkout_path).args([
"log",
"--format=%H",
&repo.branch,
@ -243,20 +202,7 @@ fn http500(req: &rocket::Request) -> String {
format!("{:?}", req)
}
#[derive(Debug, Deserialize)]
struct Config {
source_root: PathBuf,
build_root: PathBuf,
www_root: PathBuf,
repos: HashMap<String, Repo>,
}
#[derive(Debug, Deserialize)]
struct Repo {
name: String,
branch: String,
first_commit: Option<String>,
}
struct ReloadLock(Arc<Mutex<()>>);
#[launch]
fn rocket() -> _ {
@ -276,6 +222,7 @@ fn rocket() -> _ {
rocket::build()
.mount("/", routes![index, get_reload, post_reload])
.mount("/results/", FileServer::from(config.www_root))
.manage(ReloadLock(Arc::new(Mutex::new(()))))
//.register("/", catchers![http500])
.attach(AdHoc::config::<Config>())
.attach(Template::fairing())