Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-41334] Adding parallel stages.
  • Loading branch information
abayer committed May 21, 2017
1 parent ddf519b commit f2211f5
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 80 deletions.
Expand Up @@ -23,20 +23,27 @@ public final class ModelASTStage extends ModelASTElement {
private ModelASTTools tools;
private ModelASTEnvironment environment;
private Boolean failFast;
private ModelASTStages parallelStages;

public ModelASTStage(Object sourceLocation) {
super(sourceLocation);
}

@Override
public JSONObject toJSON() {
final JSONArray a = new JSONArray();
for (ModelASTBranch branch : branches) {
a.add(branch.toJSON());
}
JSONObject o = new JSONObject();
o.accumulate("name", name);
o.accumulate("branches", a);

if (branches.isEmpty() && parallelStages != null) {
o.accumulate("parallelStages", parallelStages.toJSON());
} else {
final JSONArray a = new JSONArray();
for (ModelASTBranch branch : branches) {
a.add(branch.toJSON());
}
o.accumulate("branches", a);
}

if (failFast != null) {
o.accumulate("failFast", failFast);
}
Expand Down Expand Up @@ -64,10 +71,17 @@ public JSONObject toJSON() {

@Override
public void validate(final ModelValidator validator) {
validator.validateElement(this);
validate(validator, false);
}

public void validate(final ModelValidator validator, boolean isNested) {
validator.validateElement(this, isNested);
for (ModelASTBranch branch : branches) {
branch.validate(validator);
}
if (parallelStages != null) {
parallelStages.validate(validator, true);
}
if (agent != null) {
agent.validate(validator);
}
Expand Down Expand Up @@ -102,31 +116,35 @@ public String toGroovy() {
if (environment != null) {
result.append(environment.toGroovy());
}
result.append("steps {\n");
if (branches.size() > 1) {
result.append("parallel(");
boolean first = true;
for (ModelASTBranch branch: branches) {
if (first) {
first = false;
} else {
result.append(',');
if (branches.isEmpty() && parallelStages != null) {
result.append(parallelStages.toGroovy());
} else {
result.append("steps {\n");
if (branches.size() > 1) {
result.append("parallel(");
boolean first = true;
for (ModelASTBranch branch : branches) {
if (first) {
first = false;
} else {
result.append(',');
}
result.append('\n');
result.append('"' + StringEscapeUtils.escapeJava(branch.getName()) + '"')
.append(": {\n")
.append(branch.toGroovy())
.append("\n}");
}
result.append('\n');
result.append('"' + StringEscapeUtils.escapeJava(branch.getName()) + '"')
.append(": {\n")
.append(branch.toGroovy())
.append("\n}");
}
if (failFast != null && failFast) {
result.append(",\nfailFast: true");
if (failFast != null && failFast) {
result.append(",\nfailFast: true");
}
result.append("\n)\n");
} else if (branches.size() == 1) {
result.append(branches.get(0).toGroovy());
}
result.append("\n)\n");
} else if (branches.size() == 1) {
result.append(branches.get(0).toGroovy());
}

result.append("}\n");
result.append("}\n");
}

if (post != null) {
result.append(post.toGroovy());
Expand All @@ -146,6 +164,9 @@ public void removeSourceLocation() {
if (agent != null) {
agent.removeSourceLocation();
}
if (parallelStages != null) {
parallelStages.removeSourceLocation();
}
if (when != null) {
when.removeSourceLocation();
}
Expand Down Expand Up @@ -224,6 +245,14 @@ public void setFailFast(Boolean f) {
this.failFast = f;
}

public ModelASTStages getParallelStages() {
return parallelStages;
}

public void setParallelStages(ModelASTStages s) {
this.parallelStages = s;
}

@Override
public String toString() {
return "ModelASTStage{" +
Expand All @@ -235,6 +264,7 @@ public String toString() {
", tools=" + tools +
", environment=" + environment +
", failFast=" + failFast +
", parallelStages=" + parallelStages +
"}";
}

Expand Down Expand Up @@ -268,6 +298,9 @@ public boolean equals(Object o) {
if (getFailFast() != null ? !getFailFast().equals(that.getFailFast()) : that.getFailFast() != null) {
return false;
}
if (getParallelStages() != null ? !getParallelStages().equals(that.getParallelStages()) : that.getParallelStages() != null) {
return false;
}
if (getTools() != null ? !getTools().equals(that.getTools()) : that.getTools() != null) {
return false;
}
Expand All @@ -289,6 +322,7 @@ public int hashCode() {
result = 31 * result + (getTools() != null ? getTools().hashCode() : 0);
result = 31 * result + (getEnvironment() != null ? getEnvironment().hashCode() : 0);
result = 31 * result + (getFailFast() != null ? getFailFast().hashCode() : 0);
result = 31 * result + (getParallelStages() != null ? getParallelStages().hashCode() : 0);
return result;
}
}
Expand Up @@ -28,9 +28,13 @@ public JSONArray toJSON() {

@Override
public void validate(final ModelValidator validator) {
validate(validator, false);
}

public void validate(final ModelValidator validator, boolean isNested) {
validator.validateElement(this);
for (ModelASTStage stage : stages) {
stage.validate(validator);
stage.validate(validator, isNested);
}
}

Expand Down
Expand Up @@ -89,7 +89,7 @@ public interface ModelValidator {

boolean validateElement(ModelASTPipelineDef pipelineDef);

boolean validateElement(ModelASTStage stage);
boolean validateElement(ModelASTStage stage, boolean isNested);

boolean validateElement(ModelASTStages stages);

Expand Down
8 changes: 5 additions & 3 deletions pipeline-model-api/src/main/resources/ast-schema.json
Expand Up @@ -445,7 +445,7 @@
"additionalProperties": false
},
"stage": {
"description": "A single Pipeline stage, with a name and one or more branches",
"description": "A single Pipeline stage, with a name and either one or more branches or one or more nested stages",
"type": "object",
"properties": {
"name": {
Expand Down Expand Up @@ -474,11 +474,13 @@
"items": {
"$ref": "#/definitions/branch"
}
},
"parallelStages": {
"$ref": "#/definitions/stages"
}
},
"required": [
"name",
"branches"
"name"
],
"additionalProperties": false
},
Expand Down
Expand Up @@ -99,7 +99,8 @@ public class Environment implements Serializable {
* @param parent An optional parent {@link Environment}
* @return
*/
public Map<String,String> resolveEnvVars(CpsScript script, boolean firstLevel, Environment parent = null) {
public Map<String,String> resolveEnvVars(CpsScript script, boolean withContext, Environment parent = null,
Stage parentStage = null) {
Map<String, String> alreadySet = new TreeMap<>()
if (getMap().isEmpty()) {
return alreadySet
Expand Down Expand Up @@ -129,6 +130,15 @@ public class Environment implements Serializable {
k instanceof String && v instanceof String
})

// If we're being called for a stage, add any root level environment variables after resolving them.
if (parent != null) {
if (parentStage != null && parentStage.environment != null) {
alreadySet.putAll(parentStage.environment.resolveEnvVars(script, false, parent))
} else {
alreadySet.putAll(parent.resolveEnvVars(script, false))
}
}

// If we're being called directly and not to pull in root-level environment variables into a stage, add anything
// in the current env global variable.
if (firstLevel) {
Expand Down
Expand Up @@ -55,6 +55,8 @@ public class Stage implements NestedModel, Serializable {

Environment environment

Stages parallelStages

Stage name(String n) {
this.name = n
return this
Expand Down Expand Up @@ -90,15 +92,19 @@ public class Stage implements NestedModel, Serializable {
return this
}

Stage parallelStages(Stages stages) {
this.parallelStages = stages
return this
}
/**
* Helper method for translating the key/value pairs in the {@link Environment} into a list of "key=value" strings
* suitable for use with the withEnv step.
*
* @return a list of "key=value" strings.
*/
List<String> getEnvVars(Root root, CpsScript script) {
List<String> getEnvVars(Root root, CpsScript script, Stage parentStage = null) {
if (environment != null) {
return environment.resolveEnvVars(script, true, root.environment).findAll {
return environment.resolveEnvVars(script, true, root.environment, parentStage).findAll {
it.key in environment.getMap().keySet()
}.collect { k, v ->
"${k}=${v}"
Expand Down
Expand Up @@ -158,6 +158,9 @@ class JSONParser implements Parser {
if (j.node.has("agent")) {
stage.agent = parseAgent(j.append(JsonPointer.of("agent")))
}
if (j.node.has("parallelStages")) {
stage.parallelStages = parseStages(j.append(JsonPointer.of("parallelStages")))
}

JsonTree branches = j.append(JsonPointer.of("branches"))
branches?.node?.eachWithIndex { JsonNode entry, int i ->
Expand Down
Expand Up @@ -488,6 +488,9 @@ class ModelParser implements Parser {
case 'environment':
stage.environment = parseEnvironment(s)
break
case 'parallelStages':
stage.parallelStages = parseStages(s)
break
default:
errorCollector.error(stage, Messages.ModelParser_UnknownStageSection(name))
}
Expand Down
Expand Up @@ -601,20 +601,35 @@ class ModelValidatorImpl implements ModelValidator {
return valid
}

public boolean validateElement(@Nonnull ModelASTStage stage) {
public boolean validateElement(@Nonnull ModelASTStage stage, boolean isNested) {
boolean valid = true
if (stage.name == null) {
errorCollector.error(stage, Messages.ModelValidatorImpl_NoStageName())
valid = false
}
if (stage.branches.isEmpty()) {
errorCollector.error(stage, Messages.ModelValidatorImpl_NothingForStage(stage.name))
} else if (isNested && (stage.branches.size() > 1 || stage.parallelStages != null)) {
errorCollector.error(stage, Messages.ModelValidatorImpl_NoNestedWithinNestedStages())
valid = false
}
def branchNames = stage.branches.collect { it.name }
branchNames.findAll { branchNames.count(it) > 1 }.unique().each { bn ->
errorCollector.error(stage, Messages.ModelValidatorImpl_DuplicateParallelName(bn))
} else if (!stage.branches.isEmpty() && stage.parallelStages != null) {
errorCollector.error(stage, Messages.ModelValidatorImpl_BothStagesAndSteps(stage.name))
valid = false
} else if (stage.branches.isEmpty() && stage.parallelStages == null) {
errorCollector.error(stage, Messages.ModelValidatorImpl_NothingForStage(stage.name))
valid = false
} else if (stage.parallelStages != null) {
if (stage.agent != null) {
errorCollector.error(stage.agent, Messages.ModelValidatorImpl_AgentInNestedStages(stage.name))
valid = false
}
if (stage.tools != null) {
errorCollector.error(stage.tools, Messages.ModelValidatorImpl_ToolsInNestedStages(stage.name))
valid = false
}
} else {
def branchNames = stage.branches.collect { it.name }
branchNames.findAll { branchNames.count(it) > 1 }.unique().each { bn ->
errorCollector.error(stage, Messages.ModelValidatorImpl_DuplicateParallelName(bn))
valid = false
}
}

return valid
Expand Down
Expand Up @@ -116,7 +116,7 @@ ModelValidatorImpl.InvalidUnnamedParameterType=Expecting "{0}" but got "{1}" of
ModelValidatorImpl.NoSteps=No steps specified for branch
ModelValidatorImpl.RequiredSection=Missing required section "{0}"
ModelValidatorImpl.NoStageName=Stage does not have a name
ModelValidatorImpl.NothingForStage=Nothing to execute within stage "{0}"
ModelValidatorImpl.NothingForStage=No "steps" or "parallelStages" to execute within stage "{0}"
ModelValidatorImpl.NoStages=No stages specified
ModelValidatorImpl.DuplicateParallelName=Duplicate parallel branch name: "{0}"
ModelValidatorImpl.DuplicateStageName=Duplicate stage name: "{0}"
Expand All @@ -135,5 +135,9 @@ ModelValidatorImpl.NestedWhenNoArgs=No additional arguments are allowed for chil
ModelValidatorImpl.NoNestedWhenAllowed=No children when conditions are allowed for when condition "{0}".
ModelValidatorImpl.NestedWhenWithoutChildren=Nested when condition "{0}" requires at least one child condition.
ModelValidatorImpl.NestedWhenWrongChildrenCount=Nested when condition "{0}" requires exactly {1} child condition(s).
ModelValidatorImpl.BothStagesAndSteps=Only one of "parallelStages" or "steps" allowed for stage "{0}"
ModelValidatorImpl.AgentInNestedStages="agent" is not allowed in stage "{0}" as it contains parallel stages
ModelValidatorImpl.ToolsInNestedStages="tools" is not allowed in stage "{0}" as it contains parallel stages
ModelValidatorImpl.NoNestedWithinNestedStages=Parallel stages or branches can only be included in a top-level stage.

ModelInterpreter.NoNodeContext=Attempted to execute a step that requires a node context while 'agent none' was specified. Be sure to specify your own 'node { ... }' blocks when using 'agent none'.

0 comments on commit f2211f5

Please sign in to comment.