Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #78 from jenkinsci/JENKINS-40370
[JENKINS-40370] Make stage.when more declarative
  • Loading branch information
abayer committed Jan 9, 2017
2 parents 1e14524 + cadb16c commit cf217be
Show file tree
Hide file tree
Showing 69 changed files with 2,028 additions and 278 deletions.
29 changes: 28 additions & 1 deletion SYNTAX.md
Expand Up @@ -131,7 +131,7 @@ stages {
label 'some-node'
}
when {
env.BRANCH == 'master'
branch "master"
}
steps {
checkout scm
Expand Down Expand Up @@ -164,6 +164,33 @@ stages {
}
```

#### when
* *Description*: A block within a `stage` that determines whether the stage should be executed.
* *Required*: No
* *Parameters*: None
* *Takes a Closure*: Yes
* *Closure Contents*: One or more of the possible `when` conditions, provided by an extension point.
* *Built-In `when` Conditions*:
* `branch` - run when the branch being built matches the branch pattern given.
* `environment` - run when the environment contains a given environment variable with a given value.
* `expression` - run when an arbitrary Groovy expression returning a boolean is true.
* *Examples*:
```groovy
when {
branch "*/master"
}
when {
environment name: "FOO", value: "SOME_OTHER_VALUE"
}
when {
expression {
return "foo" == "bar"
}
}
```
#### script
* *Description*: A block within a `stage`'s steps that can contain Pipeline code not subject to the "declarative" subset
described below.
Expand Down
Expand Up @@ -33,7 +33,7 @@
/**
* Represents the special step which are executed without validation against the declarative subset.
* @see ModelASTScriptBlock
* @see ModelASTWhen
* @see ModelASTWhenExpression
*/
public abstract class AbstractModelASTCodeBlock extends ModelASTStep {

Expand Down
Expand Up @@ -25,29 +25,73 @@

package org.jenkinsci.plugins.pipeline.modeldefinition.ast;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.jenkinsci.plugins.pipeline.modeldefinition.validator.ModelValidator;

import java.util.ArrayList;
import java.util.List;

/**
* Represents a block for when/if a {@link ModelASTStage} will be executed or not.
* If {@link ModelASTStage} will be executed or not.
*/
public class ModelASTWhen extends AbstractModelASTCodeBlock {
public class ModelASTWhen extends ModelASTElement {

private List<ModelASTStep> conditions = new ArrayList<>();

public ModelASTWhen(Object sourceLocation) {
super(sourceLocation, "when");
super(sourceLocation);
}

public List<ModelASTStep> getConditions() {
return conditions;
}

public void setConditions(List<ModelASTStep> conditions) {
this.conditions = conditions;
}

@Override
public Object toJSON() {
final JSONArray a = new JSONArray();
for (ModelASTStep c: conditions) {
a.add(c.toJSON());
}
return new JSONObject().accumulate("conditions", a);
}

@Override
public JSONObject toJSON() {
JSONObject o = new JSONObject();
if (getArgs() != null) {
o.accumulate("arguments", getArgs().toJSON());
public String toGroovy() {
StringBuilder result = new StringBuilder("when {\n");
for (ModelASTStep c: conditions) {
result.append(c.toGroovy()).append("\n");
}
return o;
result.append("}\n");
return result.toString();
}

@Override
public void validate(ModelValidator validator) {
super.validate(validator);
public void removeSourceLocation() {
super.removeSourceLocation();
for (ModelASTStep c: conditions) {
c.removeSourceLocation();
}
}

@Override
public String toString() {
return "ModelASTWhen{" +
"conditions=" + conditions +
"}";
}

@Override
public void validate(final ModelValidator validator) {
validator.validateElement(this);
for (ModelASTStep s : conditions) {
validator.validateWhenCondition(s);
}
}


}
@@ -0,0 +1,42 @@
/*
* 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.
*
*/

package org.jenkinsci.plugins.pipeline.modeldefinition.ast;

import org.jenkinsci.plugins.pipeline.modeldefinition.validator.ModelValidator;

/**
* Code expression {@link ModelASTStage} will be executed or not.
*/
public class ModelASTWhenExpression extends AbstractModelASTCodeBlock {
public ModelASTWhenExpression(Object sourceLocation) {
super(sourceLocation, "expression");
}

@Override
public void validate(ModelValidator validator) {
// no-op - we don't do validation of script blocks
}
}
Expand Up @@ -88,4 +88,6 @@ public interface ModelValidator {
boolean validateElement(ModelASTStage stage);

boolean validateElement(ModelASTStages stages);

boolean validateWhenCondition(ModelASTStep condition);
}
23 changes: 17 additions & 6 deletions pipeline-model-api/src/main/resources/ast-schema.json
Expand Up @@ -320,17 +320,28 @@
"additionalProperties": false
},
"when": {
"description": "Closure to evaluate if the stage should run or not",
"description": "Conditions to evaluate whether the stage should run or not",
"type": "object",
"properties": {
"arguments": {
"$ref": "#/definitions/rawArgument"
"conditions": {
"type": "array",
"minItems": 1,
"items": {
"anyOf": [
{
"$ref": "#/definitions/step"
},
{
"$ref": "#/definitions/treeStep"
}
]
}
}

},

"required": [
"conditions"
],
"additionalProperties": false

},
"post": {
"description": "An array of build conditions with blocks of steps to run if those conditions are satisfied at the end of the build while still on the image/node the build ran on",
Expand Down
Expand Up @@ -25,41 +25,13 @@
package org.jenkinsci.plugins.pipeline.modeldefinition.agent;

import hudson.ExtensionPoint;
import hudson.model.AbstractDescribableImpl;
import org.jenkinsci.plugins.workflow.cps.CpsScript;
import org.jenkinsci.plugins.workflow.cps.CpsThread;

import java.io.Serializable;
import org.jenkinsci.plugins.pipeline.modeldefinition.withscript.WithScriptDescribable;

/**
* Implementations for {@link DeclarativeAgentDescriptor} - pluggable agent backends for Declarative Pipelines.
*
* @author Andrew Bayer
*/
public abstract class DeclarativeAgent extends AbstractDescribableImpl<DeclarativeAgent> implements Serializable, ExtensionPoint {

/**
* ONLY TO BE RUN FROM WITHIN A CPS THREAD. Parses the script source and loads it.
* TODO: Decide if we want to cache the resulting objects or just *shrug* and re-parse them every time.
*
* @return The script object for this declarative agent.
* @throws Exception if the script source cannot be loaded or we're called from outside a CpsThread.
*/
@SuppressWarnings("unchecked")
public DeclarativeAgentScript getScript(CpsScript cpsScript) throws Exception {
CpsThread c = CpsThread.current();
if (c == null)
throw new IllegalStateException("Expected to be called from CpsThread");

return (DeclarativeAgentScript) cpsScript.getClass()
.getClassLoader()
.loadClass(getDescriptor().getDeclarativeAgentScriptClass())
.getConstructor(CpsScript.class, DeclarativeAgent.class)
.newInstance(cpsScript, this);
}
public abstract class DeclarativeAgent<A extends DeclarativeAgent<A>> extends WithScriptDescribable<A> implements ExtensionPoint {

@Override
public DeclarativeAgentDescriptor getDescriptor() {
return (DeclarativeAgentDescriptor) super.getDescriptor();
}
}
Expand Up @@ -24,7 +24,7 @@
package org.jenkinsci.plugins.pipeline.modeldefinition.agent;

import hudson.ExtensionList;
import hudson.model.Descriptor;
import org.jenkinsci.plugins.pipeline.modeldefinition.withscript.WithScriptDescriptor;
import org.jenkinsci.plugins.structs.SymbolLookup;
import org.jenkinsci.plugins.structs.describable.DescribableModel;
import org.jenkinsci.plugins.structs.describable.UninstantiatedDescribable;
Expand All @@ -33,59 +33,13 @@
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
* Descriptor for {@link DeclarativeAgent}.
*
* @author Andrew Bayer
*/
public abstract class DeclarativeAgentDescriptor extends Descriptor<DeclarativeAgent> {

/**
* The name for this agent type. Defaults to the first string in the {@code Symbol} on the class.
*
* @return The name.
*/
public @Nonnull String getName() {
Set<String> symbolValues = SymbolLookup.getSymbolValue(this);
if (symbolValues.isEmpty()) {
throw new IllegalArgumentException("Declarative Agent descriptor class " + this.getClass().getName()
+ " does not have a @Symbol and does not override getName().");
}
return symbolValues.iterator().next();
}

/**
* The full package and class name for the {@link DeclarativeAgentScript} class corresponding to this. Defaults to
* the {@link DeclarativeAgent} class name with "Script" appended to the end.
*
* @return The class name, defaulting to the {@link DeclarativeAgent} {@link #clazz} class name with "Script" appended.
*/
public @Nonnull String getDeclarativeAgentScriptClass() {
return clazz.getName() + "Script";
}

/**
* Creates an instance of the corresponding {@link DeclarativeAgent} from the given arguments.
*
* @param arguments A map of strings/objects to be passed to the constructor.
* @return An instantiated {@link DeclarativeAgent}
* @throws Exception
*/
public DeclarativeAgent newInstance(Map<String,Object> arguments) throws Exception {
return new DescribableModel<>(clazz).instantiate(arguments);
}

/**
* Creates an instance of the corresponding {@link DeclarativeAgent} with no arguments.
*
* @return An instantiated {@link DeclarativeAgent}
* @throws Exception
*/
public DeclarativeAgent newInstance() throws Exception {
return clazz.newInstance();
}
public abstract class DeclarativeAgentDescriptor<A extends DeclarativeAgent<A>> extends WithScriptDescriptor<A> {

/**
* Get all {@link DeclarativeAgentDescriptor}s.
Expand Down Expand Up @@ -162,7 +116,7 @@ public static Map<String,DescribableModel> noRequiredArgsModels() {
* @return The instantiated {@link DeclarativeAgent} instance, or null if the name isn't found.
* @throws Exception
*/
public static @Nullable DeclarativeAgent instanceForName(@Nonnull String name,
public static @Nullable DeclarativeAgent<?> instanceForName(@Nonnull String name,
Map<String,Object> arguments) throws Exception {
DeclarativeAgentDescriptor descriptor = byName(name);

Expand All @@ -181,7 +135,7 @@ public static Map<String,DescribableModel> noRequiredArgsModels() {
* @return The instantiated {@link DeclarativeAgent} instance.
* @throws Exception
*/
public static @Nonnull DeclarativeAgent instanceForDescriptor(@Nonnull DeclarativeAgentDescriptor descriptor,
public static @Nonnull DeclarativeAgent<?> instanceForDescriptor(@Nonnull DeclarativeAgentDescriptor<?> descriptor,
Map<String,Object> arguments) throws Exception {
if (zeroArgModels().keySet().contains(descriptor.getName()) ||
(noRequiredArgsModels().keySet().contains(descriptor.getName()) &&
Expand Down
Expand Up @@ -24,17 +24,14 @@
package org.jenkinsci.plugins.pipeline.modeldefinition.agent;

import groovy.lang.Closure;
import org.jenkinsci.plugins.pipeline.modeldefinition.withscript.WithScriptScript;
import org.jenkinsci.plugins.workflow.cps.CpsScript;

import java.io.Serializable;

public abstract class DeclarativeAgentScript implements Serializable {
protected CpsScript script;
protected DeclarativeAgent declarativeAgent;
public abstract class DeclarativeAgentScript<A extends DeclarativeAgent<A>> extends WithScriptScript<A> {

public DeclarativeAgentScript(CpsScript s, DeclarativeAgent a) {
this.script = s;
this.declarativeAgent = a;
public DeclarativeAgentScript(CpsScript s, A a) {
super(s, a);
}

public abstract Closure run(Closure body);
Expand Down

0 comments on commit cf217be

Please sign in to comment.