make functions part of Refractr object
This commit is contained in:
parent
3b7d30f492
commit
4791e1dd84
4 changed files with 213 additions and 208 deletions
|
@ -1,10 +1,4 @@
|
|||
|
||||
pub struct Refractr {
|
||||
pub verbose: u8,
|
||||
pub pid: u32,
|
||||
pub unix: bool
|
||||
}
|
||||
|
||||
pub fn verbose(level: u8, msg_lvl: u8, msg: String) {
|
||||
if level < msg_lvl { return };
|
||||
let mut prefix = String::new();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::common;
|
||||
use crate::refractr::Refractr;
|
||||
use core::fmt;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
@ -106,7 +107,7 @@ pub struct Schedule {
|
|||
pub interval: Option<i32>,
|
||||
}
|
||||
|
||||
pub fn read_config(paths: Vec<PathBuf>, refractr: &common::Refractr) -> Vec<ConfigFile> {
|
||||
pub fn read_config(paths: Vec<PathBuf>, refractr: &Refractr) -> Vec<ConfigFile> {
|
||||
let mut config_files: Vec<ConfigFile> = vec![];
|
||||
for path in paths {
|
||||
common::verbose(refractr.verbose, 1, format!("Reading config file: \"{}\"", String::from(path.to_string_lossy())));
|
||||
|
|
|
@ -6,6 +6,7 @@ use clap::Parser;
|
|||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use users;
|
||||
use crate::refractr::Refractr;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "refractor")]
|
||||
|
@ -33,7 +34,7 @@ fn get_config_default() -> &'static str {
|
|||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let args = Args::parse();
|
||||
let refractr = common::Refractr {
|
||||
let refractr = Refractr {
|
||||
verbose: args.verbose,
|
||||
pid: process::id(),
|
||||
unix: cfg!(unix)
|
||||
|
@ -65,7 +66,7 @@ fn main() -> std::io::Result<()> {
|
|||
}
|
||||
|
||||
common::verbose(refractr.verbose, 1, format!("Config file(s) read successfully"));
|
||||
refractr::run(refractr, cfgs)
|
||||
refractr.run(cfgs)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
407
src/refractr.rs
407
src/refractr.rs
|
@ -1,8 +1,8 @@
|
|||
use git2::build::CheckoutBuilder;
|
||||
use git2::{BranchType, Cred, PushOptions, RemoteCallbacks, Repository};
|
||||
use git2::{Cred, PushOptions, RemoteCallbacks, Repository};
|
||||
use sha2::{Sha256, Digest};
|
||||
|
||||
use crate::common::{self, Refractr};
|
||||
use crate::common;
|
||||
use crate::config::{Config, ConfigFile};
|
||||
use std::fs;
|
||||
use std::env;
|
||||
|
@ -14,6 +14,12 @@ use hex;
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Refractr {
|
||||
pub verbose: u8,
|
||||
pub pid: u32,
|
||||
pub unix: bool
|
||||
}
|
||||
|
||||
struct OpenedRepository {
|
||||
repo: Repository,
|
||||
path: String,
|
||||
|
@ -21,231 +27,234 @@ struct OpenedRepository {
|
|||
cfg: Config,
|
||||
}
|
||||
|
||||
fn set_up_work_dir(work_dir: PathBuf) -> String {
|
||||
if let Err(e) = fs::create_dir_all(&work_dir) {
|
||||
panic!("refractr: could not create working directory: {}: {}", work_dir.to_string_lossy(), e)
|
||||
impl Refractr {
|
||||
fn set_up_work_dir(&self, work_dir: PathBuf) -> String {
|
||||
if let Err(e) = fs::create_dir_all(&work_dir) {
|
||||
panic!("refractr: could not create working directory: {}: {}", work_dir.to_string_lossy(), e)
|
||||
}
|
||||
work_dir.to_string_lossy().to_string()
|
||||
}
|
||||
work_dir.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
fn get_branches(repo: &Repository, branches: &Option<Vec<String>>, refs: bool) -> Vec<String> {
|
||||
match branches {
|
||||
Some(repo_branches) => {
|
||||
if refs {
|
||||
let mut refs_branches = Vec::new();
|
||||
for branch in repo_branches {
|
||||
refs_branches.push(format!("refs/heads/{}", branch));
|
||||
fn get_branches(&self, repo: &Repository, branches: &Option<Vec<String>>, refs: bool) -> Vec<String> {
|
||||
match branches {
|
||||
Some(repo_branches) => {
|
||||
if refs {
|
||||
let mut refs_branches = Vec::new();
|
||||
for branch in repo_branches {
|
||||
refs_branches.push(format!("refs/heads/{}", branch));
|
||||
}
|
||||
refs_branches
|
||||
} else {
|
||||
repo_branches.to_vec()
|
||||
}
|
||||
refs_branches
|
||||
} else {
|
||||
repo_branches.to_vec()
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let mut strings = Vec::new();
|
||||
let remote_branches = match repo.branches(Some(git2::BranchType::Remote)) {
|
||||
Ok(b) => b,
|
||||
Err(e) => panic!("refractr: failed to get branches: {}", e)
|
||||
};
|
||||
for branch in remote_branches {
|
||||
if let Ok((b, _)) = branch {
|
||||
if let Ok(Some(name)) = b.name() {
|
||||
if refs {
|
||||
strings.push(format!("refs/heads/{}", name.to_string()))
|
||||
} else {
|
||||
strings.push(name.to_string());
|
||||
},
|
||||
None => {
|
||||
let mut strings = Vec::new();
|
||||
let remote_branches = match repo.branches(Some(git2::BranchType::Remote)) {
|
||||
Ok(b) => b,
|
||||
Err(e) => panic!("refractr: failed to get branches: {}", e)
|
||||
};
|
||||
for branch in remote_branches {
|
||||
if let Ok((b, _)) = branch {
|
||||
if let Ok(Some(name)) = b.name() {
|
||||
if refs {
|
||||
strings.push(format!("refs/heads/{}", name.to_string()))
|
||||
} else {
|
||||
strings.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
strings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fast_forward(refractr: &Refractr, repo_dir: &str, branches: &Option<Vec<String>>) -> Result<(), Error> {
|
||||
let repo = Repository::open(repo_dir)?;
|
||||
let branch_list: Vec<String> = get_branches(&repo, branches, false);
|
||||
|
||||
common::verbose(refractr.verbose, 2, format!("Pulling origin"));
|
||||
repo.find_remote("origin")?.fetch(&branch_list, None, None)?;
|
||||
|
||||
let fetch_head = repo.find_reference("FETCH_HEAD")?;
|
||||
let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?;
|
||||
let analysis = repo.merge_analysis(&[&fetch_commit])?;
|
||||
if analysis.0.is_fast_forward() {
|
||||
for branch in branch_list {
|
||||
let refname = format!("refs/heads/{}", branch);
|
||||
let mut reference = repo.find_reference(&refname)?;
|
||||
reference.set_target(fetch_commit.id(), "Fast-forward")?;
|
||||
repo.set_head(&refname)?;
|
||||
let _ = repo.checkout_head(Some(CheckoutBuilder::default().force()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
strings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remote_list
|
||||
}
|
||||
fn fast_forward(&self, repo_dir: &str, branches: &Option<Vec<String>>) -> Result<(), Error> {
|
||||
let repo = Repository::open(repo_dir)?;
|
||||
let branch_list: Vec<String> = self.get_branches(&repo, branches, false);
|
||||
|
||||
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);
|
||||
common::verbose(self.verbose, 2, format!("Pulling origin"));
|
||||
repo.find_remote("origin")?.fetch(&branch_list, None, None)?;
|
||||
|
||||
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)
|
||||
let fetch_head = repo.find_reference("FETCH_HEAD")?;
|
||||
let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?;
|
||||
let analysis = repo.merge_analysis(&[&fetch_commit])?;
|
||||
if analysis.0.is_fast_forward() {
|
||||
for branch in branch_list {
|
||||
let refname = format!("refs/heads/{}", branch);
|
||||
let mut reference = repo.find_reference(&refname)?;
|
||||
reference.set_target(fetch_commit.id(), "Fast-forward")?;
|
||||
repo.set_head(&refname)?;
|
||||
let _ = repo.checkout_head(Some(CheckoutBuilder::default().force()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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 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));
|
||||
let _ = fast_forward(&refractr, &repos[i].path, &repos[i].cfg.branches);
|
||||
push_remotes(&refractr, &repos[i].cfg, &repos[i].repo, &repos[i].remotes);
|
||||
fn make_remotes<'a> (&self, 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(self.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);
|
||||
}
|
||||
}
|
||||
do_break = false;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
remote_list
|
||||
}
|
||||
|
||||
fn push_remotes(&self, cfg: &Config, repo: &Repository, remote_list: &Vec<String>) {
|
||||
for id in remote_list {
|
||||
let mut remote = repo.find_remote(&id).unwrap();
|
||||
common::verbose(self.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 = self.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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"));
|
||||
let mut loop_repos = Vec::new();
|
||||
|
||||
for cfg in cfgs {
|
||||
// set up the working directory
|
||||
common::verbose(refractr.verbose, 3, format!("Loaded config: {}", cfg.path));
|
||||
let path_str = set_up_work_dir(match &cfg.config.work_dir {
|
||||
None => {
|
||||
if cfg!(windows) {
|
||||
PathBuf::from(format!("\"{}\\refractr\"", match env::var("TEMP") {
|
||||
Ok(val) => val,
|
||||
Err(_) => format!("This shouldn't happen!")
|
||||
}))
|
||||
} else {
|
||||
PathBuf::from("/tmp/refractr")
|
||||
}
|
||||
},
|
||||
Some(path) => PathBuf::from(path)
|
||||
});
|
||||
|
||||
common::verbose(refractr.verbose, 2, format!("Created working directory: {}", &path_str));
|
||||
let repo_name = match &cfg.config.from.split("/").last() {
|
||||
Some(split) => split.to_string(),
|
||||
None => panic!("refractr: failed to parse repository name")
|
||||
fn looper(&self, 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 original_ints = current_ints.clone();
|
||||
|
||||
// make initial clone
|
||||
common::verbose(refractr.verbose, 1, format!("Cloning repository: {}", &cfg.config.from));
|
||||
let repo_dir = format!("{}/{}", &path_str, repo_name);
|
||||
let repo = match Repository::clone(&cfg.config.from, Path::new(&repo_dir)) {
|
||||
Ok(repo) => repo,
|
||||
Err(_) => {
|
||||
eprintln!("refractr: warning: found existing repo at {}, attempting to use", repo_dir);
|
||||
match fast_forward(&refractr, &repo_dir, &cfg.config.branches) {
|
||||
Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) {
|
||||
repo
|
||||
ctrlc::set_handler(move || {
|
||||
r.store(false, Ordering::SeqCst);
|
||||
}).expect("Failed to set ^C handler");
|
||||
|
||||
common::verbose(self.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(self.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(self.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(self.verbose, 2, format!("Interval for {} has arrived, pulling", repos[i].cfg.from));
|
||||
let _ = self.fast_forward(&repos[i].path, &repos[i].cfg.branches);
|
||||
self.push_remotes(&repos[i].cfg, &repos[i].repo, &repos[i].remotes);
|
||||
}
|
||||
}
|
||||
do_break = false;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
common::verbose(self.verbose, 1, format!("Exited looper due to ^C"));
|
||||
|
||||
}
|
||||
|
||||
pub fn run(&self, cfgs: Vec<ConfigFile>) -> std::io::Result<()> {
|
||||
common::verbose(self.verbose, 3, format!("Starting main refractr loop"));
|
||||
let mut loop_repos = Vec::new();
|
||||
|
||||
for cfg in cfgs {
|
||||
// set up the working directory
|
||||
common::verbose(self.verbose, 3, format!("Loaded config: {}", cfg.path));
|
||||
let path_str = self.set_up_work_dir(match &cfg.config.work_dir {
|
||||
None => {
|
||||
if cfg!(windows) {
|
||||
PathBuf::from(format!("\"{}\\refractr\"", match env::var("TEMP") {
|
||||
Ok(val) => val,
|
||||
Err(_) => format!("This shouldn't happen!")
|
||||
}))
|
||||
} else {
|
||||
panic!("refractr: failed to obtain existing repo")
|
||||
},
|
||||
Err(e) => panic!("refractr: failed to obtain existing repo: {}", e)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let repo_fresh = Repository::open(Path::new(&repo_dir)).unwrap();
|
||||
let remotes = make_remotes(&refractr, &repo_fresh, &cfg);
|
||||
push_remotes(&refractr, &cfg.config, &repo, &remotes);
|
||||
if cfg.config.schedule.enabled {
|
||||
loop_repos.push(OpenedRepository {
|
||||
repo,
|
||||
path: repo_dir,
|
||||
remotes,
|
||||
cfg: cfg.config
|
||||
PathBuf::from("/tmp/refractr")
|
||||
}
|
||||
},
|
||||
Some(path) => PathBuf::from(path)
|
||||
});
|
||||
|
||||
common::verbose(self.verbose, 2, format!("Created working directory: {}", &path_str));
|
||||
let repo_name = match &cfg.config.from.split("/").last() {
|
||||
Some(split) => split.to_string(),
|
||||
None => panic!("refractr: failed to parse repository name")
|
||||
};
|
||||
|
||||
// make initial clone
|
||||
common::verbose(self.verbose, 1, format!("Cloning repository: {}", &cfg.config.from));
|
||||
let repo_dir = format!("{}/{}", &path_str, repo_name);
|
||||
let repo = match Repository::clone(&cfg.config.from, Path::new(&repo_dir)) {
|
||||
Ok(repo) => repo,
|
||||
Err(_) => {
|
||||
eprintln!("refractr: warning: found existing repo at {}, attempting to use", repo_dir);
|
||||
match self.fast_forward(&repo_dir, &cfg.config.branches) {
|
||||
Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) {
|
||||
repo
|
||||
} else {
|
||||
panic!("refractr: failed to obtain existing repo")
|
||||
},
|
||||
Err(e) => panic!("refractr: failed to obtain existing repo: {}", e)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let repo_fresh = Repository::open(Path::new(&repo_dir)).unwrap();
|
||||
let remotes = self.make_remotes(&repo_fresh, &cfg);
|
||||
self.push_remotes(&cfg.config, &repo, &remotes);
|
||||
if cfg.config.schedule.enabled {
|
||||
loop_repos.push(OpenedRepository {
|
||||
repo,
|
||||
path: repo_dir,
|
||||
remotes,
|
||||
cfg: cfg.config
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if loop_repos.len() >= 1 {
|
||||
common::verbose(refractr.verbose, 2, format!("{} configs have schedules enabled, setting up looper", loop_repos.len()));
|
||||
looper(refractr, loop_repos);
|
||||
} else {
|
||||
common::verbose(refractr.verbose, 2, format!("No scheduled configs found, exiting refractr"));
|
||||
}
|
||||
if loop_repos.len() >= 1 {
|
||||
common::verbose(self.verbose, 2, format!("{} configs have schedules enabled, setting up looper", loop_repos.len()));
|
||||
self.looper(loop_repos);
|
||||
} else {
|
||||
common::verbose(self.verbose, 2, format!("No scheduled configs found, exiting refractr"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue