[JENKINS-49779] sequential stages inside parallel in Declarative syntax (#1778)
* [JENKINS-49779] Support for sequential stage groups in BlueOcean nodes API Signed-off-by: olivier lamy <olamy@apache.org>
This commit is contained in:
parent
90371826c7
commit
55db7d5f67
|
@ -19,6 +19,7 @@ import javax.annotation.Nullable;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
|
@ -43,7 +44,7 @@ public class FlowNodeWrapper {
|
|||
private Collection<Action> pipelineActions;
|
||||
|
||||
|
||||
public FlowNodeWrapper(@Nonnull FlowNode node, @Nonnull NodeRunStatus status, @Nonnull TimingInfo timingInfo, @Nonnull WorkflowRun run) {
|
||||
public FlowNodeWrapper(@Nonnull FlowNode node, @Nonnull NodeRunStatus status, @Nonnull TimingInfo timingInfo, @Nonnull WorkflowRun run) {
|
||||
this.node = node;
|
||||
this.status = status;
|
||||
this.timingInfo = timingInfo;
|
||||
|
@ -160,6 +161,11 @@ public class FlowNodeWrapper {
|
|||
return node.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "[id=" + node.getId() + ",displayName=" + this.displayName + ",type=" + this.type + "]";
|
||||
}
|
||||
|
||||
boolean hasBlockError(){
|
||||
return blockErrorAction != null
|
||||
&& blockErrorAction.getError() != null;
|
||||
|
|
|
@ -21,7 +21,7 @@ public class PipelineNodeContainerImpl extends BluePipelineNodeContainer {
|
|||
private final WorkflowRun run;
|
||||
private final Map<String, BluePipelineNode> nodeMap = new HashMap<>();
|
||||
|
||||
List<BluePipelineNode> nodes = new ArrayList<>();
|
||||
private final List<BluePipelineNode> nodes;
|
||||
private final Link self;
|
||||
|
||||
public PipelineNodeContainerImpl(WorkflowRun run, Link parentLink) {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package io.jenkins.blueocean.rest.impl.pipeline;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.model.Action;
|
||||
import io.jenkins.blueocean.rest.Reachable;
|
||||
import io.jenkins.blueocean.rest.hal.Link;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineNode;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineStep;
|
||||
import io.jenkins.blueocean.rest.model.BlueRun;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.ExecutionModelAction;
|
||||
import org.jenkinsci.plugins.workflow.actions.LabelAction;
|
||||
import org.jenkinsci.plugins.workflow.actions.NotExecutedNodeAction;
|
||||
import org.jenkinsci.plugins.workflow.actions.TimingAction;
|
||||
|
@ -49,6 +49,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
|
@ -68,6 +69,8 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
|
||||
public final Map<String, FlowNodeWrapper> nodeMap = new LinkedHashMap<>();
|
||||
|
||||
public final Map<String, Stack<FlowNodeWrapper>> stackPerEnd = new HashMap<>();
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PipelineNodeGraphVisitor.class);
|
||||
|
||||
private static final boolean isNodeVisitorDumpEnabled = Boolean.getBoolean("NODE-DUMP-ENABLED");
|
||||
|
@ -78,6 +81,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
private final ArrayDeque<FlowNode> pendingInputSteps = new ArrayDeque<>();
|
||||
|
||||
private final Stack<FlowNode> parallelBranchEndNodes = new Stack<>();
|
||||
private final Stack<FlowNode> parallelBranchStartNodes = new Stack<>();
|
||||
|
||||
private final InputAction inputAction;
|
||||
|
||||
|
@ -91,11 +95,14 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
|
||||
private final static String PARALLEL_SYNTHETIC_STAGE_NAME = "Parallel";
|
||||
|
||||
private final boolean declarative;
|
||||
|
||||
public PipelineNodeGraphVisitor(WorkflowRun run) {
|
||||
this.run = run;
|
||||
this.inputAction = run.getAction(InputAction.class);
|
||||
this.pipelineActions = new HashSet<>();
|
||||
this.pendingActionsForBranches = new HashMap<>();
|
||||
declarative = run.getAction(ExecutionModelAction.class) != null;
|
||||
FlowExecution execution = run.getExecution();
|
||||
if(execution!=null) {
|
||||
try {
|
||||
|
@ -150,10 +157,9 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
|
||||
//if block stage node push it to stack as it may have nested stages
|
||||
if(parallelEnd == null &&
|
||||
endNode instanceof StepEndNode
|
||||
&& !PipelineNodeUtil.isSyntheticStage(((StepEndNode) endNode).getStartNode()) //skip synthetic stages
|
||||
&& PipelineNodeUtil.isStage(((StepEndNode) endNode).getStartNode())) {
|
||||
|
||||
endNode instanceof StepEndNode
|
||||
&& !PipelineNodeUtil.isSyntheticStage(((StepEndNode) endNode).getStartNode()) //skip synthetic stages
|
||||
&& PipelineNodeUtil.isStage(((StepEndNode) endNode).getStartNode())) {
|
||||
|
||||
//XXX: There seems to be bug in eventing, chunkEnd is sent twice for the same FlowNode
|
||||
// Lets peek and if the last one is same as this endNode then skip adding it
|
||||
|
@ -187,14 +193,21 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
return;
|
||||
}
|
||||
|
||||
// its stage inside parallel, we skip it and clear nest stages collected inside parallel
|
||||
boolean parallelNestedStages = false;
|
||||
|
||||
// it's nested stages inside parallel so let's collect them later
|
||||
if(parallelEnd != null){
|
||||
return;
|
||||
// nested stages not supported in scripted pipeline.
|
||||
if(!isDeclarative()){
|
||||
return;
|
||||
}
|
||||
parallelNestedStages = true;
|
||||
}
|
||||
|
||||
if(!nestedStages.empty()){
|
||||
nestedStages.pop(); //we throw away nested stages
|
||||
if(!nestedStages.empty()){ //there is still a nested stage, return
|
||||
nestedStages.pop(); //we throw away first nested stage
|
||||
// nested stages not supported in scripted pipeline.
|
||||
if(!nestedStages.isEmpty()&&!isDeclarative()){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -218,8 +231,8 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
status = new NodeRunStatus(GenericStatus.NOT_EXECUTED);
|
||||
}else if(chunk.getLastNode() != null){
|
||||
status = new NodeRunStatus(StatusAndTiming
|
||||
.computeChunkStatus2(run, chunk.getNodeBefore(),
|
||||
firstExecuted, chunk.getLastNode(), chunk.getNodeAfter()));
|
||||
.computeChunkStatus2(run, chunk.getNodeBefore(),
|
||||
firstExecuted, chunk.getLastNode(), chunk.getNodeAfter()));
|
||||
}else{
|
||||
status = new NodeRunStatus(firstExecuted);
|
||||
}
|
||||
|
@ -228,14 +241,16 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
status = new NodeRunStatus(BlueRun.BlueRunResult.UNKNOWN, BlueRun.BlueRunState.PAUSED);
|
||||
}
|
||||
FlowNodeWrapper stage = new FlowNodeWrapper(chunk.getFirstNode(),
|
||||
status, times, run);
|
||||
status, times, run);
|
||||
|
||||
stage.setCauseOfFailure(PipelineNodeUtil.getCauseOfBlockage(stage.getNode(), agentNode));
|
||||
accumulatePipelineActions(chunk.getFirstNode());
|
||||
stage.setPipelineActions(drainPipelineActions());
|
||||
|
||||
nodes.push(stage);
|
||||
nodeMap.put(stage.getId(), stage);
|
||||
if(!parallelNestedStages){
|
||||
nodes.push( stage );
|
||||
nodeMap.put( stage.getId(), stage );
|
||||
}
|
||||
if(!skippedStage && !parallelBranches.isEmpty()){
|
||||
Iterator<FlowNodeWrapper> branches = parallelBranches.descendingIterator();
|
||||
while(branches.hasNext()){
|
||||
|
@ -244,13 +259,24 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
stage.addEdge(p);
|
||||
}
|
||||
}else{
|
||||
if(parallelNestedStages) {
|
||||
String endId = parallelBranchEndNodes.peek().getId();
|
||||
Stack<FlowNodeWrapper> stack = stackPerEnd.get(endId);
|
||||
if(stack==null){
|
||||
stack=new Stack<>();
|
||||
stackPerEnd.put(endId, stack);
|
||||
}
|
||||
stack.add(stage);
|
||||
}
|
||||
if(nextStage != null) {
|
||||
nextStage.addParent(stage);
|
||||
stage.addEdge(nextStage);
|
||||
}
|
||||
}
|
||||
parallelBranches.clear();
|
||||
this.nextStage = stage;
|
||||
if(!parallelNestedStages) {
|
||||
this.nextStage = stage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -265,14 +291,14 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
public void parallelStart(@Nonnull FlowNode parallelStartNode, @Nonnull FlowNode branchNode, @Nonnull ForkScanner scanner) {
|
||||
if(isNodeVisitorDumpEnabled) {
|
||||
dump(String.format("parallelStart=> id: %s, name: %s, function: %s", parallelStartNode.getId(),
|
||||
parallelStartNode.getDisplayName(), parallelStartNode.getDisplayFunctionName()));
|
||||
parallelStartNode.getDisplayName(), parallelStartNode.getDisplayFunctionName()));
|
||||
dump(String.format("\tbranch=> id: %s, name: %s, function: %s", branchNode.getId(),
|
||||
branchNode.getDisplayName(), branchNode.getDisplayFunctionName()));
|
||||
branchNode.getDisplayName(), branchNode.getDisplayFunctionName()));
|
||||
}
|
||||
|
||||
if(nestedbranches.size() != parallelBranchEndNodes.size()){
|
||||
logger.debug(String.format("nestedBranches size: %s not equal to parallelBranchEndNodes: %s",
|
||||
nestedbranches.size(), parallelBranchEndNodes.size()));
|
||||
nestedbranches.size(), parallelBranchEndNodes.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -323,20 +349,44 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
}
|
||||
|
||||
branch.setPipelineActions(branchActions);
|
||||
|
||||
if(nextStage!=null) {
|
||||
// do we have nested sequential stages for this parallel branch?
|
||||
Stack<FlowNodeWrapper> stack = stackPerEnd.get(endNode.getId());
|
||||
if(stack!=null&&!stack.isEmpty()){
|
||||
// yes so we can rebuild the graph here
|
||||
// we don't want the first one as it's a duplicate of the parallel parent but with stage type so rid of it
|
||||
FlowNodeWrapper flowNodeWrapper = stack.pop();
|
||||
if(stack.isEmpty()){
|
||||
if(nextStage!=null) {
|
||||
branch.addEdge(nextStage);
|
||||
}
|
||||
parallelBranches.push(branch);
|
||||
continue;
|
||||
}
|
||||
// if name is different it's not a dummy 'duplicate' stage with the same name so let's add it to the graph
|
||||
if(!StringUtils.equals( flowNodeWrapper.getDisplayName(), branch.getDisplayName())){
|
||||
branch.addEdge(flowNodeWrapper);
|
||||
flowNodeWrapper.addParent(branch);
|
||||
nodes.add(flowNodeWrapper);
|
||||
}
|
||||
flowNodeWrapper = stack.pop();
|
||||
// here we rebuild parent/edge relation
|
||||
branch.addEdge(flowNodeWrapper);
|
||||
flowNodeWrapper.addParent(branch);
|
||||
nodes.add(flowNodeWrapper);
|
||||
stack.stream().forEach(nodeWrapper->{
|
||||
nodes.peekLast().addEdge(nodeWrapper);
|
||||
nodeWrapper.addParent(nodes.peekLast());
|
||||
nodes.add( nodeWrapper );
|
||||
});
|
||||
}
|
||||
else if(nextStage!=null) {
|
||||
branch.addEdge(nextStage);
|
||||
}
|
||||
parallelBranches.push(branch);
|
||||
}
|
||||
|
||||
FlowNodeWrapper[] sortedBranches = parallelBranches.toArray(new FlowNodeWrapper[parallelBranches.size()]);
|
||||
Arrays.sort(sortedBranches, new Comparator<FlowNodeWrapper>() {
|
||||
@Override
|
||||
public int compare(FlowNodeWrapper o1, FlowNodeWrapper o2) {
|
||||
return o1.getDisplayName().compareTo(o2.getDisplayName());
|
||||
}
|
||||
});
|
||||
Arrays.sort(sortedBranches, Comparator.comparing(FlowNodeWrapper::getDisplayName));
|
||||
|
||||
parallelBranches.clear();
|
||||
for(int i=0; i< sortedBranches.length; i++){
|
||||
|
@ -355,10 +405,10 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
public void parallelEnd(@Nonnull FlowNode parallelStartNode, @Nonnull FlowNode parallelEndNode, @Nonnull ForkScanner scanner) {
|
||||
if(isNodeVisitorDumpEnabled) {
|
||||
dump(String.format("parallelEnd=> id: %s, name: %s, function: %s", parallelEndNode.getId(),
|
||||
parallelEndNode.getDisplayName(), parallelEndNode.getDisplayFunctionName()));
|
||||
parallelEndNode.getDisplayName(), parallelEndNode.getDisplayFunctionName()));
|
||||
if(parallelEndNode instanceof StepEndNode){
|
||||
dump(String.format("parallelEnd=> id: %s, StartNode: %s, name: %s, function: %s", parallelEndNode.getId(),
|
||||
((StepEndNode) parallelEndNode).getStartNode().getId(),((StepEndNode) parallelEndNode).getStartNode().getDisplayName(), ((StepEndNode) parallelEndNode).getStartNode().getDisplayFunctionName()));
|
||||
((StepEndNode) parallelEndNode).getStartNode().getId(),((StepEndNode) parallelEndNode).getStartNode().getDisplayName(), ((StepEndNode) parallelEndNode).getStartNode().getDisplayFunctionName()));
|
||||
}
|
||||
}
|
||||
captureOrphanParallelBranches();
|
||||
|
@ -381,14 +431,15 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
public void parallelBranchEnd(@Nonnull FlowNode parallelStartNode, @Nonnull FlowNode branchEndNode, @Nonnull ForkScanner scanner) {
|
||||
if(isNodeVisitorDumpEnabled) {
|
||||
dump(String.format("parallelBranchEnd=> id: %s, name: %s, function: %s, type: %s", branchEndNode.getId(),
|
||||
branchEndNode.getDisplayName(), branchEndNode.getDisplayFunctionName(), branchEndNode.getClass()));
|
||||
branchEndNode.getDisplayName(), branchEndNode.getDisplayFunctionName(), branchEndNode.getClass()));
|
||||
if(branchEndNode instanceof StepEndNode){
|
||||
dump(String.format("parallelBranchEnd=> id: %s, StartNode: %s, name: %s, function: %s", branchEndNode.getId(),
|
||||
((StepEndNode) branchEndNode).getStartNode().getId(),((StepEndNode) branchEndNode).getStartNode().getDisplayName(),
|
||||
((StepEndNode) branchEndNode).getStartNode().getDisplayFunctionName()));
|
||||
((StepEndNode) branchEndNode).getStartNode().getId(),((StepEndNode) branchEndNode).getStartNode().getDisplayName(),
|
||||
((StepEndNode) branchEndNode).getStartNode().getDisplayFunctionName()));
|
||||
}
|
||||
}
|
||||
parallelBranchEndNodes.add(branchEndNode);
|
||||
parallelBranchStartNodes.add(parallelStartNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -413,7 +464,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
chunk.setPauseTimeMillis(chunk.getPauseTimeMillis()+pause);
|
||||
|
||||
if(atomNode instanceof StepAtomNode
|
||||
&& PipelineNodeUtil.isPausedForInputStep((StepAtomNode) atomNode, inputAction)){
|
||||
&& PipelineNodeUtil.isPausedForInputStep((StepAtomNode) atomNode, inputAction)){
|
||||
pendingInputSteps.add(atomNode);
|
||||
}
|
||||
}
|
||||
|
@ -458,16 +509,9 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
|
||||
@Override
|
||||
public List<BluePipelineNode> getPipelineNodes(final Link parent) {
|
||||
List<BluePipelineNode> nodes = new ArrayList<>();
|
||||
for(FlowNodeWrapper n: this.nodes){
|
||||
nodes.add(new PipelineNodeImpl(n,new Reachable() {
|
||||
@Override
|
||||
public Link getLink() {
|
||||
return parent;
|
||||
}
|
||||
}, run));
|
||||
}
|
||||
return nodes;
|
||||
return this.nodes.stream()
|
||||
.map( n -> new PipelineNodeImpl(n, () -> parent, run))
|
||||
.collect( Collectors.toList() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -479,24 +523,21 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
}
|
||||
DepthFirstScanner depthFirstScanner = new DepthFirstScanner();
|
||||
//If blocked scope, get the end node
|
||||
FlowNode n = depthFirstScanner.findFirstMatch(execution.getCurrentHeads(), new Predicate<FlowNode>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable FlowNode input) {
|
||||
return (input!= null && input.getId().equals(nodeId) &&
|
||||
(PipelineNodeUtil.isStage(input) || PipelineNodeUtil.isParallelBranch(input)));
|
||||
}
|
||||
});
|
||||
FlowNode n = depthFirstScanner
|
||||
.findFirstMatch(execution.getCurrentHeads(),
|
||||
input -> (input!= null
|
||||
&& input.getId().equals(nodeId)
|
||||
&& (PipelineNodeUtil.isStage(input) || PipelineNodeUtil.isParallelBranch(input))));
|
||||
|
||||
if(n == null){ //if no node found or the node is not stage or parallel we return empty steps
|
||||
return Collections.emptyList();
|
||||
}
|
||||
PipelineStepVisitor visitor = new PipelineStepVisitor(run, n);
|
||||
ForkScanner.visitSimpleChunks(execution.getCurrentHeads(), visitor, new StageChunkFinder());
|
||||
List<BluePipelineStep> steps = new ArrayList<>();
|
||||
for(FlowNodeWrapper node: visitor.getSteps()){
|
||||
steps.add(new PipelineStepImpl(node, parent));
|
||||
}
|
||||
return steps;
|
||||
return visitor.getSteps()
|
||||
.stream()
|
||||
.map( node -> new PipelineStepImpl(node, parent))
|
||||
.collect( Collectors.toList() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -507,11 +548,10 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
}
|
||||
PipelineStepVisitor visitor = new PipelineStepVisitor(run, null);
|
||||
ForkScanner.visitSimpleChunks(execution.getCurrentHeads(), visitor, new StageChunkFinder());
|
||||
List<BluePipelineStep> steps = new ArrayList<>();
|
||||
for(FlowNodeWrapper node: visitor.getSteps()){
|
||||
steps.add(new PipelineStepImpl(node, parent));
|
||||
}
|
||||
return steps;
|
||||
return visitor.getSteps()
|
||||
.stream()
|
||||
.map( node -> new PipelineStepImpl(node, parent))
|
||||
.collect( Collectors.toList() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -562,8 +602,8 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
thatStage = futureNode;
|
||||
futureStage = futureNode;
|
||||
}else if(futureNode.type == FlowNodeWrapper.NodeType.PARALLEL &&
|
||||
futureNodeParent != null &&
|
||||
futureNodeParent.equals(latestNode.getFirstParent())){
|
||||
futureNodeParent != null &&
|
||||
futureNodeParent.equals(latestNode.getFirstParent())){
|
||||
thatStage = futureNode.getFirstParent();
|
||||
if(futureNode.edges.size() > 0){
|
||||
futureStage = futureNode.edges.get(0);
|
||||
|
@ -591,8 +631,8 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
}
|
||||
}
|
||||
FlowNodeWrapper n = new FlowNodeWrapper(futureNode.getNode(),
|
||||
new NodeRunStatus(null,null),
|
||||
new TimingInfo(), run);
|
||||
new NodeRunStatus(null,null),
|
||||
new TimingInfo(), run);
|
||||
n.addEdges(futureNode.edges);
|
||||
n.addParents(futureNode.getParents());
|
||||
currentNodes.add(n);
|
||||
|
@ -600,12 +640,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
}
|
||||
List<BluePipelineNode> newNodes = new ArrayList<>();
|
||||
for(FlowNodeWrapper n: currentNodes){
|
||||
newNodes.add(new PipelineNodeImpl(n,new Reachable() {
|
||||
@Override
|
||||
public Link getLink() {
|
||||
return parent;
|
||||
}
|
||||
},run));
|
||||
newNodes.add(new PipelineNodeImpl(n,() -> parent,run));
|
||||
}
|
||||
return newNodes;
|
||||
}
|
||||
|
@ -613,7 +648,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
|
||||
private void captureOrphanParallelBranches(){
|
||||
if(!parallelBranches.isEmpty() && (firstExecuted == null
|
||||
|| !PipelineNodeUtil.isStage(firstExecuted)
|
||||
|| !PipelineNodeUtil.isStage(firstExecuted)
|
||||
)){
|
||||
FlowNodeWrapper synStage = createParallelSyntheticNode();
|
||||
if(synStage!=null) {
|
||||
|
@ -649,7 +684,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
parents = new ArrayList<>();
|
||||
}
|
||||
FlowNode syntheticNode = new FlowNode(firstBranch.getNode().getExecution(),
|
||||
createSyntheticStageId(firstNodeId, PARALLEL_SYNTHETIC_STAGE_NAME), parents){
|
||||
createSyntheticStageId(firstNodeId, PARALLEL_SYNTHETIC_STAGE_NAME), parents){
|
||||
@Override
|
||||
public void save() throws IOException {
|
||||
// no-op to avoid JENKINS-45892 violations from serializing the synthetic FlowNode.
|
||||
|
@ -689,9 +724,9 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
}
|
||||
|
||||
BlueRun.BlueRunState state = isCompleted ? BlueRun.BlueRunState.FINISHED :
|
||||
(isPaused ? BlueRun.BlueRunState.PAUSED : BlueRun.BlueRunState.RUNNING);
|
||||
(isPaused ? BlueRun.BlueRunState.PAUSED : BlueRun.BlueRunState.RUNNING);
|
||||
BlueRun.BlueRunResult result = isFailure ? BlueRun.BlueRunResult.FAILURE :
|
||||
(isUnknown ? BlueRun.BlueRunResult.UNKNOWN : BlueRun.BlueRunResult.SUCCESS);
|
||||
(isUnknown ? BlueRun.BlueRunResult.UNKNOWN : BlueRun.BlueRunResult.SUCCESS);
|
||||
|
||||
TimingInfo timingInfo = new TimingInfo(duration,pauseDuration, startTime);
|
||||
|
||||
|
@ -706,6 +741,10 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
return synStage;
|
||||
}
|
||||
|
||||
public boolean isDeclarative() {
|
||||
return declarative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create id of synthetic stage in a deterministic base.
|
||||
*
|
||||
|
@ -717,4 +756,4 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
|
|||
private @Nonnull String createSyntheticStageId(@Nonnull String firstNodeId, @Nonnull String syntheticStageName){
|
||||
return String.format("%s-%s-synthetic",firstNodeId, syntheticStageName.toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,6 +49,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.LogManager;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.jenkins.blueocean.auth.jwt.JwtToken.X_BLUEOCEAN_JWT;
|
||||
import static org.junit.Assert.fail;
|
||||
|
@ -344,15 +345,12 @@ public abstract class PipelineBaseTest{
|
|||
}
|
||||
|
||||
protected List<FlowNode> getStages(NodeGraphBuilder builder){
|
||||
List<FlowNode> nodes = new ArrayList<>();
|
||||
for(FlowNodeWrapper node: builder.getPipelineNodes()){
|
||||
if(node.type == FlowNodeWrapper.NodeType.STAGE){
|
||||
nodes.add(node.getNode());
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
return builder.getPipelineNodes().stream()
|
||||
.filter( nodeWrapper -> nodeWrapper.type == FlowNodeWrapper.NodeType.STAGE )
|
||||
.map( nodeWrapper -> nodeWrapper.getNode() )
|
||||
.collect( Collectors.toList() );
|
||||
}
|
||||
|
||||
protected List<FlowNode> getAllSteps(WorkflowRun run){
|
||||
PipelineStepVisitor visitor = new PipelineStepVisitor(run, null);
|
||||
ForkScanner.visitSimpleChunks(run.getExecution().getCurrentHeads(), visitor, new StageChunkFinder());
|
||||
|
@ -363,25 +361,18 @@ public abstract class PipelineBaseTest{
|
|||
return steps;
|
||||
}
|
||||
protected List<FlowNode> getStagesAndParallels(NodeGraphBuilder builder){
|
||||
List<FlowNode> nodes = new ArrayList<>();
|
||||
for(FlowNodeWrapper node: builder.getPipelineNodes()){
|
||||
if(node.type == FlowNodeWrapper.NodeType.STAGE || node.type == FlowNodeWrapper.NodeType.PARALLEL){
|
||||
nodes.add(node.getNode());
|
||||
}
|
||||
}
|
||||
return builder.getPipelineNodes().stream()
|
||||
.filter( nodeWrapper -> nodeWrapper.type == FlowNodeWrapper.NodeType.PARALLEL || nodeWrapper.type == FlowNodeWrapper.NodeType.STAGE)
|
||||
.map( nodeWrapper -> nodeWrapper.getNode() )
|
||||
.collect( Collectors.toList() );
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
protected List<FlowNode> getParallelNodes(NodeGraphBuilder builder){
|
||||
List<FlowNode> nodes = new ArrayList<>();
|
||||
for(FlowNodeWrapper node: builder.getPipelineNodes()){
|
||||
if(node.type == FlowNodeWrapper.NodeType.PARALLEL){
|
||||
nodes.add(node.getNode());
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
return builder.getPipelineNodes().stream()
|
||||
.filter( nodeWrapper -> nodeWrapper.type == FlowNodeWrapper.NodeType.PARALLEL )
|
||||
.map( nodeWrapper -> nodeWrapper.getNode() )
|
||||
.collect( Collectors.toList() );
|
||||
}
|
||||
|
||||
protected String getHrefFromLinks(Map resp, String link){
|
||||
|
|
|
@ -4,13 +4,17 @@ import com.google.common.base.Charsets;
|
|||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Resources;
|
||||
import com.mashape.unirest.http.Unirest;
|
||||
import hudson.FilePath;
|
||||
import hudson.model.FreeStyleProject;
|
||||
import hudson.model.Result;
|
||||
import hudson.model.Run;
|
||||
import hudson.model.Slave;
|
||||
import hudson.model.queue.QueueTaskFuture;
|
||||
import hudson.util.RunList;
|
||||
import io.jenkins.blueocean.listeners.NodeDownstreamBuildAction;
|
||||
import io.jenkins.blueocean.rest.hal.Link;
|
||||
import io.jenkins.blueocean.rest.model.BluePipelineNode;
|
||||
import jenkins.branch.BranchSource;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.plugins.git.GitSCMSource;
|
||||
|
@ -60,6 +64,7 @@ public class PipelineNodeTest extends PipelineBaseTest {
|
|||
@BeforeClass
|
||||
public static void setupStatic() throws Exception {
|
||||
System.setProperty("NODE-DUMP-ENABLED", "true");//tests node dump code path, also helps debug test failure
|
||||
Unirest.setTimeouts( 10000, 600000000 );
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -454,12 +459,12 @@ public class PipelineNodeTest extends PipelineBaseTest {
|
|||
" },\n" +
|
||||
" secondBranch: {"+
|
||||
" echo 'first Branch'\n" +
|
||||
" stage('firstBranchTest') {"+
|
||||
" echo 'running firstBranchTest'\n" +
|
||||
" sh 'sleep 1'\n" +
|
||||
" }\n"+
|
||||
" echo 'first Branch end'\n" +
|
||||
" },\n"+
|
||||
" stage('firstBranchTest') {"+
|
||||
" echo 'running firstBranchTest'\n" +
|
||||
" sh 'sleep 1'\n" +
|
||||
" }\n"+
|
||||
" echo 'first Branch end'\n" +
|
||||
" },\n"+
|
||||
" failFast: false\n" +
|
||||
" } \n" +
|
||||
" stage ('deploy') { " +
|
||||
|
@ -478,7 +483,8 @@ public class PipelineNodeTest extends PipelineBaseTest {
|
|||
j.assertBuildStatusSuccess(b1);
|
||||
|
||||
|
||||
NodeGraphBuilder builder = NodeGraphBuilder.NodeGraphBuilderFactory.getInstance(b1);
|
||||
PipelineNodeGraphVisitor builder = new PipelineNodeGraphVisitor(b1);
|
||||
assertFalse( builder.isDeclarative() );
|
||||
List<FlowNode> stages = getStages(builder);
|
||||
List<FlowNode> parallels = getParallelNodes(builder);
|
||||
|
||||
|
@ -2357,6 +2363,79 @@ public class PipelineNodeTest extends PipelineBaseTest {
|
|||
assertEquals("Wait for interactive input", steps.get(3).get("displayName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Issue("JENKINS-49050")
|
||||
public void parallelStagesGroupsAndNestedStages() throws Exception {
|
||||
WorkflowJob p = createWorkflowJobWithJenkinsfile( getClass(), "parallelStagesGroupsAndStages.jenkinsfile");
|
||||
Slave s = j.createOnlineSlave();
|
||||
s.setLabelString( "foo" );
|
||||
s.setNumExecutors(2);
|
||||
|
||||
// Run until completed
|
||||
WorkflowRun run = p.scheduleBuild2( 0).waitForStart();
|
||||
j.waitForCompletion( run );
|
||||
|
||||
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
|
||||
assertTrue( pipelineNodeGraphVisitor.isDeclarative() );
|
||||
|
||||
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
|
||||
|
||||
FlowNodeWrapper flowNodeWrapper = wrappers.get( 0 );
|
||||
assertEquals( "top", flowNodeWrapper.getDisplayName() );
|
||||
assertEquals( 2, flowNodeWrapper.edges.size() );
|
||||
|
||||
flowNodeWrapper = wrappers.get( 1 );
|
||||
assertEquals( "first", flowNodeWrapper.getDisplayName() );
|
||||
assertEquals( 1, flowNodeWrapper.edges.size() );
|
||||
assertEquals( 1, flowNodeWrapper.getParents().size() );
|
||||
|
||||
assertEquals( "first-inner-first", flowNodeWrapper.edges.get( 0 ).getDisplayName() );
|
||||
|
||||
assertEquals(7, wrappers.size());
|
||||
|
||||
List<Map> nodes = get("/organizations/jenkins/pipelines/" + p.getName() + "/runs/1/nodes/", List.class);
|
||||
assertEquals(7, nodes.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void nestedStagesGroups() throws Exception {
|
||||
WorkflowJob p = createWorkflowJobWithJenkinsfile( getClass(), "nestedStagesGroups.jenkinsfile");
|
||||
Slave s = j.createOnlineSlave();
|
||||
s.setLabelString( "foo" );
|
||||
s.setNumExecutors(4);
|
||||
|
||||
// Run until completed
|
||||
WorkflowRun run = p.scheduleBuild2( 0).waitForStart();
|
||||
j.waitForCompletion( run );
|
||||
|
||||
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
|
||||
assertTrue( pipelineNodeGraphVisitor.isDeclarative() );
|
||||
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
|
||||
assertEquals(7, wrappers.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Issue("JENKINS-49050")
|
||||
public void parallelStagesNonNested() throws Exception {
|
||||
WorkflowJob p = createWorkflowJobWithJenkinsfile( getClass(), "parallelStagesNonNested.jenkinsfile");
|
||||
Slave s = j.createOnlineSlave();
|
||||
s.setLabelString( "foo" );
|
||||
s.setNumExecutors(2);
|
||||
|
||||
// Run until completed
|
||||
WorkflowRun run = p.scheduleBuild2( 0).waitForStart();
|
||||
j.waitForCompletion( run );
|
||||
|
||||
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
|
||||
|
||||
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
|
||||
assertEquals(3, wrappers.size());
|
||||
|
||||
List<Map> nodes = get("/organizations/jenkins/pipelines/" + p.getName() + "/runs/1/nodes/", List.class);
|
||||
assertEquals(3, nodes.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pipelineLogError() throws Exception {
|
||||
String script = "def foo = null\n" +
|
||||
|
@ -2425,50 +2504,51 @@ public class PipelineNodeTest extends PipelineBaseTest {
|
|||
WorkflowJob job1 = j.jenkins.createProject(WorkflowJob.class, "pipeline1");
|
||||
job1.setDefinition(new CpsFlowDefinition(script, false));
|
||||
WorkflowRun b1 = job1.scheduleBuild2(0).get();
|
||||
j.assertBuildStatus(Result.SUCCESS, b1);
|
||||
WorkflowRun run = j.waitForCompletion( b1 );
|
||||
j.assertBuildStatus(Result.SUCCESS, run);
|
||||
|
||||
List<Map> resp = get("/organizations/jenkins/pipelines/pipeline1/runs/1/nodes/", List.class);
|
||||
|
||||
Assert.assertEquals(3, resp.size());
|
||||
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
|
||||
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
|
||||
|
||||
Assert.assertEquals(3, wrappers.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orphanParallels2() throws Exception{
|
||||
String script = "stage(\"stage1\"){\n" +
|
||||
" echo \"stage 1...\"\n" +
|
||||
"}\n" +
|
||||
"parallel('branch1':{\n" +
|
||||
" node {\n" +
|
||||
" stage('Setup') {\n" +
|
||||
" sh 'echo \"Setup...\"'\n" +
|
||||
" }\n" +
|
||||
" stage('Unit and Integration Tests') {\n" +
|
||||
" sh 'echo \"Unit and Integration Tests...\"'\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}, 'branch3': {\n" +
|
||||
" node {\n" +
|
||||
" stage('Setup') {\n" +
|
||||
" sh 'echo \"Branch3 setup...\"'\n" +
|
||||
" }\n" +
|
||||
" stage('Unit and Integration Tests') {\n" +
|
||||
" echo '\"my command to execute tests\"'\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}, 'branch2': {\n" +
|
||||
" node {\n" +
|
||||
" stage('Setup') {\n" +
|
||||
" sh 'echo \"Branch2 setup...\"'\n" +
|
||||
" }\n" +
|
||||
" stage('Unit and Integration Tests') {\n" +
|
||||
" echo '\"my command to execute tests\"'\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"})\n" +
|
||||
"stage(\"stage2\"){\n" +
|
||||
" echo \"stage 2...\"\n" +
|
||||
"}";
|
||||
" echo \"stage 1...\"\n" +
|
||||
"}\n" +
|
||||
"parallel('branch1':{\n" +
|
||||
" node {\n" +
|
||||
" stage('Setup') {\n" +
|
||||
" sh 'echo \"Setup...\"'\n" +
|
||||
" }\n" +
|
||||
" stage('Unit and Integration Tests') {\n" +
|
||||
" sh 'echo \"Unit and Integration Tests...\"'\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}, 'branch3': {\n" +
|
||||
" node {\n" +
|
||||
" stage('Setup') {\n" +
|
||||
" sh 'echo \"Branch3 setup...\"'\n" +
|
||||
" }\n" +
|
||||
" stage('Unit and Integration Tests') {\n" +
|
||||
" echo '\"my command to execute tests\"'\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}, 'branch2': {\n" +
|
||||
" node {\n" +
|
||||
" stage('Setup') {\n" +
|
||||
" sh 'echo \"Branch2 setup...\"'\n" +
|
||||
" }\n" +
|
||||
" stage('Unit and Integration Tests') {\n" +
|
||||
" echo '\"my command to execute tests\"'\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"})\n" +
|
||||
"stage(\"stage2\"){\n" +
|
||||
" echo \"stage 2...\"\n" +
|
||||
"}";
|
||||
WorkflowJob job1 = j.jenkins.createProject(WorkflowJob.class, "pipeline1");
|
||||
job1.setDefinition(new CpsFlowDefinition(script, false));
|
||||
WorkflowRun b1 = job1.scheduleBuild2(0).get();
|
||||
|
@ -2514,21 +2594,6 @@ public class PipelineNodeTest extends PipelineBaseTest {
|
|||
assertEquals(0, edges.size());
|
||||
}
|
||||
}
|
||||
|
||||
Map synNode = nodes.get(1);
|
||||
|
||||
List<Map> edges = (List<Map>) synNode.get("edges");
|
||||
|
||||
Map n = get("/organizations/jenkins/pipelines/pipeline1/runs/1/nodes/"+ synNode.get("id") +"/", Map.class);
|
||||
List<Map> receivedEdges = (List<Map>) n.get("edges");
|
||||
assertNotNull(n);
|
||||
assertEquals(synNode.get("displayName"), n.get("displayName"));
|
||||
assertEquals(synNode.get("id"), n.get("id"));
|
||||
|
||||
Assert.assertEquals(3, edges.size());
|
||||
assertEquals(edges.get(0).get("id"), receivedEdges.get(0).get("id"));
|
||||
assertEquals(edges.get(1).get("id"), receivedEdges.get(1).get("id"));
|
||||
assertEquals(edges.get(2).get("id"), receivedEdges.get(2).get("id"));
|
||||
}
|
||||
|
||||
@Issue("JENKINS-47158")
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage("top") {
|
||||
stages {
|
||||
stage("first") {
|
||||
stages {
|
||||
stage("first-inner-first") {
|
||||
steps {
|
||||
echo "#1 second-inner-first"
|
||||
echo "#2 second-inner-first"
|
||||
echo "#3 second-inner-first"
|
||||
echo "#4 second-inner-first"
|
||||
}
|
||||
}
|
||||
stage("first-inner-second") {
|
||||
steps {
|
||||
echo "first-inner-second"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
stage("second") {
|
||||
stages {
|
||||
stage("second-inner-first") {
|
||||
steps {
|
||||
echo "second-inner-first"
|
||||
}
|
||||
}
|
||||
stage("second-inner-second") {
|
||||
when {
|
||||
expression {
|
||||
return false
|
||||
}
|
||||
}
|
||||
steps {
|
||||
echo "WE SHOULD NEVER GET HERE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage("top") {
|
||||
parallel {
|
||||
stage("first") {
|
||||
stages {
|
||||
stage("first-inner-first") {
|
||||
steps {
|
||||
echo "stage first-inner-first"
|
||||
}
|
||||
}
|
||||
stage("first-inner-second") {
|
||||
steps {
|
||||
echo "stage first-inner-second"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("second") {
|
||||
stages {
|
||||
stage("second-inner-first") {
|
||||
steps {
|
||||
echo "stage second-inner-first"
|
||||
}
|
||||
}
|
||||
stage("second-inner-second") {
|
||||
steps {
|
||||
echo "stage second-inner-second"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage("top") {
|
||||
parallel {
|
||||
stage("first") {
|
||||
steps {
|
||||
echo "stage first"
|
||||
}
|
||||
}
|
||||
stage("second") {
|
||||
steps {
|
||||
echo "stage second"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,4 +4,5 @@ handlers=java.util.logging.ConsoleHandler
|
|||
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
||||
java.util.logging.ConsoleHandler.level = ALL
|
||||
|
||||
org.apache.http.wire.level = FINE
|
||||
#org.apache.http.wire.level = FINE
|
||||
io.jenkins.blueocean.rest.impl.pipeline.level = FINE
|
||||
|
|
22
pom.xml
22
pom.xml
|
@ -321,7 +321,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jenkinsci.plugins</groupId>
|
||||
<artifactId>pipeline-model-definition</artifactId>
|
||||
<version>1.3</version>
|
||||
<version>1.3.1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
|
@ -332,27 +332,27 @@
|
|||
<dependency>
|
||||
<groupId>org.jenkinsci.plugins</groupId>
|
||||
<artifactId>pipeline-stage-tags-metadata</artifactId>
|
||||
<version>1.3</version>
|
||||
<version>1.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkinsci.plugins</groupId>
|
||||
<artifactId>pipeline-model-api</artifactId>
|
||||
<version>1.3</version>
|
||||
<version>1.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-cps</artifactId>
|
||||
<version>2.46</version>
|
||||
<version>2.54</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-api</artifactId>
|
||||
<version>2.25</version>
|
||||
<version>2.28</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-job</artifactId>
|
||||
<version>2.17</version>
|
||||
<version>2.23</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
|
@ -362,17 +362,17 @@
|
|||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-multibranch</artifactId>
|
||||
<version>2.17</version>
|
||||
<version>2.20</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-step-api</artifactId>
|
||||
<version>2.14</version>
|
||||
<version>2.16</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-durable-task-step</artifactId>
|
||||
<version>2.18</version>
|
||||
<version>2.19</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
|
@ -388,7 +388,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-basic-steps</artifactId>
|
||||
<version>2.6</version>
|
||||
<version>2.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
|
@ -466,7 +466,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>apache-httpcomponents-client-4-api</artifactId>
|
||||
<version>4.5.5-2.0</version>
|
||||
<version>4.5.5-3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Other -->
|
||||
|
|
Loading…
Reference in New Issue