Add support for across Step modifier

See: https://github.com/spring-projects/sts4/issues/752
This commit is contained in:
Kris De Volder
2022-04-27 17:10:24 -07:00
parent a626e5813e
commit ff01f60a36
12 changed files with 150 additions and 5 deletions

View File

@@ -26,6 +26,8 @@ public class ValueParsers {
};
public static final ValueParser POS_INTEGER = integerRange(0, null);
public static final ValueParser STRICTLY_POS_INTEGER = integerAtLeast(1);
public static ValueParser integerAtLeast(final Integer lowerBound) {
return integerRange(lowerBound, null);

View File

@@ -15,7 +15,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -38,10 +37,8 @@ import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.SimpleGlob;
import org.springframework.ide.vscode.commons.util.SimpleGlob.Match;
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.AbstractUnionType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanAndSequenceUnion;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType;
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;

View File

@@ -16,6 +16,7 @@ import org.springframework.ide.vscode.commons.util.RegexpParser;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParseException;
import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.util.ValueParsers;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware;
@@ -96,6 +97,23 @@ public class ConcourseValueParsers {
+ "you should use location instead."
);
public static final ValueParser IDENTIFIER = new RegexpParser(
"(\\d|[\\p{L}&&[^\\p{Lu}]]|\\-|_|\\.)*",
"Identifier",
"An identifier is a string value comprised of Unicode lowercase letters, Decimal numbers, hyphens (`-`)" +
" underscores (`_`) and periods (`.`)");
public static final ValueParser MAX_IN_FLIGHT_OR_ALL = (str) -> {
if (str.equals("all")) {
return str;
}
try {
return ValueParsers.STRICTLY_POS_INTEGER.parse(str);
} catch (Exception e ) {
throw new IllegalArgumentException("Expecting either 'all' or an integer greater than 0");
}
};
private static String createTimeRegexp() {
String hours = "([0-2]?[0-9])";
String minutes = "([0-6][0-9])";

View File

@@ -94,6 +94,8 @@ public class PipelineYmlSchema implements YamlSchema {
public final YType t_string = f.yatomic("String");
public final YType t_ne_string = f.yatomic("String")
.parseWith(ValueParsers.NE_STRING);
public final YType t_identifier = f.yatomic("Identifier")
.parseWith(ConcourseValueParsers.IDENTIFIER);
public final YType t_opt_string = f.yatomic("String")
.parseWith(YamlSchemaValueParsers.OPT_STRING);
@@ -381,7 +383,7 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(setPipelineStep, "var_files", t_strings);
YBeanType loadVarStep = f.ybean("LoadVarStep");
addProp(loadVarStep, "load_var", t_ne_string); //TODO: t_identifier: see https://concourse-ci.org/config-basics.html#schema.identifier
addProp(loadVarStep, "load_var", t_identifier);
addProp(loadVarStep, "file", t_ne_string).isRequired(true);
addProp(loadVarStep, "format", f.yenum("LoadVarFormat", "json", "yaml", "yml", "trim", "raw"));
addProp(loadVarStep, "reveal", t_boolean);
@@ -418,6 +420,12 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(doStep, "do", f.yseq(step));
addProp(tryStep, "try", step);
YBeanType acrossVar = f.ybean("AcrossVar");
addProp(acrossVar, "var", t_identifier).isPrimary(true);
addProp(acrossVar, "values", f.yseq(t_any)).isRequired(true);
addProp(acrossVar, "max_in_flight", f.yatomic("AcrossVarMaxInFlight").parseWith(ConcourseValueParsers.MAX_IN_FLIGHT_OR_ALL));
addProp(acrossVar, "fail_fast", t_boolean);
// shared properties applicable for any subtype of Step:
for (AbstractType subStep : stepTypes) {
addProp(step, subStep, "on_success", step);
@@ -427,6 +435,9 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(step, subStep, "tags", t_strings);
addProp(step, subStep, "timeout", t_duration);
addProp(step, subStep, "attempts", t_strictly_pos_integer);
if (subStep!=getStep && subStep!=putStep) { // doc says 'across' doesn't work with put and get
addProp(step, subStep, "across", f.yseq(acrossVar)); //TODO: create proper schema
}
}
models.setStepType(step);

View File

@@ -0,0 +1 @@
Default `false`. When enabled, the `across` 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.

View File

@@ -0,0 +1,2 @@
Default 1. If set to `all`, the substep will run with all combinations of the current var in parallel. If set to a positive integer, only that number of substeps may run in parallel.

View File

@@ -0,0 +1,4 @@
The list of values that the [var](https://concourse-ci.org/across-step.html#schema.across_var.var) will iterate over when running the substep. If multiple vars are configured, all combinations of values across all vars will run.
The list of values may also be interpolated. For instance, you may use the [`load_var` step](https://concourse-ci.org/load-var-step.html) to first load a list of [*value* schema](https://concourse-ci.org/config-basics.html#schema.value) into a [local var](https://concourse-ci.org/vars.html#local-vars), and then iterate across that dynamic list of values.

View File

@@ -0,0 +1,3 @@
The name of the variable that will be added to the ["." var source](https://concourse-ci.org/vars.html#local-vars). This variable will only be accessible in the scope of the step - each iteration of the step gets its own scope.
If a variable of the same name already exists in the parent scope, a warning will be printed.

View File

@@ -0,0 +1,3 @@
Run a step multiple times with different combinations of variable values.
See [Across Step Modifier](https://concourse-ci.org/across-step.html#across-step)

View File

@@ -74,6 +74,42 @@ public class ConcourseEditorTest {
serverInitializer.setMaxCompletions(100);
}
@Test public void GH_752_accross_step_modifier() throws Exception {
//See: https://github.com/spring-projects/sts4/issues/752
//See: https://concourse-ci.org/across-step.html#schema.across
Editor editor = harness.newEditor(
"jobs:\n" +
"- name: job\n" +
" plan:\n" +
" - across:\n" +
" - var: some-text\n" +
" values: some_values\n" +
" max_in_flight: bad_flight\n" +
" fail_fast: is_fail_fast\n" +
" task: running-((.:some-text))\n" +
" config:\n" +
" platform: linux\n" +
" image_resource:\n" +
" type: docker-image\n" +
" source:\n" +
" repository: ubuntu\n" +
" run:\n" +
" path: echo\n" +
" args: [\"((.:some-text))\"]"
);
editor.assertProblems(
"some_values|Expecting a 'Sequence'",
"bad_flight|'all' or an integer greater than 0",
"is_fail_fast|boolean"
);
editor.assertHoverContains("across", "Run a step multiple times with different combinations of variable values");
editor.assertHoverContains("var", "The name of the variable that will be added");
editor.assertHoverContains("values", "will iterate over");
editor.assertHoverContains("max_in_flight", "number of substeps may run in parallel");
editor.assertHoverContains("fail_fast", "steps will be interrupted and pending steps");
}
@Test public void GH_752_repo_mirror() throws Exception {
//See:
// - https://github.com/concourse/registry-image-resource#:~:text=registry_mirror%3A%20Optional.
@@ -4569,6 +4605,7 @@ public class ConcourseEditorTest {
//"plan", exists
//"public", exists
//Completions for nested context (i.e. task step)
"→ across",
"→ attempts",
"→ config",
"→ ensure",

View File

@@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2022 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.commons.util.ValueParser;
import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.*;
public class IdentifierParserTest {
private ValueParser parser = ConcourseValueParsers.IDENTIFIER;
@Test
public void goodExamples() throws Exception {
parser.parse("identifier123.ha-boo_lalala");
parser.parse("simple_ident");
parser.parse("anything-with-dashes-123");
}
@Test
public void badExamples() {
does_not_parse("spaces are bad");
does_not_parse("upperCaseisBad");
does_not_parse("strange@symbols");
does_not_parse("strange!symbols");
does_not_parse("strange:symbols");
}
private void does_not_parse(String string) {
try {
parser.parse(string);
fail("Should have failed parsing!");
} catch (Exception e) {
assertContains("Identifier", ExceptionUtil.getMessage(e));
}
}
}

View File

@@ -0,0 +1,17 @@
jobs:
- name: job
plan:
- across:
- var: some-text
max_in_flight: 1
fail_fast: alala
task: running-((.:some-text))
config:
platform: linux
image_resource:
type: docker-image
source:
repository: ubuntu
run:
path: echo
args: ["((.:some-text))"]