Initial commit
Signed-off-by: Louis Vallat <louis@louis-vallat.xyz>
This commit is contained in:
commit
77ad60dd06
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
config.json
|
||||||
|
*.lock
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "gitlab-rust"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gitlab = "0.1500"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
log = "0.4.17"
|
||||||
|
env_logger = "0.9.0"
|
||||||
|
launchy = "0.2.0"
|
||||||
|
open = "2.1.2"
|
36
src/config_manager.rs
Normal file
36
src/config_manager.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
const CONFIG_FILENAME: &str = "./config.json";
|
||||||
|
|
||||||
|
// TODO REMOVE PUB AND REPLACE BY FN
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub host: String,
|
||||||
|
pub key: String,
|
||||||
|
pub projects: Vec<Project>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO REMOVE PUB AND REPLACE BY FN
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Project {
|
||||||
|
pub name: String,
|
||||||
|
pub ref_: String,
|
||||||
|
pub abs_x: u8,
|
||||||
|
pub abs_y: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config() -> Option<Config> {
|
||||||
|
if Path::exists(Path::new(CONFIG_FILENAME)) {
|
||||||
|
return serde_json::from_str(
|
||||||
|
std::fs::read_to_string(CONFIG_FILENAME).unwrap().as_str())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config(config: &Config) {
|
||||||
|
std::fs::write(Path::new(CONFIG_FILENAME),
|
||||||
|
serde_json::to_string_pretty(config).unwrap()).unwrap();
|
||||||
|
}
|
115
src/gitlab_controller.rs
Normal file
115
src/gitlab_controller.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
use std::{thread::{sleep, self, JoinHandle}, time::Duration};
|
||||||
|
|
||||||
|
use launchy::{launchpad_mini::{Button, Color, Output, DoubleBufferingBehavior}, OutputDevice};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use log::info;
|
||||||
|
use gitlab::{Gitlab, api::{projects::pipelines::{Pipelines, PipelineOrderBy}, Query}};
|
||||||
|
|
||||||
|
use crate::config_manager::{Config, Project};
|
||||||
|
|
||||||
|
const REFRESH_DELAY: Duration = Duration::from_millis(2000);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct Pipeline {
|
||||||
|
id: u64,
|
||||||
|
status: String
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PipelineStatus {
|
||||||
|
Created,
|
||||||
|
WaitingForResource,
|
||||||
|
Preparing,
|
||||||
|
Pending,
|
||||||
|
Running,
|
||||||
|
Success,
|
||||||
|
Failed,
|
||||||
|
Canceled,
|
||||||
|
Skipped,
|
||||||
|
Manual,
|
||||||
|
Scheduled
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PipelineStatus {
|
||||||
|
fn from(s: &str) -> Option<PipelineStatus> {
|
||||||
|
return match s {
|
||||||
|
"created" => Some(PipelineStatus::Created),
|
||||||
|
"waiting_for_resource" => Some(PipelineStatus::WaitingForResource),
|
||||||
|
"preparing" => Some(PipelineStatus::Preparing),
|
||||||
|
"pending" => Some(PipelineStatus::Pending),
|
||||||
|
"running" => Some(PipelineStatus::Running),
|
||||||
|
"success" => Some(PipelineStatus::Success),
|
||||||
|
"failed" => Some(PipelineStatus::Failed),
|
||||||
|
"canceled" => Some(PipelineStatus::Canceled),
|
||||||
|
"skipped" => Some(PipelineStatus::Skipped),
|
||||||
|
"manual" => Some(PipelineStatus::Manual),
|
||||||
|
"scheduled" => Some(PipelineStatus::Scheduled),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color(&self) -> (Color, bool) {
|
||||||
|
return match self {
|
||||||
|
PipelineStatus::Pending => (Color::YELLOW, false),
|
||||||
|
PipelineStatus::Running => (Color::AMBER, true),
|
||||||
|
PipelineStatus::Success => (Color::GREEN, false),
|
||||||
|
_ => (Color::OFF, false)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn refresh_on_timer(client: Gitlab, project: Project) {
|
||||||
|
let mut output = Output::guess().unwrap();
|
||||||
|
loop {
|
||||||
|
let pipeline = get_latest_pipelines(&client, &project);
|
||||||
|
if pipeline.is_none() { return; }
|
||||||
|
let c = PipelineStatus::from(pipeline.unwrap().status.as_str())
|
||||||
|
.unwrap();
|
||||||
|
output.set_button(Button::GridButton { x: project.abs_x, y: project.abs_y },
|
||||||
|
c.get_color().0,
|
||||||
|
if c.get_color().1 { DoubleBufferingBehavior::None }
|
||||||
|
else { DoubleBufferingBehavior::Copy }).unwrap();
|
||||||
|
sleep(REFRESH_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_from_config(config: Config) -> Vec<JoinHandle<()>> {
|
||||||
|
info!("Loading gitlab manager from configuration.");
|
||||||
|
let mut threads = vec![];
|
||||||
|
for p in config.projects.iter() {
|
||||||
|
let client = Gitlab::new(config.host.as_str(), config.key.as_str()).unwrap();
|
||||||
|
let project = p.clone();
|
||||||
|
threads.push(thread::spawn(move|| refresh_on_timer(client, project)));
|
||||||
|
}
|
||||||
|
return threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_latest_pipelines(client: &Gitlab, project: &Project) -> Option<Pipeline> {
|
||||||
|
let endpoint = Pipelines::builder()
|
||||||
|
.project(project.name.as_str()).ref_(project.ref_.as_str())
|
||||||
|
.order_by(PipelineOrderBy::UpdatedAt).build().unwrap();
|
||||||
|
let pipelines: Vec<Pipeline> = endpoint.query(client).unwrap_or(vec![]);
|
||||||
|
return if pipelines.is_empty() { None } else {
|
||||||
|
Some(pipelines.first().unwrap().to_owned()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retry_pipeline(host: &str, token: &str, project: &Project) {
|
||||||
|
let client = Gitlab::new(host, token).unwrap();
|
||||||
|
let pipeline = get_latest_pipelines(&client, &project);
|
||||||
|
if pipeline.is_none() { return; }
|
||||||
|
let _res: Pipeline;
|
||||||
|
if pipeline.clone().unwrap().status == "success" {
|
||||||
|
let endpoint = gitlab::api::projects::pipelines::CreatePipeline::builder()
|
||||||
|
.project(project.name.clone())
|
||||||
|
.ref_(project.ref_.clone())
|
||||||
|
.build().unwrap();
|
||||||
|
_res = endpoint.query(&client).unwrap();
|
||||||
|
} else {
|
||||||
|
let endpoint = gitlab::api::projects::pipelines::RetryPipeline::builder()
|
||||||
|
.project(project.name.clone())
|
||||||
|
.pipeline(pipeline.unwrap().id)
|
||||||
|
.build().unwrap();
|
||||||
|
_res = endpoint.query(&client).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
56
src/launchpad_controller.rs
Normal file
56
src/launchpad_controller.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use launchy::{OutputDevice, InputDevice, launchpad_mini::{Output, Input, Buffer, Button, Color, DoubleBuffering, DoubleBufferingBehavior, Message}, MsgPollingWrapper};
|
||||||
|
|
||||||
|
use crate::{config_manager::Config, gitlab_controller::retry_pipeline};
|
||||||
|
|
||||||
|
const RESTART_BUTTON: Button = Button::GridButton { x: 8, y: 0 };
|
||||||
|
|
||||||
|
fn engage_restart(output: &mut Output) {
|
||||||
|
output.set_button(RESTART_BUTTON, Color::DIM_GREEN, DoubleBufferingBehavior::Copy).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disengage_restart(output: &mut Output) {
|
||||||
|
output.set_button(RESTART_BUTTON, Color::OFF, DoubleBufferingBehavior::Copy).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn running_thread(config: Config) {
|
||||||
|
let mut output = Output::guess().unwrap();
|
||||||
|
let input = Input::guess_polling().unwrap();
|
||||||
|
let mut restart = false;
|
||||||
|
for msg in input.iter() {
|
||||||
|
if let Message::Release { button } = msg {
|
||||||
|
if button == RESTART_BUTTON {
|
||||||
|
if !restart {
|
||||||
|
restart = true;
|
||||||
|
engage_restart(&mut output);
|
||||||
|
} else {
|
||||||
|
restart = false;
|
||||||
|
disengage_restart(&mut output);
|
||||||
|
}
|
||||||
|
} else if button.abs_x() != 8 {
|
||||||
|
let project = config.projects.iter()
|
||||||
|
.find(|p| p.abs_x == button.abs_x() && p.abs_y + 1 == button.abs_y());
|
||||||
|
if project.is_none() { continue; }
|
||||||
|
let project = project.unwrap();
|
||||||
|
if restart {
|
||||||
|
restart = false;
|
||||||
|
retry_pipeline(config.host.as_str(), config.key.as_str(), project);
|
||||||
|
disengage_restart(&mut output);
|
||||||
|
} else {
|
||||||
|
open::that_in_background(format!("https://{}/{}", config.host, project.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(config: Config) {
|
||||||
|
let mut output = launchy::launchpad_mini::Output::guess().unwrap();
|
||||||
|
output.reset().unwrap();
|
||||||
|
output.control_double_buffering(DoubleBuffering {
|
||||||
|
copy: false,
|
||||||
|
flash: true,
|
||||||
|
edited_buffer: Buffer::A,
|
||||||
|
displayed_buffer: Buffer::A
|
||||||
|
}).unwrap();
|
||||||
|
std::thread::spawn(move || running_thread(config));
|
||||||
|
}
|
33
src/main.rs
Normal file
33
src/main.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use log::error;
|
||||||
|
|
||||||
|
use crate::config_manager::Config;
|
||||||
|
use crate::config_manager::Project;
|
||||||
|
|
||||||
|
mod config_manager;
|
||||||
|
mod launchpad_controller;
|
||||||
|
mod gitlab_controller;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
let conf = config_manager::get_config();
|
||||||
|
if conf.is_none() {
|
||||||
|
config_manager::save_config(&Config {
|
||||||
|
host: "gitlab-host".to_string(),
|
||||||
|
key: "your-gitlab-key".to_string(),
|
||||||
|
projects: vec![Project {
|
||||||
|
name: "example".to_string(),
|
||||||
|
ref_: "master".to_string(),
|
||||||
|
abs_x: 0,
|
||||||
|
abs_y: 0
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
error!("Please configure the application with the configuration file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let conf = conf.unwrap();
|
||||||
|
launchpad_controller::init(conf.clone());
|
||||||
|
let threads = gitlab_controller::load_from_config(conf);
|
||||||
|
for t in threads {
|
||||||
|
t.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user