schedule should be working now, split run function into some helper functions
This commit is contained in:
parent
25a6871126
commit
46a5980432
5 changed files with 176 additions and 43 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
@ -84,6 +84,12 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.31"
|
version = "4.5.31"
|
||||||
|
@ -149,6 +155,16 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctrlc"
|
||||||
|
version = "3.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
|
||||||
|
dependencies = [
|
||||||
|
"nix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -456,6 +472,18 @@ version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.3"
|
version = "1.20.3"
|
||||||
|
@ -515,6 +543,7 @@ name = "refractr"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"ctrlc",
|
||||||
"git2",
|
"git2",
|
||||||
"hex",
|
"hex",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.29", features = ["derive"] }
|
clap = { version = "4.5.29", features = ["derive"] }
|
||||||
|
ctrlc = "3.4.5"
|
||||||
git2 = "0.20.0"
|
git2 = "0.20.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
serde = "1.0.217"
|
serde = "1.0.217"
|
||||||
|
|
|
@ -103,7 +103,7 @@ pub struct Git {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Schedule {
|
pub struct Schedule {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
interval: Option<i32>,
|
pub interval: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_config(paths: Vec<PathBuf>, refractr: &common::Refractr) -> Vec<ConfigFile> {
|
pub fn read_config(paths: Vec<PathBuf>, refractr: &common::Refractr) -> Vec<ConfigFile> {
|
||||||
|
@ -154,6 +154,7 @@ pub fn read_config(paths: Vec<PathBuf>, refractr: &common::Refractr) -> Vec<Conf
|
||||||
fn verify_config(config: Config) -> Config {
|
fn verify_config(config: Config) -> Config {
|
||||||
if config.schedule.enabled {
|
if config.schedule.enabled {
|
||||||
assert_ne!(config.schedule.interval, None);
|
assert_ne!(config.schedule.interval, None);
|
||||||
|
assert!(config.schedule.interval.unwrap() >= 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_ne!("", match &config.work_dir {
|
assert_ne!("", match &config.work_dir {
|
||||||
|
|
|
@ -65,7 +65,7 @@ fn main() -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
common::verbose(refractr.verbose, 1, format!("Config file(s) read successfully"));
|
common::verbose(refractr.verbose, 1, format!("Config file(s) read successfully"));
|
||||||
refractr::start(refractr, cfgs)
|
refractr::run(refractr, cfgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
184
src/refractr.rs
184
src/refractr.rs
|
@ -1,13 +1,23 @@
|
||||||
use git2::{Cred, PushOptions, Remote, RemoteCallbacks, Repository};
|
use git2::{Cred, FetchOptions, PushOptions, Remote, RemoteCallbacks, Repository};
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
use crate::common::{self, Refractr};
|
use crate::common::{self, Refractr};
|
||||||
use crate::config::ConfigFile;
|
use crate::config::{Config, ConfigFile};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use git2::{Error, ErrorCode};
|
use git2::{Error, ErrorCode};
|
||||||
use hex;
|
use hex;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
struct OpenedRepository {
|
||||||
|
repo: Repository,
|
||||||
|
remotes: Vec<String>,
|
||||||
|
cfg: Config,
|
||||||
|
}
|
||||||
|
|
||||||
fn set_up_work_dir(work_dir: PathBuf) -> String {
|
fn set_up_work_dir(work_dir: PathBuf) -> String {
|
||||||
if let Err(e) = fs::create_dir_all(&work_dir) {
|
if let Err(e) = fs::create_dir_all(&work_dir) {
|
||||||
|
@ -59,9 +69,122 @@ fn fast_forward(repo_dir: &str, branches: &Option<Vec<String>>) -> Result<(), Er
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(refractr: Refractr, cfgs: Vec<ConfigFile>) -> std::io::Result<()> {
|
fn fetch_origin(repo: &Repository, branches: &Option<Vec<String>>) {
|
||||||
|
let mut origin = repo.find_remote("origin").unwrap();
|
||||||
|
let callbacks = RemoteCallbacks::new();
|
||||||
|
let mut push_options = FetchOptions::new();
|
||||||
|
push_options.remote_callbacks(callbacks);
|
||||||
|
|
||||||
|
let mut refs = Vec::new();
|
||||||
|
let strings = get_branches(&repo, &branches, true);
|
||||||
|
for branch in &strings {
|
||||||
|
refs.push(branch.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = origin.download(&[] as &[&str], Some(&mut push_options)) {
|
||||||
|
eprintln!("refractr: failed to fetch origin: {}: {}", origin.url().unwrap(), e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_remotes<'a> (refractr: &Refractr, repo: &'a Repository, cfg: &ConfigFile) -> Vec<String> {
|
||||||
|
// create remotes for each "to" repo
|
||||||
|
let mut remote_list = Vec::new();
|
||||||
|
for to in &cfg.config.to {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(to);
|
||||||
|
let remote_id = format!("refractr-{}", &hex::encode(hasher.finalize())[..8]);
|
||||||
|
common::verbose(refractr.verbose, 2, format!("Attempting to create remote {} for url {}", remote_id, to));
|
||||||
|
match repo.remote(remote_id.as_str(), to) {
|
||||||
|
Ok(_) => remote_list.push(remote_id),
|
||||||
|
Err(e) => {
|
||||||
|
if e.code() == ErrorCode::Exists {
|
||||||
|
eprintln!("refractr: warning: remote {} already exists, skipping", remote_id);
|
||||||
|
remote_list.push(remote_id)
|
||||||
|
} else {
|
||||||
|
panic!("refractr: failed to create remote: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_list
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_remotes(refractr: &Refractr, cfg: &Config, repo: &Repository, remote_list: &Vec<String>) {
|
||||||
|
for id in remote_list {
|
||||||
|
let mut remote = repo.find_remote(&id).unwrap();
|
||||||
|
common::verbose(refractr.verbose, 1, format!("Pushing to remote: {}", remote.url().unwrap()));
|
||||||
|
let mut callbacks = RemoteCallbacks::new();
|
||||||
|
callbacks.credentials(|_,_,_| Cred::ssh_key("git", None, &Path::new(&cfg.git.ssh_identity_file), None));
|
||||||
|
let mut push_options = PushOptions::new();
|
||||||
|
push_options.remote_callbacks(callbacks);
|
||||||
|
|
||||||
|
let mut refs = Vec::new();
|
||||||
|
let strings = get_branches(&repo, &cfg.branches, true);
|
||||||
|
for branch in &strings {
|
||||||
|
refs.push(branch.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
match remote.push::<&str>(&refs, Some(&mut push_options)) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("refractr: failed to push to remote: {}: {}", remote.url().unwrap(), e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn looper(refractr: Refractr, repos: Vec<OpenedRepository>) {
|
||||||
|
let mut current_ints = Vec::new();
|
||||||
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
|
let r = running.clone();
|
||||||
|
let count = repos.len();
|
||||||
|
for i in 0..repos.len() {
|
||||||
|
current_ints.push(u64::from(repos[i].cfg.schedule.interval.unwrap().unsigned_abs()));
|
||||||
|
};
|
||||||
|
let mut original_ints = current_ints.clone();
|
||||||
|
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
r.store(false, Ordering::SeqCst);
|
||||||
|
}).expect("Failed to set ^C handler");
|
||||||
|
|
||||||
|
common::verbose(refractr.verbose, 1, format!("Starting scheduled loop"));
|
||||||
|
let min = *current_ints.iter().min().unwrap();
|
||||||
|
let mut do_break = false;
|
||||||
|
while !do_break {
|
||||||
|
do_break = true;
|
||||||
|
let sleep_int = time::Duration::from_secs(min);
|
||||||
|
let now = time::Instant::now();
|
||||||
|
|
||||||
|
common::verbose(refractr.verbose, 2, format!("Sleeping for {} seconds", sleep_int.as_secs()));
|
||||||
|
while running.load(Ordering::SeqCst) {
|
||||||
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
|
if now.elapsed().as_secs() >= sleep_int.as_secs() {
|
||||||
|
common::verbose(refractr.verbose, 3, format!("Thread has awoken!"));
|
||||||
|
for i in 0..count {
|
||||||
|
current_ints[i] -= now.elapsed().as_secs();
|
||||||
|
if i <= 0 {
|
||||||
|
current_ints[i] = original_ints[i].clone();
|
||||||
|
common::verbose(refractr.verbose, 2, format!("Interval for {} has arrived, pulling", repos[i].cfg.from));
|
||||||
|
fetch_origin(&repos[i].repo, &repos[i].cfg.branches);
|
||||||
|
common::verbose(refractr.verbose, 2, format!("Pushing {}", repos[i].cfg.from));
|
||||||
|
push_remotes(&refractr, &repos[i].cfg, &repos[i].repo, &repos[i].remotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do_break = false;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common::verbose(refractr.verbose, 1, format!("Exited looper due to ^C"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(refractr: Refractr, cfgs: Vec<ConfigFile>) -> std::io::Result<()> {
|
||||||
common::verbose(refractr.verbose, 3, format!("Starting main refractr loop"));
|
common::verbose(refractr.verbose, 3, format!("Starting main refractr loop"));
|
||||||
for cfg in &cfgs {
|
let mut loop_repos = Vec::new();
|
||||||
|
|
||||||
|
for cfg in cfgs {
|
||||||
// set up the working directory
|
// set up the working directory
|
||||||
common::verbose(refractr.verbose, 3, format!("Loaded config: {}", cfg.path));
|
common::verbose(refractr.verbose, 3, format!("Loaded config: {}", cfg.path));
|
||||||
let path_str = set_up_work_dir(match &cfg.config.work_dir {
|
let path_str = set_up_work_dir(match &cfg.config.work_dir {
|
||||||
|
@ -102,45 +225,24 @@ pub fn start(refractr: Refractr, cfgs: Vec<ConfigFile>) -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// create remotes for each "to" repo
|
|
||||||
let mut remote_list: Vec<Remote<'_>> = Vec::new();
|
let repo_fresh = Repository::open(Path::new(&repo_dir)).unwrap();
|
||||||
for to in &cfg.config.to {
|
let remotes = make_remotes(&refractr, &repo_fresh, &cfg);
|
||||||
let mut hasher = Sha256::new();
|
push_remotes(&refractr, &cfg.config, &repo, &remotes);
|
||||||
hasher.update(to);
|
if cfg.config.schedule.enabled {
|
||||||
let remote_id = format!("refractr-{}", &hex::encode(hasher.finalize())[..8]);
|
loop_repos.push(OpenedRepository {
|
||||||
common::verbose(refractr.verbose, 2, format!("Attempting to create remote {} for url {}", remote_id, to));
|
repo,
|
||||||
match repo.remote(remote_id.as_str(), to) {
|
remotes,
|
||||||
Ok(r) => remote_list.push(r),
|
cfg: cfg.config
|
||||||
Err(e) => {
|
});
|
||||||
if e.code() == ErrorCode::Exists {
|
|
||||||
eprintln!("refractr: warning: remote {} already exists, skipping", remote_id)
|
|
||||||
} else {
|
|
||||||
panic!("refractr: failed to create remote: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for mut remote in remote_list {
|
if loop_repos.len() >= 1 {
|
||||||
common::verbose(refractr.verbose, 1, format!("Pushing to remote: {}", remote.url().unwrap()));
|
common::verbose(refractr.verbose, 2, format!("{} configs have schedules enabled, setting up looper", loop_repos.len()));
|
||||||
let mut callbacks = RemoteCallbacks::new();
|
looper(refractr, loop_repos);
|
||||||
callbacks.credentials(|_,_,_| Cred::ssh_key("git", None, &Path::new(&cfg.config.git.ssh_identity_file), None));
|
} else {
|
||||||
let mut push_options = PushOptions::new();
|
common::verbose(refractr.verbose, 2, format!("No scheduled configs found, exiting refractr"));
|
||||||
push_options.remote_callbacks(callbacks);
|
|
||||||
|
|
||||||
let mut refs = Vec::new();
|
|
||||||
let strings = get_branches(&repo, &cfg.config.branches, true);
|
|
||||||
for branch in &strings {
|
|
||||||
refs.push(branch.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
match remote.push::<&str>(&refs, Some(&mut push_options)) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("refractr: failed to push to remote: {}: {}", remote.url().unwrap(), e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Reference in a new issue