From f4fffa20322aa4132d85a7631d03df89d80f2572 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 11 Oct 2016 13:23:11 +0200 Subject: [PATCH] Validation failure tests and SYNTAX.md update. Also added missing descriptions for job properties, triggers and build parameters, and removed the not-actually-existing "use script" section. --- SYNTAX.md | 60 ++++++++++++------- .../modeldefinition/parser/ModelParser.groovy | 2 +- .../modeldefinition/AbstractModelDefTest.java | 4 ++ .../modeldefinition/ValidatorTest.java | 28 +++++++++ .../errors/perStageConfigEmptyAgent.groovy | 38 ++++++++++++ .../errors/perStageConfigEmptySteps.groovy | 37 ++++++++++++ .../errors/perStageConfigMissingSteps.groovy | 35 +++++++++++ .../perStageConfigUnknownSection.groovy | 41 +++++++++++++ .../json/errors/perStageConfigEmptySteps.json | 20 +++++++ .../errors/perStageConfigMissingSteps.json | 19 ++++++ .../errors/perStageConfigUnknownSection.json | 26 ++++++++ 11 files changed, 286 insertions(+), 24 deletions(-) create mode 100644 src/test/resources/errors/perStageConfigEmptyAgent.groovy create mode 100644 src/test/resources/errors/perStageConfigEmptySteps.groovy create mode 100644 src/test/resources/errors/perStageConfigMissingSteps.groovy create mode 100644 src/test/resources/errors/perStageConfigUnknownSection.groovy create mode 100644 src/test/resources/json/errors/perStageConfigEmptySteps.json create mode 100644 src/test/resources/json/errors/perStageConfigMissingSteps.json create mode 100644 src/test/resources/json/errors/perStageConfigUnknownSection.json diff --git a/SYNTAX.md b/SYNTAX.md index f5af4e9ce..c62b053eb 100644 --- a/SYNTAX.md +++ b/SYNTAX.md @@ -10,8 +10,9 @@ These are sections that are specified directly within the `pipeline` argument closure. ### agent -* *Description*: Specifies where the build will run. -* *Required*: Yes +* *Description*: Specifies where the build or stage will run. +* *Required*: Yes for the top-level `pipeline` closure, optional for individual `stage` closures. +* *Allowed In*: Top-level `pipeline` closure and individual `stage` closures. * *Parameters*: Either a `Map` of one or more arguments or one of two constants - `none` or `any`. * *Map Keys*: * Note that this will be an `ExtensionPoint`, so plugins will be able to add to the available image providers. @@ -41,6 +42,7 @@ These are sections that are specified directly within the `pipeline` argument cl * *Description*: A sequence of `key = value` pairs, which will be passed to the `withEnv` step the build will be executed within. * *Required*: No +* *Allowed In*: Top-level `pipeline` closure only. * *Parameters*: None * *Takes a Closure*: Yes * *Closure Contents*: One or more lines with `foo = 'bar'` variable name/value pairs. @@ -59,6 +61,7 @@ environment { ### stages * *Description*: A sequence of one or more Pipeline `stage`s, each of which consist of a sequence of steps. * *Required*: Yes +* *Allowed In*: Top-level `pipeline` closure only. * *Parameters*: None * *Takes a Closure*: Yes * *Closure Contents*: one or more `stage` blocks, as described below. @@ -69,7 +72,10 @@ to-be-released block-scoped `stage` syntax in base Pipeline. * *Required*: At least one is required. * *Parameters*: A single `String`, the name for the `stage`. * *Takes a Closure*: Yes -* *Closure Contents*: One or more Pipeline steps, including block-scoped steps and the special `script` block described below. +* *Closure Contents*: A `steps` block containing one or more Pipeline steps, including block-scoped steps and the +special `script` block described below, and optionally, certain configuration sections that allow being set on a +per-stage basis. + * *NOTE*: Currently only the `agent` section can be configured per-stage. * *NOTE*: Only the "declarative subset" of Groovy is allowed by default. See below for details on that subset. * *NOTE*: The `parallel` step is a special case - it can only be used if it's the sole step in the `stage`. * *Examples*: @@ -77,19 +83,24 @@ to-be-released block-scoped `stage` syntax in base Pipeline. ``` stages { stage('foo') { - echo 'bar' + steps { + echo 'bar' + } } } stages { stage('first') { - timeout(time:5, unit:'MINUTES') { - sh "mvn clean install -DskipTests" + steps { + timeout(time:5, unit:'MINUTES') { + sh "mvn clean install -DskipTests" + } } } stage('second') { - node('some-node') { + agent label:'some-node' + steps { checkout scm sh "mvn clean install" } @@ -98,14 +109,16 @@ stages { stages { stage('parallel-stage') { - parallel( - firstBlock: { - echo "First block of the parallel" - }, - secondBlock: { - echo "Second block of the parallel" - } - ) + steps { + parallel( + firstBlock: { + echo "First block of the parallel" + }, + secondBlock: { + echo "Second block of the parallel" + } + ) + } } } ``` @@ -136,6 +149,7 @@ stages { * *Description*: A top-level section defining tools to auto-install and put on the PATH. This is ignored if `image none` is specified. * *Required*: No +* *Allowed In*: Top-level `pipeline` closure only. * *Parameters*: None * *Takes a Closure*: Yes * *Closure Contents*: Names and versions of tools configured in Jenkins to install. @@ -155,6 +169,7 @@ tools { ### notifications * *Description*: Defines notifications to be sent after build completion, assuming build status conditions are met. * *Required*: No +* *Allowed In*: Top-level `pipeline` closure only. * *Parameters*: None * *Takes a Closure*: Yes * *Closure Contents*: A sequence of one or more build conditions containing Pipeline steps to run. See below for @@ -164,6 +179,7 @@ definition of build conditions and their contents. * *Description*: Defines post build actions to be run after build completion, assuming build status conditions are met. Note that `postBuild` steps are run *before* `notifications`. * *Required*: No +* *Allowed In*: Top-level `pipeline` closure only. * *Parameters*: None * *Takes a Closure*: Yes * *Closure Contents*: A sequence of one or more build conditions containing Pipeline steps to run. See below for @@ -205,8 +221,9 @@ postBuild { ``` ### Triggers -* *Description*: +* *Description*: Triggers for this job, as used in other Jenkins jobs. * *Required*: No +* *Allowed In*: Top-level `pipeline` closure only. * *Parameters*: None * *Takes a Closure*: Yes * *Closure Contents*: A sequence of one or more trigger configurations, using `@Symbol` names for constructors. @@ -221,8 +238,9 @@ triggers { ``` ### Build Parameters -* *Description*: +* *Description*: Build parameters that will be prompted for at build time. * *Required*: No +* *Allowed In*: Top-level `pipeline` closure only. * *Parameters*: None * *Takes a Closure*: Yes * *Closure Contents*: A sequence of one or more parameter definition configurations, using `@Symbol` names for constructors. @@ -237,8 +255,9 @@ parameters { ``` ### Job Properties -* *Description*: +* *Description*: Other job properties, such as build discarding, limiting concurrent builds, and more. * *Required*: No +* *Allowed In*: Top-level `pipeline` closure only. * *Parameters*: None * *Takes a Closure*: Yes * *Closure Contents*: A sequence of one or more job property configurations, using `@Symbol` names for constructors. @@ -272,8 +291,3 @@ jobProperties { * Method calls where the left hand side is a variable reference or a sequence of property references: `x.y.z(...)` * Method calls (including `@Symbol` constructors like used above in job properties, triggers and build parameters) where there is no left hand side. * Closure without parameters: `{ ... }` - -## Script mode -* *Description*: A flag which, when set, allows usage of standard non-declarative-subset Pipeline code throughout the Jenkinsfile. -* *Usage*: Set by putting `use script` at the beginning of the Jenkinsfile -* *Examples*: (hard to figure out a good example here since we've moved to the `pipeline` step?) diff --git a/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/ModelParser.groovy b/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/ModelParser.groovy index 7a7ecf171..9bee3dcb1 100644 --- a/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/ModelParser.groovy +++ b/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/ModelParser.groovy @@ -288,7 +288,7 @@ class ModelParser { eachStatement(((ClosureExpression)bodyExp).code) { s -> def mc = matchMethodCall(s); if (mc == null) { - errorCollector.error(stage, "Not a valid section definition: '${getSourceText(s)}'. Some extra configuration is required.") + errorCollector.error(stage, "Not a valid stage section definition: '${getSourceText(s)}'. Some extra configuration is required.") } else { def name = parseMethodName(mc); diff --git a/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/AbstractModelDefTest.java b/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/AbstractModelDefTest.java index 7c4249853..10fb3bd53 100644 --- a/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/AbstractModelDefTest.java +++ b/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/AbstractModelDefTest.java @@ -139,6 +139,10 @@ public static Iterable configsWithErrors() { result.add(new Object[]{"wrongParameterNameMethodCall", "Invalid parameter 'namd', did you mean 'name'?"}); result.add(new Object[]{"invalidParameterTypeMethodCall", "Expecting class java.lang.String for parameter 'name' but got '1234' instead"}); + result.add(new Object[]{"perStageConfigEmptySteps", "At /pipeline/stages/0/branches/0/steps: Array has 0 entries, requires minimum of 1"}); + result.add(new Object[]{"perStageConfigMissingSteps", "At /pipeline/stages/0/branches/0: Missing one or more required properties: 'steps'"}); + result.add(new Object[]{"perStageConfigUnknownSection", "At /pipeline/stages/0: additional properties are not allowed"}); + result.add(new Object[]{"malformed", "Expected a ',' or '}' at character 243 of {\"pipeline\": {\n" + " \"stages\": [ {\n" + " \"name\": \"foo\",\n" + diff --git a/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/ValidatorTest.java b/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/ValidatorTest.java index 3948fe0bc..df6db8e7e 100644 --- a/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/ValidatorTest.java +++ b/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/ValidatorTest.java @@ -212,6 +212,27 @@ public void unknownStepParameter() throws Exception { failWithError("Invalid parameter 'banana', did you mean 'unit'?"); } + @Test + public void perStageConfigEmptySteps() throws Exception { + prepRepoWithJenkinsfile("errors", "perStageConfigEmptySteps"); + + failWithError("No steps specified for branch"); + } + + @Test + public void perStageConfigMissingSteps() throws Exception { + prepRepoWithJenkinsfile("errors", "perStageConfigMissingSteps"); + + failWithError("Nothing to execute within stage"); + } + + @Test + public void perStageConfigUnknownSection() throws Exception { + prepRepoWithJenkinsfile("errors", "perStageConfigUnknownSection"); + + failWithError("Unknown stage section 'banana'"); + } + @Test public void invalidMetaStepSyntax() throws Exception { prepRepoWithJenkinsfile("errors", "invalidMetaStepSyntax"); @@ -254,6 +275,13 @@ public void emptyAgent() throws Exception { failWithError("Not a valid section definition: 'agent'. Some extra configuration is required."); } + @Test + public void perStageConfigEmptyAgent() throws Exception { + prepRepoWithJenkinsfile("errors", "perStageConfigEmptyAgent"); + + failWithError("Not a valid stage section definition: 'agent'. Some extra configuration is required."); + } + @Test public void invalidBuildCondition() throws Exception { prepRepoWithJenkinsfile("errors", "invalidBuildCondition"); diff --git a/src/test/resources/errors/perStageConfigEmptyAgent.groovy b/src/test/resources/errors/perStageConfigEmptyAgent.groovy new file mode 100644 index 000000000..9808d0ec1 --- /dev/null +++ b/src/test/resources/errors/perStageConfigEmptyAgent.groovy @@ -0,0 +1,38 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +pipeline { + agent none + stages { + stage("foo") { + agent + steps { + echo "Hi" + } + } + } +} + + + diff --git a/src/test/resources/errors/perStageConfigEmptySteps.groovy b/src/test/resources/errors/perStageConfigEmptySteps.groovy new file mode 100644 index 000000000..1fd2b1721 --- /dev/null +++ b/src/test/resources/errors/perStageConfigEmptySteps.groovy @@ -0,0 +1,37 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +pipeline { + agent none + stages { + stage("foo") { + agent label:'some-label' + steps { + } + } + } +} + + + diff --git a/src/test/resources/errors/perStageConfigMissingSteps.groovy b/src/test/resources/errors/perStageConfigMissingSteps.groovy new file mode 100644 index 000000000..a204cc88e --- /dev/null +++ b/src/test/resources/errors/perStageConfigMissingSteps.groovy @@ -0,0 +1,35 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +pipeline { + agent none + stages { + stage("foo") { + agent label:'some-label' + } + } +} + + + diff --git a/src/test/resources/errors/perStageConfigUnknownSection.groovy b/src/test/resources/errors/perStageConfigUnknownSection.groovy new file mode 100644 index 000000000..c47548b52 --- /dev/null +++ b/src/test/resources/errors/perStageConfigUnknownSection.groovy @@ -0,0 +1,41 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +pipeline { + agent none + stages { + stage("foo") { + banana { + echo "monkey" + } + agent label:'some-label' + steps { + echo "hi" + } + } + } +} + + + diff --git a/src/test/resources/json/errors/perStageConfigEmptySteps.json b/src/test/resources/json/errors/perStageConfigEmptySteps.json new file mode 100644 index 000000000..a363a647f --- /dev/null +++ b/src/test/resources/json/errors/perStageConfigEmptySteps.json @@ -0,0 +1,20 @@ +{"pipeline": { + "stages": [ { + "name": "foo", + "branches": [ { + "name": "default", + "steps": [] + }], + "agent": [ { + "key": "label", + "value": { + "isLiteral": true, + "value": "some-label" + } + }] + }], + "agent": { + "isLiteral": true, + "value": "none" + } +}} \ No newline at end of file diff --git a/src/test/resources/json/errors/perStageConfigMissingSteps.json b/src/test/resources/json/errors/perStageConfigMissingSteps.json new file mode 100644 index 000000000..1dab8bdf5 --- /dev/null +++ b/src/test/resources/json/errors/perStageConfigMissingSteps.json @@ -0,0 +1,19 @@ +{"pipeline": { + "stages": [ { + "name": "foo", + "branches": [ { + "name": "default" + }], + "agent": [ { + "key": "label", + "value": { + "isLiteral": true, + "value": "some-label" + } + }] + }], + "agent": { + "isLiteral": true, + "value": "none" + } +}} \ No newline at end of file diff --git a/src/test/resources/json/errors/perStageConfigUnknownSection.json b/src/test/resources/json/errors/perStageConfigUnknownSection.json new file mode 100644 index 000000000..b98ca27e6 --- /dev/null +++ b/src/test/resources/json/errors/perStageConfigUnknownSection.json @@ -0,0 +1,26 @@ +{"pipeline": { + "stages": [ { + "name": "foo", + "branches": [ { + "name": "default", + "steps": [ { + "name": "sh", + "arguments": { + "isLiteral": true, + "value": "echo ONSLAVE=$ONSLAVE" + } + }] + }], + "banana": [ { + "key": "label", + "value": { + "isLiteral": true, + "value": "some-label" + } + }] + }], + "agent": { + "isLiteral": true, + "value": "none" + } +}} \ No newline at end of file