diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java index f353630639..f7ace0e028 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java @@ -19,6 +19,7 @@ import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.ScalarNode; +import org.yaml.snakeyaml.nodes.SequenceNode; /** * Adapts a SnakeYaml ast node as a {@link DynamicSchemaContext} (so it @@ -77,4 +78,8 @@ public boolean isAtomic() { public boolean isMap() { return mapNode!=null; } + @Override + public boolean isSequence() { + return node instanceof SequenceNode; + } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java index 7b4131022e..fb14ae964b 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java @@ -56,6 +56,11 @@ public boolean isAtomic() { public boolean isMap() { return false; } + + @Override + public boolean isSequence() { + return false; + } }; /** @@ -98,5 +103,10 @@ public boolean isMap() { * Returns true if the current node is a Mapping node */ boolean isMap(); + + /** + * Returns true if the current node is a Sequence node + */ + boolean isSequence(); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java index b1079ad386..8bb73eafb2 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java @@ -81,6 +81,11 @@ public boolean isAtomic() { @Override public boolean isMap() { + return !computeDefinedProperties().isEmpty(); + } + + @Override + public boolean isSequence() { return false; } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index a4c5518088..7f7bebe862 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -38,6 +38,7 @@ import org.springframework.ide.vscode.commons.util.ValueParser; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanAndSequenceUnion; import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint; import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints; import org.springframework.ide.vscode.commons.yaml.snippet.TypeBasedSnippetProvider; @@ -168,19 +169,27 @@ public YType yunion(String name, YType... types) { } return new YBeanUnionType(name, beanTypes); } + ArrayList beans = new ArrayList<>(types.length); ArrayList maps = new ArrayList<>(types.length); ArrayList atoms = new ArrayList<>(types.length); + ArrayList arrays = new ArrayList<>(types.length); for (YType t : types) { if (t instanceof YMapType) { maps.add((YMapType) t); } else if (t instanceof YAtomicType) { atoms.add((YAtomicType) t); + } else if (t instanceof YSeqType) { + arrays.add((YSeqType) t); + } else if (t instanceof YBeanType) { + beans.add((YBeanType) t); } else { throw new IllegalArgumentException("Union of this kind of types is not (yet) supported: "+t); } } - if (atoms.size()==1 && maps.size()==1) { + if (atoms.size()==1 && maps.size()==1 && arrays.size()==0 && beans.size()==0) { return new YAtomAndMapUnion(name, atoms.get(0), maps.get(0)); + } else if (atoms.size()==0 && maps.size()==0 && arrays.size()==1 && beans.size()==1) { + return new YBeanAndSequenceUnion(name, beans.get(0), arrays.get(0)); } throw new IllegalArgumentException("Union of this kind of types is not (yet) supported: "+types); } @@ -544,7 +553,6 @@ public AbstractType treatAsBean() { this.isSeq = false; return this; } - } @@ -836,6 +844,49 @@ public PartialCollection getHintValues(DynamicSchemaContext dc) { } } + + public class YBeanAndSequenceUnion extends AbstractType { + + private String name; + private YBeanType bean; + private YSeqType seq; + + public YBeanAndSequenceUnion(String name, YBeanType yBeanType, YSeqType ySeqType) { + this.name = name; + this.bean = yBeanType; + this.seq = ySeqType; + } + + @Override + public YType inferMoreSpecificType(DynamicSchemaContext dc) { + if (dc.isMap()) { + return bean; + } else if (dc.isSequence()) { + return seq; + } + return super.inferMoreSpecificType(dc); + } + + @Override + public String toString() { + if (name!=null) { + return name; + } else { + return "(" + bean +" | " + seq + ")"; + } + } + + @Override + public boolean isBean() { + return true; + } + + @Override + public boolean isSequenceable() { + return true; + } + + } public static class YTypedPropertyImpl implements YTypedProperty, Cloneable { diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index 6c59de9307..62f1c8f1c4 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -377,9 +377,18 @@ public PipelineYmlSchema(ConcourseModel models, GithubInfoProvider github) { doStep, tryStep }; + + YBeanUnionType step = f.yBeanUnion("Step", stepTypes); addProp(aggregateStep, "aggregate", f.yseq(step)); - addProp(inParallelStep, "in_parallel", f.yseq(step)); + YBeanType inParallelStepOptions = f.ybean("InParallelStepOptions"); + addProp(inParallelStepOptions, "steps", f.yseq(step)); + addProp(inParallelStepOptions, "limit", t_pos_integer); + addProp(inParallelStepOptions, "fail_fast", t_boolean); + addProp(inParallelStep, "in_parallel", f.yunion(null, + f.yseq(step), + inParallelStepOptions + )); addProp(doStep, "do", f.yseq(step)); addProp(tryStep, "try", step); diff --git a/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/fail_fast.md b/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/fail_fast.md new file mode 100644 index 0000000000..52d2cf1a26 --- /dev/null +++ b/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/fail_fast.md @@ -0,0 +1,4 @@ +*Optional*. Default is *false*. When enabled the parallel step will fail fast by returning as soon as any +sub-step fails. This means that running steps will be interrupted and pending steps will no longer be +scheduled. + diff --git a/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/limit.md b/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/limit.md new file mode 100644 index 0000000000..c8d5a54f63 --- /dev/null +++ b/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/limit.md @@ -0,0 +1,6 @@ +*Optional. Default is no limit*. A sempahore which limits the parallelism when executing the steps in a +`in_parallel` step. When set, the number of running steps will not exceed the limit. + +When not specified `in_parallel` will execute all steps immediately, making the default behavior +identical to [aggregate](https://concourse-ci.org/aggregate-step.html). + diff --git a/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/steps.md b/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/steps.md new file mode 100644 index 0000000000..e0628c7d72 --- /dev/null +++ b/headless-services/concourse-language-server/src/main/resources/desc/InParallelStepOptions/steps.md @@ -0,0 +1 @@ +*Optional*. The steps to perform in parallel. diff --git a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java index cf0860b194..228f95603f 100644 --- a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java +++ b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java @@ -315,7 +315,7 @@ public void primaryStepCompletions() throws Exception { "get: <*>" , // ============== "in_parallel:\n" + - " - <*>" + " <*>" , // ============== "put: <*>" , // ============== @@ -551,12 +551,68 @@ public void inParallelStepHovers() throws Exception { "- name: some-job\n" + " plan:\n" + " - in_parallel:\n" + - " - get: some-resource\n" + " limit: 2\n" + + " fail_fast: true\n" + + " steps:\n" + + " - get: some-resource\n" ); editor.assertHoverContains("in_parallel", "Performs the given steps in parallel"); + editor.assertHoverContains("limit", "sempahore which limits the parallelism"); + editor.assertHoverContains("fail_fast", "step will fail fast"); + } + + @Test + public void inParallelStepReconcile() throws Exception { + Editor editor; + + //old style... just a sequence of steps + editor = harness.newEditor( + "jobs:\n" + + "- name: some-job\n" + + " plan:\n" + + " - in_parallel:\n" + + " - get: some-resource\n" + ); + editor.assertProblems( + "some-resource|does not exist" + ); + + //new style... object with a 'steps' property + editor = harness.newEditor( + "jobs:\n" + + "- name: some-job\n" + + " plan:\n" + + " - in_parallel:\n" + + " limit: not-a-number\n" + + " fail_fast: not-a-bool\n"+ + " steps:\n" + + " - get: some-resource\n" + ); + editor.assertProblems( + "not-a-number|NumberFormatException", + "not-a-bool|boolean", + "some-resource|does not exist" + ); } + @Test + public void inParallelStepCompletion() throws Exception { + Editor editor = harness.newEditor( + "jobs:\n" + + "- name: some-job\n" + + " plan:\n" + + " - <*>" + ); + editor.assertCompletionWithLabel("in_parallel", + "jobs:\n" + + "- name: some-job\n" + + " plan:\n" + + " - in_parallel:\n" + + " <*>" + ); + } + @Test public void reconcileSimpleTypes() throws Exception { Editor editor; @@ -4232,14 +4288,6 @@ public void gotoResourceTypeDefinition() throws Exception { " plan:\n" + " in_" ); - editor.assertCompletionWithLabel("- in_parallel", - "jobs:\n" + - "- name: build-docker-image\n" + - " serial: true\n" + - " plan:\n" + - " - in_parallel:\n" + - " - <*>" - ); } @Test public void relaxedContentAssist_primary_properties() throws Exception{