From 675ad0c0f73c91d4751f2ff97040a5a88a90b8b0 Mon Sep 17 00:00:00 2001 From: hirosassa Date: Sun, 27 Nov 2022 05:33:39 +0900 Subject: [PATCH] support target and empty output (#30) * support target * local mode --- README.md | 17 +++++++ src/ci.rs | 9 ++++ src/main.rs | 31 ++++++++++--- src/template.rs | 117 +++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 157 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a3b0d05..0e09f18 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,20 @@ skaffold render -p dev | kubectl diff -f - 2> /dev/null | | ksnotify --notifier ``` The concrete example of GitLab CI configuration is shown in [example](https://github.com/hirosassa/ksnotify/tree/main/example). + + +## For developers + +To run `ksnotify` locally, use local option for debug. +For local mode, `ksnotify` just renders contents on stdout. + +```console +skaffold render -p dev | kubectl diff -f - 2> /dev/null | ~/Dev/ksnotify/ksnotify --ci local --notifier gitlab --suppress-skaffold + +> ## Plan result +> [CI link]( ) +> +> * updated +> blah +> blah +``` diff --git a/src/ci.rs b/src/ci.rs index 7a85df5..5c29986 100644 --- a/src/ci.rs +++ b/src/ci.rs @@ -5,8 +5,13 @@ use strum_macros::EnumString; #[derive(Debug, PartialEq, Eq, Clone, Copy, EnumString)] pub enum CIKind { + /// ksnotify is running on GitLab CI. #[strum(serialize = "gitlab")] GitLab, + + /// ksnotify is running on Local PC (for debug). + #[strum(serialize = "local")] + Local, } #[derive(Clone, Debug)] @@ -32,6 +37,10 @@ impl CI { merge_request, }) } + CIKind::Local => Ok(Self { + job_url: "".to_string(), + merge_request: MergeRequest { number: 1 }, + }), } } diff --git a/src/main.rs b/src/main.rs index c214da4..66cc773 100755 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,11 @@ pub struct Cli { #[arg(long)] pub notifier: Option, - /// Whether if suppress diffs comes from Skaffold labels + /// Target component name to distinguish for each environments or product. + #[arg(long)] + pub target: Option, + + /// Whether if suppress diffs comes from Skaffold labels. #[arg(long)] pub suppress_skaffold: bool, @@ -56,6 +60,14 @@ fn run() -> Result<()> { let config = config::Config::new(&cli).with_context(|| format!("failed to load config: {:?}", cli))?; info!("config: {:?}", config); + + // Local PC (for debug) + if config.ci == ci::CIKind::Local { + let content = process(config, None, cli.target)?; + println!("{}", content); + return Ok(()); + } + let ci = ci::CI::new(config.ci).with_context(|| format!("failed to create CI: {:?}", config.ci))?; let notifier_kind = config.notifier; @@ -65,14 +77,19 @@ fn run() -> Result<()> { } .with_context(|| format!("failed to create notifier: {:?}", ci))?; + let content = process(config, Some(ci.job_url().to_string()), cli.target)?; + notifier + .notify(content) + .with_context(|| "failed to notify".to_string())?; + Ok(()) +} + +fn process(config: config::Config, url: Option, target: Option) -> Result { let mut body = String::new(); io::stdin().read_to_string(&mut body)?; let parser = parser::DiffParser::new(config.suppress_skaffold)?; let result = parser.parse(&body)?; - let template = template::Template::new(result.kind_result, ci.job_url().to_string()); - - notifier - .notify(template.render()?) - .with_context(|| "failed to notify".to_string())?; - Ok(()) + let link = url.unwrap_or_default(); + let template = template::Template::new(result.kind_result, link, target); + template.render() } diff --git a/src/template.rs b/src/template.rs index 56e70ca..a40b386 100644 --- a/src/template.rs +++ b/src/template.rs @@ -7,19 +7,24 @@ use serde::Serialize; #[derive(Serialize)] pub struct Template { - title: String, + target: Option, changed_kinds: String, details: String, link: String, + is_no_changes: bool, } impl Template { - const DEFAULT_BUILD_TITLE: &'static str = "## Plan result"; - - const DEFAULT_BUILD_TEMPLATE: &'static str = "{{ title }} - + const DEFAULT_BUILD_TITLE_TEMPLATE: &'static str = + "## Plan result{{#if target}} ({{target}}){{/if}}"; + const DEFAULT_BUILD_BODY_TEMPLATE: &'static str = " [CI link]( {{ link }} ) +{{#if is_no_changes}} +``` +No changes. Kubernetes configurations are up-to-date. +``` +{{else}} * updated {{ changed_kinds }} @@ -28,23 +33,28 @@ impl Template { {{{ details }}} +{{/if}} "; - pub fn new(results: HashMap, link: String) -> Self { + pub fn new(results: HashMap, link: String, target: Option) -> Self { let changed_kinds = Self::generate_changed_kinds_markdown(&results); let details = Self::generate_details_markdown(&results); + let is_no_changes = results.is_empty(); Self { - title: Self::DEFAULT_BUILD_TITLE.to_string(), + target, changed_kinds, details, link, + is_no_changes, } } pub fn render(&self) -> Result { let reg = Handlebars::new(); let j = serde_json::to_value(self).unwrap(); - Ok(reg.render_template(Self::DEFAULT_BUILD_TEMPLATE, &j)?) + let title = reg.render_template(Self::DEFAULT_BUILD_TITLE_TEMPLATE, &j)?; + let body = reg.render_template(Self::DEFAULT_BUILD_BODY_TEMPLATE, &j)?; + Ok(format!("{}{}", title, body)) } fn generate_changed_kinds_markdown(results: &HashMap) -> String { @@ -57,7 +67,7 @@ impl Template { let details: Vec = kinds .iter() .map(|k| { - let title = format!("## {}", k); + let title = format!("### {}", k); let body = format!("```diff\n{}\n```", results[k]); format!("{}\n{}", title, body) }) @@ -94,7 +104,94 @@ mod tests { .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); let actual = Template::generate_details_markdown(&data); - let expected = "## test1\n```diff\nABC\n```\n## test2\n```diff\nDEF\n```".to_string(); + let expected = "### test1\n```diff\nABC\n```\n### test2\n```diff\nDEF\n```".to_string(); + assert_eq!(actual, expected); + } + + #[test] + fn test_new_with_no_changes() { + let results: HashMap = HashMap::new(); + let link = "http://example.com".to_string(); + let target = Some("sample app".to_string()); + let t = Template::new(results, link, target); + assert_eq!(t.is_no_changes, true); + } + + #[test] + fn test_new_with_some_changes() { + let mut results: HashMap = HashMap::new(); + results.insert("sample".to_string(), "change content".to_string()); + let link = "http://example.com".to_string(); + let target = Some("sample app".to_string()); + let t = Template::new(results, link, target); + assert_eq!(t.is_no_changes, false); + } + + #[test] + fn test_render_title_with_target() { + let reg = Handlebars::new(); + let mut data = HashMap::new(); + data.insert("target".to_string(), "sample".to_string()); + let actual = reg + .render_template(Template::DEFAULT_BUILD_TITLE_TEMPLATE, &data) + .unwrap(); + let expected = "## Plan result (sample)".to_string(); + assert_eq!(actual, expected); + } + + #[test] + fn test_render_title_without_target() { + let reg = Handlebars::new(); + let data = HashMap::::new(); + let actual = reg + .render_template(Template::DEFAULT_BUILD_TITLE_TEMPLATE, &data) + .unwrap(); + let expected = "## Plan result".to_string(); + assert_eq!(actual, expected); + } + + #[test] + fn test_render_body_with_no_changes() { + let reg = Handlebars::new(); + let mut data = HashMap::new(); + data.insert("is_no_changes".to_string(), "true".to_string()); + data.insert("link".to_string(), "http://example.com".to_string()); + let actual = reg + .render_template(Template::DEFAULT_BUILD_BODY_TEMPLATE, &data) + .unwrap(); + let expected = " +[CI link]( http://example.com ) + +``` +No changes. Kubernetes configurations are up-to-date. +``` +" + .to_string(); + assert_eq!(actual, expected); + } + + #[test] + fn test_render_body_with_changes() { + let reg = Handlebars::new(); + let mut data = HashMap::new(); + data.insert("link".to_string(), "http://example.com".to_string()); + data.insert("link".to_string(), "http://example.com".to_string()); + let actual = reg + .render_template(Template::DEFAULT_BUILD_BODY_TEMPLATE, &data) + .unwrap(); + let expected = " +[CI link]( http://example.com ) + +* updated + + +
Details (Click me) + + + +
+" + .to_string(); assert_eq!(actual, expected); } }