From af01c2ee99b73bdcc8357a947d0375f27ae332aa Mon Sep 17 00:00:00 2001 From: peter-formlogic Date: Thu, 24 Feb 2022 17:51:08 +1030 Subject: [PATCH] Version 15 --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- README.md | 38 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 +++ src/runner.rs | 6 +++++- src/step/mod.rs | 27 ++++++++++++++++++++++++--- src/submitter.rs | 33 +++++++++++++++++++++++++++++---- src/yaml.rs | 2 ++ 8 files changed, 107 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3777366..7a71de5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -663,10 +663,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] -name = "jmespatch" +name = "jmespath" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acf91a732ade34d8eda2dee9500a051833f14f0d3d10d77c149845d6ac6a5f0" +checksum = "017f8f53dd3b8ada762acb1f850da2a742d0ef3f921c60849a644380de1d683a" dependencies = [ "lazy_static", "serde", @@ -725,7 +725,7 @@ dependencies = [ [[package]] name = "lorikeet" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "atty", @@ -736,7 +736,7 @@ dependencies = [ "env_logger", "futures", "hostname", - "jmespatch", + "jmespath", "lazy_static", "libc", "linked-hash-map", diff --git a/Cargo.toml b/Cargo.toml index 8f81295..af48232 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lorikeet" -version = "0.14.0" +version = "0.15.0" authors = ["cetra3 "] license = "MIT/Apache-2.0" description = "a parallel test runner for devops" @@ -27,7 +27,7 @@ chashmap = "2.2.2" hostname = "0.3.1" sys-info = "0.8.0" openssl-probe = "0.1.2" -jmespatch = "0.3.0" +jmespath = "0.3.0" anyhow = "1.0.38" serde_json = "1.0" quick-xml = "0.21.0" diff --git a/README.md b/README.md index 4eac1a9..1966b60 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ A Parallel test runner for DevOps. + +## Download + +Download the latest binary for linux or osx from here: [https://github.com/cetra3/lorikeet/releases](https://github.com/cetra3/lorikeet/releases) + ## Overview Lorikeet is a command line tool and a rust library to run tests for smoke testing and integration testing. Lorikeet currently supports bash commands and simple http requests along with system information (ram, cpu). @@ -57,6 +62,20 @@ The name comes from the [Rainbow Lorikeet](https://en.wikipedia.org/wiki/Rainbow They are also very noisy birds. +## Changes in `0.15.0` + +* Add in a new option to run a step on failure: + +```yaml +on_fail_example: + value: true + matches: false + on_fail: + bash: notify-send "Lorikeet Failed!" +``` + +* We now have [releases](https://github.com/cetra3/lorikeet/releases) being generated via github actions + ## Changes in `0.14.0` * Breaking Change: Add in a default timeout (`timeout_ms`) for http requests to `30000` milliseconds (30 seconds), This default can be changed as per http options below. @@ -174,6 +193,8 @@ Or clone and build this repo: cargo build --release ``` +Alternatively, you can download prebuilt from the [releases](https://github.com/cetra3/lorikeet/releases) page + ## Usage Command line usage is given by `lorikeet -h`: @@ -545,6 +566,23 @@ there_are_four_lights: less_than: 5 ``` +### On Fail + +You can run another step when a step fails. This `on_fail` can be any of the step types: bash, http, system, step and value + +```yaml +on_fail_example: + value: true + matches: false + on_fail: + bash: notify-send "Lorikeet Failed!" +``` + +The output or error of this on fail step will be included in the standard output. + +If you are using retry counts, then the `on_fail` step will execute each time the step fail. + + ### Dependencies By default tests are run in parallel and submitted to a thread pool for execution. If a step has a dependency it won't be run until the dependent step has been finished. If there are no dependencies to a step then it will run as soon as a thread is free. If you don't specify any dependencies there is no guaranteed ordering to execution. diff --git a/src/main.rs b/src/main.rs index f68cee5..c5b7681 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,6 +150,8 @@ fn step_from_error(err: Error, quiet: bool, colours: bool) -> StepResult { output: None, error: Some(err.to_string()), duration: Duration::default(), + on_fail_output: None, + on_fail_error: None, }; let result: StepResult = Step { @@ -157,6 +159,7 @@ fn step_from_error(err: Error, quiet: bool, colours: bool) -> StepResult { run: RunType::Value(String::new()), do_output: true, expect: ExpectType::Anything, + on_fail: None, description: Some( "This step is shown if there was an error when reading, parsing or running steps" .into(), diff --git a/src/runner.rs b/src/runner.rs index 9306f3d..c35990d 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -21,6 +21,7 @@ pub struct StepRunner { pub name: String, pub index: usize, pub run: RunType, + pub on_fail: Option, pub expect: ExpectType, pub retry: RetryPolicy, pub filters: Vec, @@ -35,7 +36,7 @@ impl StepRunner { tokio::spawn(async move { let outcome = self .run - .execute(self.expect, self.filters, self.retry) + .execute(self.expect, self.filters, self.retry, self.on_fail) .await; if let Some(ref output) = outcome.output { @@ -92,6 +93,7 @@ pub fn run_steps(steps: Vec) -> Result { for (i, step) in step_map.iter() { let future = StepRunner { run: step.run.clone(), + on_fail: step.on_fail.clone(), expect: step.expect.clone(), retry: step.retry, filters: step.filters.clone(), @@ -160,6 +162,8 @@ pub fn run_steps(steps: Vec) -> Result { output: Some("".into()), error: Some("Dependency Not Met".into()), duration: Duration::from_secs(0), + on_fail_output: None, + on_fail_error: None, }); if tx_steps.send(step).is_err() { diff --git a/src/step/mod.rs b/src/step/mod.rs index 31f2a18..4adf441 100644 --- a/src/step/mod.rs +++ b/src/step/mod.rs @@ -18,7 +18,7 @@ use tera::{Context, Tera}; use std::{borrow::Cow, collections::HashMap}; -use jmespatch::{self, Variable}; +use jmespath::{self, Variable}; use lazy_static::lazy_static; use log::debug; @@ -29,6 +29,8 @@ use chashmap::CHashMap; pub struct Outcome { pub output: Option, pub error: Option, + pub on_fail_output: Option, + pub on_fail_error: Option, pub duration: Duration, } @@ -44,6 +46,7 @@ pub struct Step { pub name: String, pub description: Option, pub run: RunType, + pub on_fail: Option, pub filters: Vec, pub expect: ExpectType, pub do_output: bool, @@ -91,6 +94,7 @@ impl RunType { expect: ExpectType, filters: Vec, retry: RetryPolicy, + on_fail: Option, ) -> Outcome { let start = Instant::now(); @@ -104,6 +108,8 @@ impl RunType { let mut output = String::new(); let mut error = String::new(); + let mut on_fail_output = None; + let mut on_fail_error = None; let mut successful = false; 'retry: for count in 0..try_count { @@ -120,6 +126,8 @@ impl RunType { output = String::new(); error = String::new(); + on_fail_output = None; + on_fail_error = None; //Run the runner first match self.run().await { @@ -158,6 +166,17 @@ impl RunType { break 'retry; } } + + if !successful { + if let Some(ref on_fail_runner) = on_fail { + match on_fail_runner.run().await { + Ok(val) => { + on_fail_output = Some(val); + } + Err(val) => on_fail_error = Some(val), + } + } + } } let output_opt = match output.as_ref() { @@ -175,6 +194,8 @@ impl RunType { output: output_opt, error: error_opt, duration: start.elapsed(), + on_fail_output, + on_fail_error, } } @@ -241,10 +262,10 @@ impl FilterType { match *self { FilterType::NoOutput => Ok(String::from("")), FilterType::JmesPath(ref jmes) => { - let expr = jmespatch::compile(jmes) + let expr = jmespath::compile(jmes) .map_err(|err| format!("Could not compile jmespath:{}", err))?; - let data = jmespatch::Variable::from_json(val) + let data = Variable::from_json(val) .map_err(|err| format!("Could not format as json:{}", err))?; let result = expr diff --git a/src/submitter.rs b/src/submitter.rs index c9c403b..c7fc99c 100644 --- a/src/submitter.rs +++ b/src/submitter.rs @@ -14,6 +14,8 @@ pub struct StepResult { pub pass: bool, pub output: String, pub error: Option, + pub on_fail_output: Option, + pub on_fail_error: Option, pub duration: f32, } @@ -175,11 +177,20 @@ impl StepResult { message.push_str(&format!(" output: {}\n", self.output)); } } - if let Some(ref error) = self.error { message.push_str(&format!(" error: {}\n", error)); } + if let Some(ref output) = self.on_fail_output { + if !output.trim().is_empty() { + message.push_str(&format!(" on_fail_output: {}\n", output)); + } + } + + if let Some(ref error) = self.on_fail_error { + message.push_str(&format!(" on_fail_error: {}\n", error)); + } + message.push_str(&format!(" duration: {}ms\n", self.duration)); if *colours { @@ -203,16 +214,28 @@ impl From for StepResult { let name = step.name; let description = step.description; - let (pass, output, error) = match step.outcome { + let (pass, output, error, on_fail_output, on_fail_error) = match step.outcome { Some(outcome) => { let output = match step.do_output { true => outcome.output.unwrap_or_default(), false => String::new(), }; - (outcome.error.is_none(), output, outcome.error) + ( + outcome.error.is_none(), + output, + outcome.error, + outcome.on_fail_output, + outcome.on_fail_error, + ) } - None => (false, String::new(), Some(String::from("Not finished"))), + None => ( + false, + String::new(), + Some(String::from("Not finished")), + None, + None, + ), }; StepResult { @@ -221,6 +244,8 @@ impl From for StepResult { description, pass, output, + on_fail_output, + on_fail_error, error, } } diff --git a/src/yaml.rs b/src/yaml.rs index 9f239a2..4c7ccb8 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -40,6 +40,7 @@ struct StepYaml { retry_count: Option, retry_delay_ms: Option, delay_ms: Option, + on_fail: Option, require: Option, required_by: Option, } @@ -138,6 +139,7 @@ pub fn get_steps_raw(yaml_contents: &str, context: &T) -> Result