use colored::*;
use serde_yaml::Value;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::process::exit;
#[derive(Debug)]
pub struct Pipeline {
pipeline_type: PipelineType,
stages: Vec<Stage>,
jobs: Vec<Job>,
script: Script,
variables: HashMap<String, String>,
}
impl Pipeline {
pub fn new(pipeline_file: &str) -> Pipeline {
let pipeline_data = get_pipeline_data(pipeline_file);
match get_pipeline_type(pipeline_data.clone()) {
PipelineType::Script => {
let script_data = get_script(pipeline_data.clone()).unwrap();
let new_pipeline = Pipeline {
pipeline_type: PipelineType::Script,
stages: vec![], jobs: vec![], script: Script::new(script_data), variables: get_variables(pipeline_data),
};
return new_pipeline;
}
PipelineType::Jobs => {
let mut pipeline_jobs: Vec<Job> = vec![];
let pipeline_variables = get_variables(pipeline_data.clone());
let jobs_data = get_jobs(pipeline_data.clone()).unwrap();
let mut a = 0;
loop {
if jobs_data.get(a).is_some() {
let job_name = jobs_data.get(a).unwrap().as_str().unwrap().to_owned();
let job_script = if get_job_or_stage(
pipeline_data.clone(),
job_name.clone(),
)
.is_some()
{
get_job_or_stage(pipeline_data.clone(), job_name.clone()).unwrap()
} else {
break;
};
pipeline_jobs.push(Job::new(
job_name.clone(),
job_script.clone(),
pipeline_variables.clone(),
));
} else {
break;
}
a += 1;
}
let new_pipeline = Pipeline {
pipeline_type: PipelineType::Jobs,
stages: vec![], jobs: pipeline_jobs,
script: Script { data: vec![] }, variables: pipeline_variables,
};
return new_pipeline;
}
PipelineType::Stages => {
let mut pipeline_stages: Vec<Stage> = vec![];
let pipeline_variables = get_variables(pipeline_data.clone());
let stages_data = get_stages(pipeline_data.clone()).unwrap();
let mut a = 0;
loop {
if stages_data.get(a).is_some() {
let stage_name = stages_data.get(a).unwrap().as_str().unwrap().to_owned();
pipeline_stages.push(Stage::new(
stage_name,
pipeline_data.clone(),
pipeline_variables.clone(),
));
} else {
break;
}
a += 1;
}
let new_pipeline = Pipeline {
pipeline_type: PipelineType::Stages,
stages: pipeline_stages,
jobs: vec![], script: Script { data: vec![] }, variables: pipeline_variables,
};
return new_pipeline;
}
PipelineType::Null => panic!("Error in pipeline format!"),
}
}
pub fn exec_pipeline(&self) -> &str {
match self.pipeline_type {
PipelineType::Script => {
println!("{}", "###############################".green());
self.script.exec(self.variables.clone());
println!("{}", "###############################\n".green());
return "Le script s'est éxécuté";
}
PipelineType::Jobs => {
for a in 0..self.jobs.len() {
println!("{}", "###############################".green());
self.jobs[a].exec();
println!("{}", "###############################\n".green());
}
return "Le script (job) s'est éxécuté";
}
PipelineType::Stages => {
for a in 0..self.stages.len() {
println!("{}", "###############################".green());
println!(
">>> Execution du stage '{}'",
self.stages[a].get_name().to_string().blue()
);
self.stages[a].exec();
println!("{}", "###############################\n".green());
}
return "Le script (stage) s'est éxécuté";
}
PipelineType::Null => panic!("Error in pipeline format!"),
}
}
}
#[derive(Debug)]
pub struct Script {
data: Vec<String>,
}
impl Script {
pub fn new(script_data: Value) -> Script {
let mut a = 0;
let mut commands: Vec<String> = vec![];
loop {
let next_command = get_command(script_data.clone(), a);
if next_command.is_some() {
commands.push(next_command.unwrap());
} else {
break;
}
a += 1;
}
let new_script = Script { data: commands };
return new_script;
}
pub fn exec(&self, variables: HashMap<String, String>) -> bool {
let mut script_string = "".to_owned(); script_string += "set -ebuxo pipefail\n"; script_string += "exec 2>&1\n";
for (key, value) in variables {
script_string += "export ";
script_string += key.as_str();
script_string += "=";
script_string += value.as_str();
script_string += "\n";
}
for command in self.data.iter() {
script_string += "\n";
script_string += command;
}
let res = exec_command(script_string);
if res.0 == true {
let res1 = res.1.clone();
let parts = res1.split("\n");
for part in parts {
if part.chars().nth(0).is_some() {
if part.chars().nth(0).unwrap() == '+' {
println!("{}", part.blue())
} else {
println!("{}", part)
}
}
}
return true;
} else {
let res2 = res.2.clone();
let parts = res2.split("\n");
for part in parts {
if part.chars().nth(0).is_some() {
if part.chars().nth(0).unwrap() == '+' {
println!("{}", part.on_red().cyan())
} else {
println!("{}", part.on_red())
}
}
}
return false;
}
}
}
#[derive(Debug)]
pub struct Job {
name: String,
scripts: Script,
variables: HashMap<String, String>,
}
impl Job {
pub fn new(
job_name: String,
job_data: Value,
herited_variables: HashMap<String, String>,
) -> Job {
let mut all_vars = herited_variables;
for (key, value) in get_variables(job_data.clone()).iter() {
all_vars.insert(key.to_owned(), value.to_owned());
}
return Job {
name: job_name,
scripts: Script::new(job_data.get("script").unwrap().to_owned()),
variables: all_vars,
};
}
pub fn exec(&self) {
println!("> Execution du job '{}'", self.name.to_owned().green());
self.scripts.exec(self.variables.clone());
}
}
#[derive(Debug)]
pub struct Stage {
name: String,
jobs: Vec<Job>,
}
impl Stage {
pub fn new(
stage_name: String,
pipeline_data: Value,
herited_variables: HashMap<String, String>,
) -> Stage {
let mut stage_jobs: Vec<Job> = vec![];
let stage_data = if get_job_or_stage(pipeline_data.clone(), stage_name.clone()).is_some() {
get_job_or_stage(pipeline_data.clone(), stage_name.clone()).unwrap()
} else {
panic!(
"The stage '{}' doesn't exists in pipeline",
stage_name.as_str().red()
);
};
let mut all_vars = herited_variables;
for (key, value) in get_variables(stage_data.clone()).iter() {
all_vars.insert(key.to_owned(), value.to_owned());
}
let jobs_data = if get_jobs(stage_data.clone()).is_some() {
get_jobs(stage_data.clone()).unwrap()
} else {
panic!("Jobs not found for stage '{}", stage_name.as_str().red())
};
let mut a = 0;
loop {
if jobs_data.get(a).is_some() {
let job_name = jobs_data.get(a).unwrap().as_str().unwrap().to_owned();
let job_script =
if get_job_or_stage(pipeline_data.clone(), job_name.clone()).is_some() {
get_job_or_stage(pipeline_data.clone(), job_name.clone()).unwrap()
} else {
a += 1;
continue;
};
stage_jobs.push(Job::new(
job_name.clone(),
job_script.clone(),
all_vars.clone(), ));
} else {
break;
}
a += 1;
}
return Stage {
name: stage_name,
jobs: stage_jobs,
};
}
pub fn get_name(&self) -> String {
return self.name.clone();
}
pub fn exec(&self) {
for job in self.jobs.iter() {
job.exec();
}
}
}
pub fn get_variables<'t>(data: Value) -> HashMap<String, String> {
let variables = data.get("variables");
if variables.is_some() {
if variables.unwrap().is_mapping() {
let mut map = HashMap::new();
for var in variables.unwrap().to_owned().as_mapping().unwrap().iter() {
let key = var.0.as_str().unwrap().to_owned();
let value = var.1.as_str().unwrap().to_owned();
map.insert(key, value);
}
return map;
} else {
println!(
"{}",
"Variables data is incorrect! (not a dictionnay)".red()
);
exit(1)
}
} else {
return HashMap::new();
}
}
pub fn get_script<'t>(data: Value) -> Option<Value> {
let script = data.get("script");
match script {
Some(_) => return Option::Some(script.unwrap().to_owned()),
None => return Option::None,
}
}
pub fn get_command<'t>(script: Value, i: usize) -> Option<std::string::String> {
let command = script.get(i);
if command.is_some() {
return Option::Some(command.unwrap().as_str().unwrap().to_owned());
} else {
return Option::None;
}
}
pub fn get_jobs<'t>(data: Value) -> Option<Value> {
let jobs = data.get("jobs");
match jobs {
Some(_) => return Option::Some(jobs.unwrap().to_owned()),
None => return Option::None,
}
}
pub fn get_stages<'t>(pipeline: Value) -> Option<Value> {
let stages = pipeline.get("stages");
match stages {
Some(_) => return Option::Some(stages.unwrap().to_owned()),
None => return Option::None,
}
}
pub fn get_job_or_stage<'t>(pipeline: Value, job_or_stage_name: String) -> Option<Value> {
let stages = pipeline.get(job_or_stage_name);
match stages {
Some(_) => return Option::Some(stages.unwrap().to_owned()),
None => return Option::None,
}
}
pub fn get_pipeline_data<'t>(file: &str) -> Value {
let content = fs::read_to_string(file).expect("Could not read the pipeline file");
let pipeline: Value = serde_yaml::from_str(&content).unwrap();
return pipeline;
}
pub fn exec_command(
command: std::string::String,
) -> (bool, std::string::String, std::string::String) {
let command_args = OsString::from(command.as_str());
let mut command_base = std::process::Command::new("bash");
let command_executed = command_base
.arg("-c")
.arg(command_args)
.output()
.expect("Error in command: '{command}'");
let command_success = command_executed.status.success();
let command_stdout = std::str::from_utf8(&command_executed.stdout[..])
.unwrap()
.to_owned();
let command_stderr = std::str::from_utf8(&command_executed.stdout[..])
.unwrap()
.to_owned();
return (command_success, command_stdout, command_stderr);
}
#[derive(Debug)]
pub enum PipelineType {
Null,
Stages,
Jobs,
Script,
}
pub fn get_pipeline_type(pipeline_data: Value) -> PipelineType {
let stages = get_stages(pipeline_data.clone());
let jobs = get_jobs(pipeline_data.clone());
let script = get_script(pipeline_data.clone());
let pipeline_type_found: PipelineType;
if stages.is_some() {
pipeline_type_found = PipelineType::Stages;
} else if jobs.is_some() {
pipeline_type_found = PipelineType::Jobs;
} else if script.is_some() {
pipeline_type_found = PipelineType::Script;
} else {
pipeline_type_found = PipelineType::Null
}
return pipeline_type_found;
}
pub fn main_exec(myfile: &str) {
println!("this file will be used: {}", myfile);
let main_pipeline = Pipeline::new(myfile);
println!("{:#?}", main_pipeline);
println!("{}", main_pipeline.exec_pipeline());
}
pub fn main_start() {
println!("Start: Not implemented yet!")
}
pub fn main_stop() {
println!("Stop: Not implemented yet!")
}
pub fn main_init() {
println!("Init: Not implemented yet!")
}
pub fn main_clean() {
println!("Clean: Not implemented yet!")
}