diff options
| author | n1 <hrdina.pavel@gmail.com> | 2024-06-25 14:35:33 +0200 |
|---|---|---|
| committer | n1 <hrdina.pavel@gmail.com> | 2024-06-25 14:35:33 +0200 |
| commit | 7e2b732e0466cdf07b74f2b9cdfafd360838b625 (patch) | |
| tree | 3b8c7e32242a62ded088f2f807dcbaa9f3de83c3 | |
| parent | 35752af64b1adba7108d8004d661eef09758677f (diff) | |
Added: retrying.0.1.4
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | README.md | 3 | ||||
| -rw-r--r-- | src/app.rs | 36 | ||||
| -rw-r--r-- | src/args.rs | 9 | ||||
| -rw-r--r-- | src/config.rs | 15 | ||||
| -rw-r--r-- | src/job.rs | 39 | ||||
| -rw-r--r-- | src/pipeline.rs | 56 | ||||
| -rw-r--r-- | src/ui.rs | 10 |
9 files changed, 151 insertions, 21 deletions
@@ -420,7 +420,7 @@ dependencies = [ [[package]] name = "glp" -version = "0.1.3" +version = "0.1.4" dependencies = [ "chrono", "clap", @@ -1,6 +1,6 @@ [package] name = "glp" -version = "0.1.3" +version = "0.1.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -34,6 +34,9 @@ $ echo "456" > .glp; glp --trace=789 | less # fetches traces for all failed job ## Changelog +### 0.1.4 +- retrying added (-r) - possibility to retry a job or a whole pipeline + ### 0.1.3 - spinner added - tracing added (--trace) - possibility to fetch failed jobs traces (logs) @@ -27,11 +27,12 @@ impl App { .trace_relatively(trace.chars().skip(1).collect::<String>().parse::<u8>()?) .await; } else { - result = self.trace_by_id(trace.to_string()).await; + result = self + .trace_by_id(trace.parse::<usize>().expect("Cannot parse pipeline ID.")) + .await; } - } else if false { - // Other future cases. - unimplemented!() + } else if let Some(retry) = &self.config.retry { + result = self.retry(retry).await; } else { // 2. Default behavior result = self.run_without_arguments().await; @@ -78,7 +79,7 @@ impl App { Ok(()) } - async fn trace_by_id(&self, pipeline_id: String) -> Result<(), Box<dyn Error + Send + Sync>> { + async fn trace_by_id(&self, pipeline_id: usize) -> Result<(), Box<dyn Error + Send + Sync>> { let stages = Pipeline::fetch_stages(self.config.clone(), pipeline_id).await?; self.print_job_traces(self.fetch_failed_jobs(stages).await?); @@ -132,4 +133,29 @@ impl App { self.ui.print_job_trace(job); } } + + async fn retry( + &self, + retry: &(usize, Option<String>), + ) -> Result<(), Box<dyn Error + Send + Sync>> { + if let Some(job_name) = &retry.1 { + let jobs = Pipeline::fetch_jobs(self.config.clone(), retry.0).await?; + let job = jobs.members().find(|i| i["name"].to_string() == *job_name); + let job_id; + + if let Some(job) = job { + job_id = job["id"].as_usize().expect("Cannot parse job ID."); + } else { + return Err(From::from(JobError::NotFound)); + } + + Job::retry(self.config.clone(), job_id).await?; + self.ui.print_job_retry(retry.0, job_name); + } else { + Pipeline::retry(self.config.clone(), retry.0).await?; + self.ui.print_pipeline_retry(retry.0); + } + + Ok(()) + } } diff --git a/src/args.rs b/src/args.rs index b06acd6..3922d2f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -40,5 +40,14 @@ pub fn parse() -> ArgMatches { .conflicts_with("limit") .help("Fetches traces (logs) from all failed pipeline jobs. Specify pipeline ID or it's relative number with '-' prefix."), ) + .arg( + Arg::new("retry") + .short('r') + .long("retry") + .action(ArgAction::Append) + .value_parser(value_parser!(String)) + .num_args(1..=2) + .help("Retry a job or a whole pipleline - arguments: <pipeline_id> or <pipeline_id> <job_name>.") + ) .get_matches() } diff --git a/src/config.rs b/src/config.rs index aa7f03f..64c60cf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,6 +8,7 @@ pub struct Config { pub limit: u8, pub finished: bool, pub trace: Option<String>, + pub retry: Option<(usize, Option<String>)>, _args: ArgMatches, } @@ -18,7 +19,9 @@ impl Config { Some(id) => id.to_owned(), None => fs::read_to_string(".glp") .await - .expect("No project ID (no parameter nor .glp file."), + .expect("No project ID (no parameter nor .glp file.") + .trim() + .to_owned(), }; let finished = args.get_one::<bool>("finished").unwrap().to_owned(); @@ -26,6 +29,15 @@ impl Config { .expect("No Gitlab private token found - set GLP_PRIVATE_TOKEN environment variable."); let limit = args.get_one::<u8>("limit").unwrap().to_owned(); let trace = args.get_one::<String>("trace").cloned(); + let retry = args + .get_many::<String>("retry") + .map(|mut vals| match vals.len() { + 1 => (vals.nth(0).unwrap().parse::<usize>().unwrap(), None), + _ => ( + vals.next().unwrap().parse::<usize>().unwrap(), + Some(vals.next().unwrap().to_owned()), + ), + }); Self { project_id, @@ -33,6 +45,7 @@ impl Config { limit, finished, trace, + retry, _args: args, } } @@ -9,12 +9,14 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum JobError { - // #[error("Job not found.")] - // NotFound, + #[error("Job not found.")] + NotFound, #[error("Fetching error: {0:?}")] FetchingError(#[source] reqwest::Error), - #[error("Parsing error: {0:?}.")] - ParsingError(#[source] reqwest::Error), + #[error("Parsing error.")] + ParsingError, + #[error("Retrying error.")] + RetryingError, } #[derive(Debug, Clone, Default)] @@ -74,9 +76,36 @@ impl Job { .map_err(JobError::FetchingError)? .text() .await - .map_err(JobError::ParsingError)?, + .map_err(JobError::FetchingError)?, ); Ok(()) } + + pub async fn retry(config: Arc<Config>, job_id: usize) -> Result<(), JobError> { + let client = reqwest::Client::new(); + let job = json::parse( + client + .post(format!( + "https://gitlab.com/api/v4/projects/{}/jobs/{}/retry", + &config.project_id, job_id + )) + .header("PRIVATE-TOKEN", &config.private_token) + .send() + .await + .map_err(|_| JobError::RetryingError)? + .text() + .await + .map_err(|_| JobError::RetryingError)? + .as_str(), + ) + .map_err(|_| JobError::ParsingError)?; + + // Check for errors. + if job.has_key("error") { + return Err(JobError::NotFound); + } + + Ok(()) + } } diff --git a/src/pipeline.rs b/src/pipeline.rs index d4a95f6..4dffbdf 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -28,6 +28,8 @@ pub enum PipelineError { FetchingError, #[error("Parsing error.")] ParsingError, + #[error("Retrying error.")] + RetryingError, } /// Represents Gitlab pipeline. @@ -98,11 +100,10 @@ impl Pipeline { .map_err(|_| PipelineError::ParsingError) } - // Fetch stages + jobs for the given pipeline. - pub async fn fetch_stages( + pub async fn fetch_jobs( config: Arc<Config>, - pipeline_id: String, - ) -> Result<Vec<Stage>, PipelineError> { + pipeline_id: usize, + ) -> Result<JsonValue, PipelineError> { // Fetch data. let client = reqwest::Client::new(); let jobs = json::parse( @@ -127,9 +128,18 @@ impl Pipeline { return Err(PipelineError::NotFound); } + Ok(jobs) + } + + // Fetch stages + jobs for the given pipeline. + pub async fn fetch_stages( + config: Arc<Config>, + pipeline_id: usize, + ) -> Result<Vec<Stage>, PipelineError> { // Process JSON response. // Construct jobs. let mut stages: HashMap<String, Vec<Job>> = HashMap::new(); + let jobs = Pipeline::fetch_jobs(config.clone(), pipeline_id).await?; for j in 0..jobs.len() { let job = &jobs[j]; @@ -186,11 +196,11 @@ impl Pipeline { .collect::<Vec<&Job>>(); b_jobs.sort_by_key(|j| j.started_at.clone()); - let a_started_at = match a_jobs.get(0) { + let a_started_at = match a_jobs.first() { Some(j) => j.started_at.clone(), _ => None, }; - let b_started_at = match b_jobs.get(0) { + let b_started_at = match b_jobs.first() { Some(j) => j.started_at.clone(), _ => None, }; @@ -232,8 +242,11 @@ impl Pipeline { let xconfig = config.clone(); tasks.push(tokio::spawn(async move { - let pip_stages = - Self::fetch_stages(xconfig.clone(), pip["id"].clone().to_string()).await?; + let pip_stages = Self::fetch_stages( + xconfig.clone(), + pip["id"].as_usize().expect("Cannot parse pipeline ID."), + ) + .await?; let mut pip = Pipeline { id: Label(pip["id"].as_usize().unwrap().to_string()), @@ -342,4 +355,31 @@ impl Pipeline { None } + + pub async fn retry(config: Arc<Config>, pipeline_id: usize) -> Result<(), PipelineError> { + let client = reqwest::Client::new(); + let pipeline = json::parse( + client + .post(format!( + "https://gitlab.com/api/v4/projects/{}/pipelines/{}/retry", + &config.project_id, pipeline_id + )) + .header("PRIVATE-TOKEN", &config.private_token) + .send() + .await + .map_err(|_| PipelineError::RetryingError)? + .text() + .await + .map_err(|_| PipelineError::RetryingError)? + .as_str(), + ) + .map_err(|_| PipelineError::ParsingError)?; + + // Check for errors. + if pipeline.has_key("error") { + return Err(PipelineError::NotFound); + } + + Ok(()) + } } @@ -69,4 +69,14 @@ impl Ui { eprintln!("Original error: {}", source); } } + + pub fn print_pipeline_retry(&self, pipeline_id: usize) { + println!("Pipeline {} is gonna run once again.", pipeline_id); + } + pub fn print_job_retry(&self, pipeline_id: usize, job: &String) { + println!( + "Job {} from pipeline {} is gonna run once again.", + job, pipeline_id + ); + } } |