Skip to content

Commit

Permalink
[JENKINS-40370] Make stage.when more declarative
Browse files Browse the repository at this point in the history
  • Loading branch information
rsandell committed Dec 20, 2016
1 parent 01ba61a commit d28a97c
Show file tree
Hide file tree
Showing 16 changed files with 631 additions and 28 deletions.
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,54 @@

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 ModelASTWhen(Object sourceLocation) {
super(sourceLocation, "when");
public class ModelASTWhen extends ModelASTElement {

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

ModelASTWhen(Object sourceLocation) {
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 step: conditions) {
a.add(step.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();
for (ModelASTStep step: conditions) {
result.append(step.toGroovy()).append("\n");
}
return o;
return result.toString();
}

@Override
public void validate(ModelValidator validator) {
super.validate(validator);
validator.validateElement(this);
public void removeSourceLocation() {
super.removeSourceLocation();
for (ModelASTStep step: conditions) {
step.removeSourceLocation();
}
}
}
@@ -0,0 +1,53 @@
/*
* 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 net.sf.json.JSONObject;
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 JSONObject toJSON() {
JSONObject o = new JSONObject();
if (getArgs() != null) {
o.accumulate("arguments", getArgs().toJSON());
}
return o;
}

@Override
public void validate(ModelValidator validator) {
super.validate(validator);
validator.validateElement(this);
}
}
Expand Up @@ -48,7 +48,7 @@ public class Stage implements NestedModel, Serializable {

PostStage post

StepsBlock when
StageConditionals when

Tools tools

Expand Down Expand Up @@ -84,7 +84,7 @@ public class Stage implements NestedModel, Serializable {
return this
}

Stage when(StepsBlock when) {
Stage when(StageConditionals when) {
this.when = when
return this
}
Expand Down
@@ -0,0 +1,55 @@
/*
* 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.model

import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import org.jenkinsci.plugins.pipeline.modeldefinition.when.DeclarativeStageConditional
import org.jenkinsci.plugins.workflow.cps.CpsScript
import org.jenkinsci.plugins.workflow.steps.StepContext

/**
* The {@link Stage#when} block.
*/
@ToString
@EqualsAndHashCode
class StageConditionals implements MethodsToList<DeclarativeStageConditional>, Serializable {
private List<DeclarativeStageConditional> conditions;

StageConditionals(List<DeclarativeStageConditional> conditions) {
this.conditions = conditions
}

public boolean evaluate(StepContext context, CpsScript script) {
for (int i = 0; i < conditions.size(); i++) {
DeclarativeStageConditional cond = conditions[i];
if (!cond.evaluate(context, script)) {
return false
}
}
return true
}
}
Expand Up @@ -193,6 +193,15 @@ class JSONParser {
return branch
}

public @CheckForNull ModelASTWhen parseWhen(JSONObject j) {
ModelASTWhen when = new ModelASTWhen(j)
j.getJSONArray("conditions").each { o ->
JSONObject s = (JSONObject)o
when.conditions.add(parseStep(s))
}
return when
}

public @CheckForNull ModelASTOptions parseOptions(JSONObject j) {
ModelASTOptions options = new ModelASTOptions(j)

Expand Down Expand Up @@ -312,8 +321,10 @@ class JSONParser {
public @CheckForNull ModelASTStep parseStep(JSONObject j) {
if (j.containsKey("children")) {
return parseTreeStep(j)
} else if (j.getString("name").equals("script")) {
} else if (j.getString("name") == "script") {
return parseScriptBlock(j)
} else if (j.getString("name") == "expression") {
return parseWhenExpression(j)
} else {
ModelASTStep step = new ModelASTStep(j)
step.name = j.getString("name")
Expand Down Expand Up @@ -393,8 +404,8 @@ class JSONParser {
return scriptBlock
}

public @CheckForNull ModelASTWhen parseWhen(JSONObject j) {
ModelASTWhen scriptBlock = new ModelASTWhen(j)
public @CheckForNull ModelASTWhenExpression parseWhenExpression(JSONObject j) {
ModelASTWhenExpression scriptBlock = new ModelASTWhenExpression(j)
scriptBlock.args = parseArgumentList(j.getJSONObject("arguments"))

return scriptBlock
Expand Down
Expand Up @@ -350,6 +350,15 @@ class ModelParser {
return stage
}

public ModelASTWhen parseWhen(Statement statement) {
def stepsBlock = matchBlockStatement(statement)
BlockStatement block = asBlock(stepsBlock.body.code)
ModelASTWhen w = new ModelASTWhen(statement)
block.statements.each {s ->
w.conditions.add(parseStep(s))
}
}

/**
* Parses a block of code into {@link ModelASTBranch}
*/
Expand Down Expand Up @@ -596,8 +605,10 @@ class ModelParser {
};

def stepName = parseMethodName(mc);
if (stepName.equals("script")) {
if (stepName == "script") {
return parseScriptBlock(st)
} else if (stepName == "expression") {
return parseWhenExpression(st)
}

List<Expression> args = ((TupleExpression) mc.arguments).expressions
Expand All @@ -617,8 +628,8 @@ class ModelParser {
return thisStep
}

public ModelASTWhen parseWhen(Statement st) {
return parseCodeBlockInternal(st, new ModelASTWhen(st), "When")
public ModelASTWhenExpression parseWhenExpression(Statement st) {
return parseCodeBlockInternal(st, new ModelASTWhenExpression(st), "When")
}

/**
Expand Down
Expand Up @@ -33,6 +33,7 @@ import hudson.tools.ToolInstallation
import hudson.util.EditDistance
import jenkins.model.Jenkins
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter
import org.jenkinsci.plugins.pipeline.modeldefinition.Messages
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.DeclarativeAgentDescriptor
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.*
import org.jenkinsci.plugins.pipeline.modeldefinition.model.BuildCondition
Expand All @@ -41,6 +42,7 @@ import org.jenkinsci.plugins.pipeline.modeldefinition.model.Parameters
import org.jenkinsci.plugins.pipeline.modeldefinition.model.Tools
import org.jenkinsci.plugins.pipeline.modeldefinition.model.Triggers
import org.jenkinsci.plugins.pipeline.modeldefinition.model.Wrappers
import org.jenkinsci.plugins.pipeline.modeldefinition.when.DeclarativeStageConditionalDescriptor
import org.jenkinsci.plugins.structs.SymbolLookup
import org.jenkinsci.plugins.structs.describable.DescribableModel
import org.jenkinsci.plugins.structs.describable.DescribableParameter
Expand Down Expand Up @@ -164,12 +166,22 @@ class ModelValidatorImpl implements ModelValidator {
}

public boolean validateElement(ModelASTWhen when) {
//TODO can we evaluate if the closure will return something that can be tries for true?
if (when.toGroovy() =~ /\Awhen\s[{\s}]*\z/) {
errorCollector.error(when, "Empty when closure, remove the property or add some content.")
if (when.conditions.isEmpty()) {
errorCollector.error(when, Messages.ModelValidator_ModelASTWhen_empty())
return false
} else {
def allNames = DeclarativeStageConditionalDescriptor.allNames()
boolean isUnknownName = false
when.conditions.each {step ->
if (!(step.name in allNames)) {
errorCollector.error(when, Messages.ModelValidator_ModelASTWhen_unknown(step.name, allNames.join(", ")))
isUnknownName = true
} else {
step.args
}
}
return !isUnknownName
}
return true
}


Expand Down
@@ -0,0 +1,63 @@
/*
* 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.when;

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

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Serializable;

/**
* Conditionals for when to run a stage.
*/
public abstract class DeclarativeStageConditional extends AbstractDescribableImpl<DeclarativeStageConditional> implements Serializable, ExtensionPoint {

public abstract boolean evaluate(CpsScript script) throws InterruptedException, AbortException;

@Override
public DeclarativeStageConditionalDescriptor getDescriptor() {
return (DeclarativeStageConditionalDescriptor) super.getDescriptor();
}

@Nullable
protected <T> T getContextVariable(Class<T> key) throws IOException, InterruptedException {
CpsThread current = CpsThread.current();
if (current != null) {
throw new IllegalStateException("Needs to be called within a CPS Thread");
}
StepExecution step = current.getStep();
if (step != null) {
return step.getContext().get(key);
}
return null;
}
}

0 comments on commit d28a97c

Please sign in to comment.