#![warn(missing_docs)]
pub mod artifact;
mod client;
mod logging;
use crate::client::Client;
mod run;
use crate::run::Run;
pub mod job;
use job::{Job, JobData};
pub mod uploader;
pub use logging::GitlabLayer;
use tracing::instrument::WithSubscriber;
mod runlist;
use crate::runlist::RunList;
use uploader::Uploader;
use futures::prelude::*;
use std::path::PathBuf;
use tokio::time::{sleep, Duration};
use tracing::warn;
use tracing_subscriber::{prelude::*, Registry};
use url::Url;
#[doc(hidden)]
pub use ::tracing;
#[macro_export]
macro_rules! outputln {
($f: expr) => {
$crate::tracing::trace!(gitlab.output = true, $f)
};
($f: expr, $($arg: tt) *) => {
$crate::tracing::trace!(gitlab.output = true, $f, $($arg)*)
};
}
pub type JobResult = Result<(), ()>;
pub use client::Phase;
#[async_trait::async_trait]
pub trait JobHandler: Send {
async fn step(&mut self, script: &[String], phase: Phase) -> JobResult;
async fn upload_artifacts(&mut self, _uploader: &mut Uploader) -> JobResult {
Ok(())
}
async fn cleanup(&mut self) {}
}
#[derive(Debug)]
pub struct Runner {
client: Client,
build_dir: PathBuf,
run_list: RunList<u64, JobData>,
}
impl Runner {
pub fn new(server: Url, token: String, build_dir: PathBuf) -> Self {
let (runner, layer) = Self::new_with_layer(server, token, build_dir);
Registry::default().with(layer).init();
runner
}
pub fn new_with_layer(server: Url, token: String, build_dir: PathBuf) -> (Self, GitlabLayer) {
let client = Client::new(server, token);
let run_list = RunList::new();
(
Self {
client,
build_dir,
run_list: run_list.clone(),
},
GitlabLayer::new(run_list),
)
}
pub fn running(&self) -> usize {
self.run_list.size()
}
pub async fn request_job<F, J, Ret>(&mut self, process: F) -> Result<bool, client::Error>
where
F: FnOnce(Job) -> Ret + Sync + Send + 'static,
J: JobHandler + Send + 'static,
Ret: Future<Output = Result<J, ()>> + Send + 'static,
{
let response = self.client.request_job().await?;
if let Some(response) = response {
let mut build_dir = self.build_dir.clone();
build_dir.push(format!("{}", response.id));
let mut run = Run::new(self.client.clone(), response, &mut self.run_list);
tokio::spawn(
async move { run.run(process, build_dir).await }.with_current_subscriber(),
);
Ok(true)
} else {
Ok(false)
}
}
pub async fn wait_for_space(&mut self, max: usize) {
self.run_list.wait_for_space(max).await;
}
pub async fn drain(&mut self) {
self.run_list.wait_for_space(1).await;
}
pub async fn run<F, J, Ret>(&mut self, process: F, maximum: usize) -> Result<(), client::Error>
where
F: Fn(Job) -> Ret + Sync + Send + 'static + Clone,
J: JobHandler + Send + 'static,
Ret: Future<Output = Result<J, ()>> + Send + 'static,
{
loop {
self.wait_for_space(maximum).await;
match self.request_job(process.clone()).await {
Ok(true) => continue,
Ok(false) => (),
Err(e) => warn!("Couldn't get a job from gitlab: {:?}", e),
}
sleep(Duration::from_secs(5)).await;
}
}
}