From ad1c466f7afdd5c0d2d5d87a6cef0a92c619851a Mon Sep 17 00:00:00 2001 From: "R. Tyler Croy" Date: Sun, 20 Dec 2020 14:51:46 -0800 Subject: [PATCH] Parse more complex nested stages, etc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds some more logic to the parser rather than the grammar to ensure that the stage {} directive has the necessary children blocks The errors are pretty sweet too: ❯ ./target/debug/jdp check data/invalid/no-steps-in-stage/Jenkinsfile data/invalid/no-steps-in-stage/Jenkinsfile ------------------------------------------ 0: pipeline { 1: agent any 2: stages { 3: stage('Build') { --------^ Fail: A stage must have either steps{}, parallel{}, or nested stages {} --- src/lib.rs | 50 +++++++++++++++++++++++++++++++++++++++ src/pipeline.pest | 60 +++++++++++++++++++++++++++-------------------- 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f12857a..6349e49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ extern crate pest_derive; use pest::error::Error as PestError; use pest::error::ErrorVariant; use pest::Parser; +use pest::iterators::Pairs; use std::path::PathBuf; #[derive(Parser)] @@ -41,6 +42,54 @@ pub fn parse_file(path: &PathBuf) -> Result<(), pest::error::Error> { } } +/** + * Make sure that the stage has the required directives, otherwise throw + * out a CustomError + */ +fn parse_stage(parser: &mut Pairs, span: pest::Span) -> Result<(), PestError> { + let mut met_requirements = false; + + while let Some(parsed) = parser.next() { + match parsed.as_rule() { + Rule::stepsDecl => { + met_requirements = true; + }, + Rule::parallelDecl => { + met_requirements = true; + }, + Rule::stagesDecl => { + met_requirements = true; + parse_stages(&mut parsed.into_inner())?; + } + _ => {}, + } + } + + if ! met_requirements { + Err(PestError::new_from_span( + ErrorVariant::CustomError { + message: "A stage must have either steps{}, parallel{}, or nested stages {}".to_string(), + }, span + )) + } + else { + Ok(()) + } +} + +fn parse_stages(parser: &mut Pairs) -> Result<(), PestError> { + while let Some(parsed) = parser.next() { + match parsed.as_rule() { + Rule::stage => { + let span = parsed.as_span(); + parse_stage(&mut parsed.into_inner(), span)?; + }, + _ => {}, + } + } + Ok(()) +} + pub fn parse_pipeline_string(buffer: &str) -> Result<(), PestError> { let mut parser = PipelineParser::parse(Rule::pipeline, buffer)?; @@ -70,6 +119,7 @@ pub fn parse_pipeline_string(buffer: &str) -> Result<(), PestError> { )); } stages = true; + parse_stages(&mut parsed.into_inner())?; } _ => {} } diff --git a/src/pipeline.pest b/src/pipeline.pest index b668156..7ce0da6 100644 --- a/src/pipeline.pest +++ b/src/pipeline.pest @@ -44,13 +44,11 @@ agentBlock = { | ("label" ~ string)) ~ closing_brace } -k8sAgent = { "kubernetes" ~ - opening_brace ~ - ("defaultContainer" ~ string)? ~ - ("yaml" ~ string)? ~ - ("yamlFile" ~ string)? ~ - closing_brace - } + +credentialProperty = { IDENT ~ + "=" ~ + "credentials" ~ opening_paren ~ string ~ closing_paren + } dockerAgent = { "docker" ~ opening_brace ~ ("reuseNode" ~ bool)? ~ @@ -62,6 +60,7 @@ dockerAgent = { "docker" ~ ("customWorkspace" ~ string)? ~ closing_brace } + dockerfileAgent = { "dockerfile" ~ opening_brace ~ ("reuseNode" ~ bool)? ~ @@ -73,13 +72,6 @@ dockerfileAgent = { "dockerfile" ~ ("customWorkspace" ~ string)? ~ closing_brace } -nodeAgent = { "node" ~ - opening_brace ~ - ("label" ~ string)? ~ - ("customWorkspace" ~ string)? ~ - closing_brace - } - environmentDecl = { "environment" ~ opening_brace ~ @@ -90,10 +82,8 @@ envProperty = { property | credentialProperty } -credentialProperty = { IDENT ~ - "=" ~ - "credentials" ~ opening_paren ~ string ~ closing_paren - } + +failFast = { "failFast" ~ bool } func = { IDENT ~ opening_paren? ~ (kwargs | args | func)? ~ closing_paren? } @@ -105,6 +95,23 @@ inputDecl = { "input" ~ closing_brace } +k8sAgent = { "kubernetes" ~ + opening_brace ~ + ("defaultContainer" ~ string)? ~ + ("yaml" ~ string)? ~ + ("yamlFile" ~ string)? ~ + closing_brace + } + +nodeAgent = { "node" ~ + opening_brace ~ + ("label" ~ string)? ~ + ("customWorkspace" ~ string)? ~ + closing_brace + } + + + optionsDecl = { "options" ~ opening_brace ~ (func)* ~ @@ -159,14 +166,15 @@ stagesDecl = { "stages" ~ stage = { "stage(" ~ string ~ ")" ~ opening_brace ~ - whenDecl? ~ - optionsDecl? ~ - agentDecl? ~ - environmentDecl? ~ - inputDecl? ~ - toolsDecl? ~ - (stepsDecl | parallelDecl) ~ - postDecl? ~ + (whenDecl + | failFast + | optionsDecl + | agentDecl + | environmentDecl + | inputDecl + | toolsDecl + | (stepsDecl | parallelDecl | stagesDecl) + | postDecl)* ~ closing_brace }