Reconciler check that jobs used in 'passed' attribute...

... must interact with the resource.
This commit is contained in:
Kris De Volder
2017-04-22 10:37:59 -07:00
parent da66be7aec
commit c344c13e5e
16 changed files with 587 additions and 66 deletions

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2017 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.path;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.yaml.util.Streams;
/**
* Abstract superclass for implementing concrete {@link YamlTraversal}s.
* <p>
* Note that allthoigh this class provides a default implementation for both
* `traverse` and `traverseAmbiguously`, at least one of these methods *must*
* be overridden by the subclass (otherwise the methods will just call eachother
* in a infinite recursion loop).
* <p>
* To implement a non-ambiguous traversal, override the `traverse` method.
* <p>
* To implement an ambguous traversal, override the `traverseAmbiguously` method
* instead.
*
* @author Kris De Volder
*/
public abstract class AbstractYamlTraversal implements YamlTraversal {
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
return Streams.fromNullable(traverse(start));
}
@Override
public <T extends YamlNavigable<T>> T traverse(T startNode) {
return traverseAmbiguously(startNode).findFirst().orElse(null);
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2017 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.path;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.yaml.util.Streams;
public class AlternativeYamlTraversal extends AbstractYamlTraversal {
private YamlTraversal first;
private YamlTraversal second;
public AlternativeYamlTraversal(YamlTraversal first, YamlTraversal second) {
Assert.isLegal(!first.isEmpty());
Assert.isLegal(!second.isEmpty());
this.first = first;
this.second = second;
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
return Stream.concat(
first.traverseAmbiguously(start),
second.traverseAmbiguously(start)
);
}
@Override
public boolean canEmpty() {
return first.canEmpty() || second.canEmpty();
}
@Override
public String toString() {
return "Or("+first+", "+second+")";
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2017 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.path;
import java.util.stream.Stream;
/**
* @author Kris De Volder
*/
public class FilteringTraversal extends AbstractYamlTraversal {
private YamlTraversal yamlTraversal;
private YamlTraversal check;
public FilteringTraversal(YamlTraversal yamlTraversal, YamlTraversal check) {
this.yamlTraversal = yamlTraversal;
this.check = check;
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
Stream<T> x = yamlTraversal.traverseAmbiguously(start);
return x.filter(target ->
check.traverseAmbiguously(target)
.findAny()
.isPresent()
);
}
@Override
public String toString() {
return "Filter("+yamlTraversal + "has: " + check + ")";
}
@Override
public boolean canEmpty() {
return yamlTraversal.canEmpty();
}
}

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright (c) 2017 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.path;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.yaml.util.Streams;
public class RepeatingYamlTraversal extends AbstractYamlTraversal {
private YamlTraversal step;
public RepeatingYamlTraversal(YamlTraversal step) {
Assert.isLegal(!step.canEmpty()); //This implementation is still too simplistic to handle that properly!
// If you hit this assert, then it may be time to make it more sophisticated.
this.step = step;
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
if (start==null) {
return Stream.empty();
} else {
return Stream.concat(
Streams.fromNullable(start),
step.traverseAmbiguously(start).flatMap(next -> {
return this.traverseAmbiguously(next);
})
);
}
}
@Override
public String toString() {
return "Repeat("+step+")";
}
@Override
public YamlTraversal repeat() {
//don't make 'Repeat(Repeat(...))'
return this;
}
@Override
public boolean canEmpty() {
return true;
}
}

View File

@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2017 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.path;
import java.util.stream.Stream;
public class SequencingYamlTraversal extends AbstractYamlTraversal {
private YamlTraversal first;
private YamlTraversal second;
public SequencingYamlTraversal(YamlTraversal first, YamlTraversal second) {
this.first = first;
this.second = second;
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
return first.traverseAmbiguously(start)
.flatMap(second::traverseAmbiguously);
}
@Override
public boolean canEmpty() {
return first.canEmpty() && second.canEmpty();
}
}

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.yaml.path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef;
@@ -29,7 +30,7 @@ import reactor.core.publisher.Flux;
/**
* @author Kris De Volder
*/
public class YamlPath {
public class YamlPath extends AbstractYamlTraversal {
public static final YamlPath EMPTY = new YamlPath();
private final YamlPathSegment[] segments;
@@ -132,34 +133,7 @@ public class YamlPath {
return new YamlPath(newPath);
}
public Node traverseToNode(YamlFileAST root) {
ASTCursor cursor = traverse(new ASTRootCursor(root));
if (cursor instanceof NodeCursor) {
return ((NodeCursor)cursor).getNode();
}
return null;
}
public <T extends YamlNavigable<T>> T traverse(T startNode) {
return traverseAmbiguously(startNode).findFirst().orElse(null);
}
public Stream<Node> traverseAmbiguously(YamlFileAST ast) {
if (ast!=null) {
return traverseAmbiguously(new ASTRootCursor(ast))
.map((ASTCursor cursor) -> (Node)cursor.getNode());
}
return Stream.empty();
}
public Stream<Node> traverseAmbiguously(Node startNode) {
if (startNode!=null) {
return traverseAmbiguously(new NodeCursor(startNode))
.map((ASTCursor cursor) -> (Node)cursor.getNode());
}
return Stream.empty();
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T startNode) {
if (startNode!=null) {
Stream<T> result = Stream.of(startNode);
@@ -206,6 +180,7 @@ public class YamlPath {
}
@Override
public boolean isEmpty() {
return segments.length==0;
}
@@ -311,4 +286,31 @@ public class YamlPath {
.block();
}
@Override
public YamlTraversal then(YamlTraversal _other) {
if (isEmpty()) {
return _other;
} else if (_other.isEmpty()) {
return this;
} else if (_other instanceof YamlPathSegment) {
return this.append((YamlPathSegment) _other);
} else if (_other instanceof YamlPath) {
YamlPath other = (YamlPath) _other;
return new YamlPath(
Stream.concat(
Arrays.stream(this.segments),
Arrays.stream(other.segments)
).toArray(sz -> new YamlPathSegment[sz])
);
} else {
return new SequencingYamlTraversal(this, _other);
}
}
@Override
public boolean canEmpty() {
//The empty path is the only one that 'canEmpty' since any step in path moves the cursor.
return isEmpty();
}
}

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.path;
import java.util.stream.Stream;
/**
* A YamlPathSegment is a 'primitive' NodeNavigator operation.
* More complex operations (i.e {@link YamlPath}) are composed as seqences
@@ -17,7 +19,7 @@ package org.springframework.ide.vscode.commons.yaml.path;
*
* @author Kris De Volder
*/
public abstract class YamlPathSegment {
public abstract class YamlPathSegment extends AbstractYamlTraversal {
public static YamlPathSegment decode(String code) {
switch (code.charAt(0)) {
@@ -34,6 +36,11 @@ public abstract class YamlPathSegment {
}
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
return start.traverseAmbiguously(this);
}
public static enum YamlPathSegmentType {
VAL_AT_KEY, //Go to value associate with given key in a map.
KEY_AT_KEY, //Go to the key node associated with a given key in a map.
@@ -43,7 +50,7 @@ public abstract class YamlPathSegment {
public static class AnyChild extends YamlPathSegment {
private static AnyChild INSTANCE = new AnyChild();
static AnyChild INSTANCE = new AnyChild();
private AnyChild() {}
@@ -76,6 +83,7 @@ public abstract class YamlPathSegment {
protected String getValueCode() {
return "";
}
}
public static class AtIndex extends YamlPathSegment {
@@ -226,6 +234,12 @@ public abstract class YamlPathSegment {
}
@Override
public boolean canEmpty() {
//All path segments implement a real 'one step' movement,
return false;
}
@Override
public String toString() {
return toNavString();
@@ -255,7 +269,23 @@ public abstract class YamlPathSegment {
return getTypeCode() + getValueCode();
}
protected abstract String getValueCode();
protected abstract char getTypeCode();
@Override
public YamlTraversal then(YamlTraversal other) {
//Overriding the `then` method to try to compress sequences of segments into YamlPath instead of deeply nested
if (other.isEmpty()) {
return this;
} else if (other instanceof YamlPathSegment) {
return new YamlPath(this, (YamlPathSegment)other);
} else if (other instanceof YamlPath) {
return ((YamlPath) other).prepend(this);
} else {
return new SequencingYamlTraversal(this, other);
}
}
}

View File

@@ -0,0 +1,156 @@
/*******************************************************************************
* Copyright (c) 2017 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.path;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.yaml.snakeyaml.nodes.Node;
public interface YamlTraversal {
////////////////////////////////////////////////
/// Methods for performing traversals :
/**
* This is the essence of a traversal. Any concrete traversal must provide
* some way to be applied to a starting point and return a stream of
* endpoints. If a traversal is non-ambiguous the stream contains at
* most one element.
* <p>
* If traversal is deterministic, the stream will contain at most one element.
* <p>
* If traversal has potential ambiguity then the stream may contain more than
* one element. Each of the elements in the stream is an alternate
* place that this traversal could end-up in.
*/
<T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start);
/**
* Performs a traversal and silently drops all but one of the endpoints.
* <p>
* This is a convenience method for unambiguous traversals, or for when the caller
* doesn't care about precisely which one of the possible alternate end-points
* they might get.
*/
<T extends YamlNavigable<T>> T traverse(T startNode);
default Node traverseToNode(YamlFileAST root) {
ASTCursor cursor = traverse(new ASTRootCursor(root));
if (cursor instanceof NodeCursor) {
return ((NodeCursor)cursor).getNode();
}
return null;
}
default Stream<Node> traverseAmbiguously(YamlFileAST ast) {
if (ast!=null) {
return traverseAmbiguously(new ASTRootCursor(ast))
.filter(cursor -> cursor.getNode() instanceof Node)
.map((ASTCursor cursor) -> (Node)cursor.getNode());
}
return Stream.empty();
}
default Stream<Node> traverseAmbiguously(Node startNode) {
if (startNode!=null) {
return traverseAmbiguously(new NodeCursor(startNode))
.map((ASTCursor cursor) -> (Node)cursor.getNode());
}
return Stream.empty();
}
/////////////////////////////////////////////////
// Creating/composing traversals
YamlTraversal EMPTY = YamlPath.EMPTY;
default YamlTraversal then(YamlTraversal other) {
if (this.isEmpty()) {
return other;
} else if (other.isEmpty()) {
return this;
}
return new SequencingYamlTraversal(this, other);
}
default YamlTraversal thenValAt(int index) {
return then(YamlPathSegment.valueAt(index));
}
default YamlTraversal thenValAt(String key) {
return then(YamlPathSegment.valueAt(key));
}
default YamlTraversal thenKeyAt(String key) {
return then(YamlPathSegment.keyAt(key));
}
default YamlTraversal thenAnyChild() {
return then(YamlPathSegment.anyChild());
}
default YamlTraversal or(YamlTraversal other) {
if (this.isEmpty()) {
return other;
} else if (other.isEmpty()) {
return this;
} else {
return new AlternativeYamlTraversal(this, other);
}
}
default YamlTraversal repeatAtLeast(int howMany) {
if (isEmpty()) {
return this;
} else if (howMany>0) {
return this.then(this.repeatAtLeast(howMany-1));
} else {
return this.repeat();
}
}
default YamlTraversal repeat() {
return new RepeatingYamlTraversal(this);
}
/**
* Filters the end-points of a traversal, retaining only those
* for which the `check` traversal starting at the end-point
* leads somewhere.
*/
default YamlTraversal has(YamlTraversal check) {
if (this.isEmpty()) {
return this; // don't bother filtering empty!
}
return new FilteringTraversal(this, check);
}
//////////////////////////////////////////////////////
// Computing information about the traversal's nature
/**
* Returns true if the traversal has only one possible end-point, equal
* to its starting point. In other words the traversal does nothing.
*/
default boolean isEmpty() {
return false;
}
/**
* Returns true if the traversal might include the starting point in its end points.
* <p>
* Note this is not the same as 'isEmpty', though 'isEmpty' implies 'canEmpty'.
* <p>
* If this returns null, it should be taken to mean 'unknown'. This is used for
* cases where analyzing the traversal can not predict for certain if the traversal
* allows a 'no movement' step.
*/
boolean canEmpty();
}

View File

@@ -15,7 +15,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.yaml.snakeyaml.nodes.Node;
/**
* A type collector can optionally be added to a {@link YamlASTReconciler}.
* A type collector can optionally be added to a {@link SchemaBasedYamlASTReconciler}.
* It is notified of the types the reconciler infers for
* any AST nodes it visits during reconciling.
*

View File

@@ -124,6 +124,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
if (typeCollector!=null) {
typeCollector.accept(node, type);
}
checkConstraints(parent, node, type, schemaContext);
switch (getNodeId(node)) {
case mapping:
MappingNode map = (MappingNode) node;
@@ -250,12 +251,14 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
}
problems.accept(YamlSchemaProblems.missingProperties(message, dc, missingProps, parent, map, quickfixes.MISSING_PROP_FIX));
}
}
}
//Check for other constraints attached to the type
for (Constraint constraint : typeUtil.getConstraints(type)) {
if (constraint!=null) {
constraint.verify(dc, parent, map, type, problems);
}
protected void checkConstraints(Node parent, Node node, YType type, DynamicSchemaContext dc) {
//Check for other constraints attached to the type
for (Constraint constraint : typeUtil.getConstraints(type)) {
if (constraint!=null) {
constraint.verify(dc, parent, node, type, problems);
}
}
}

View File

@@ -33,6 +33,7 @@ import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
@@ -214,10 +215,9 @@ public class YTypeFactory {
private SchemaContextAware<ValueParser> parser;
private List<YTypedProperty> propertyList = new ArrayList<>();
private final List<YValueHint> hints = new ArrayList<>();
private List<YValueHint> hints = new ArrayList<>();
private Map<String, YTypedProperty> cachedPropertyMap;
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
private List<Constraint> constraints = new ArrayList<>(2);
public boolean isSequenceable() {
@@ -357,8 +357,9 @@ public class YTypeFactory {
return parser == null ? null : parser.withContext(dc);
}
public void require(Constraint dynamicConstraint) {
public AbstractType require(Constraint dynamicConstraint) {
this.constraints.add(dynamicConstraint);
return this;
}
public void requireOneOf(String... properties) {
@@ -645,7 +646,7 @@ public class YTypeFactory {
return getPrimaryProps();
}
private synchronized Map<String, AbstractType> typesByPrimary() {
public synchronized Map<String, AbstractType> typesByPrimary() {
if (typesByPrimary==null) {
//To ensure that the map of 'typesByPrimary' is never stale, make the list of
// types immutable at this point. The assumption here is that union can be

View File

@@ -395,7 +395,7 @@ public class YamlStructureParser {
if (index!=null) {
List<SNode> cs = getChildren();
if (index>=0 && index<cs.size()) {
return Streams.of(cs.get(index));
return Streams.fromNullable(cs.get(index));
}
}
return Stream.empty();
@@ -502,7 +502,7 @@ public class YamlStructureParser {
case VAL_AT_KEY:
return this.getChildrenWithKey(s.toPropString());
case VAL_AT_INDEX:
return Streams.of(this.getSeqChildWithIndex(s.toIndex()));
return Streams.fromNullable(this.getSeqChildWithIndex(s.toIndex()));
default:
return Stream.empty();
}

View File

@@ -16,9 +16,9 @@ import java.util.stream.Stream;
public class Streams {
/**
* Like java.util.Stream.of but returns Stream.empty of the element is null
* Like java.util.Stream.of but returns Stream.empty if the element is null
*/
public static <T> Stream<T> of(T e) {
public static <T> Stream<T> fromNullable(T e) {
return e==null ? Stream.empty() : Stream.of(e);
}

View File

@@ -11,11 +11,13 @@
package org.springframework.ide.vscode.concourse;
import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.anyChild;
import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.keyAt;
import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.valueAt;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -23,6 +25,7 @@ import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
@@ -33,14 +36,17 @@ import org.springframework.ide.vscode.commons.yaml.path.ASTRootCursor;
import org.springframework.ide.vscode.commons.yaml.path.NodeCursor;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanUnionType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
import org.springframework.ide.vscode.commons.yaml.util.Streams;
import org.springframework.ide.vscode.concourse.util.CollectorUtil;
import org.springframework.ide.vscode.concourse.util.StaleFallbackCache;
import org.yaml.snakeyaml.Yaml;
@@ -60,13 +66,92 @@ import com.google.common.collect.Multiset;
*/
public class ConcourseModel {
// /**
// * Verification of contraint: a job used in the 'passed' attribute of a step
// * must interact with the resource in question.
// */
// public Constraint jobHasInteractionWithResource() {
// return (IDocument doc, Node parent, Node node, YType type, Set<String> foundProps, IProblemCollector problems) -> {
// }
/**
* Verification of contraint: a job used in the 'passed' attribute of a step
* must interact with the resource in question.
*/
public final void passedJobHasInteractionWithResource(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) {
YamlPath path = dc.getPath();
// Expecting a path like this: YamlPath([0], .jobs, [1], .plan, [0], .passed, [0])
path = path.dropLast();
if (YamlPathSegment.valueAt("passed").equals(path.getLastSegment())) {
String jobName = NodeUtil.asScalar(node);
JobModel job = getJob(dc.getDocument(), jobName);
if (job!=null) {
//Only check if the job exists. Otherwise the extra checks will show 'redundant' errors (e.g.
// complaining that 'some-job' doesn't ineract with a resource (because the resource doesn't exist).
YamlFileAST root = this.getSafeAst(dc.getDocument());
if (root!=null) {
Node stepNode = path.dropLast().traverseToNode(root);
if (stepNode!=null) {
StepModel step = newStep(stepNode);
String resourceName = step.getResourceName();
if (resourceName!=null) {
Set<String> interactions = job.getInteractedResources();
if (interactions!=null && !interactions.contains(resourceName)) {
problems.accept(YamlSchemaProblems.schemaProblem("Job '"+jobName+"' does not interact with resource '"+resourceName+"'", node));
}
}
}
}
}
}
}
/**
* Get the job with given name. If there is no such job, or if there is more than one, this will return null.
*/
private JobModel getJob(IDocument doc, String jobName) {
List<JobModel> jobs = getFromAst(doc, ast ->
JOBS_PATH.traverseAmbiguously(ast)
.filter(node -> jobName.equals(NodeUtil.getScalarProperty(node, "name")))
.map(JobModel::new)
)
.limit(2) //We only need 2 elements at most to determine if there is more than one
.collect(Collectors.toList());
return jobs.size()==1
? jobs.get(0)
: null;
}
public StepModel newStep(Node _node) {
MappingNode node = (MappingNode) _node;
Set<String> keys = NodeUtil.getScalarKeys(node);
for (Entry<String, AbstractType> primary : stepType.typesByPrimary().entrySet()) {
String stepType = primary.getKey();
if (keys.contains(primary.getKey())) {
return new StepModel(stepType, node);
}
}
throw new IllegalArgumentException("Node does not look like step node: "+node);
}
private static final YamlTraversal JobModel_GET_PUT_STEP_PATH = new YamlPath()
.then(valueAt("plan"))
.then(anyChild().repeatAtLeast(1))
.has(keyAt("get").or(keyAt("put")));
/**
* Wraps around a Node in the AST that represents a 'job' and
* provides methods for accessing information from the node.
*/
public class JobModel {
private Node node;
JobModel(Node node) {
this.node = node;
}
public Set<String> getInteractedResources() {
return JobModel_GET_PUT_STEP_PATH
.traverseAmbiguously(node)
.map(node -> newStep(node))
.flatMap(step -> Streams.fromNullable(step.getResourceName()))
.collect(Collectors.toSet());
}
}
/**
* Wraps around a Node in the AST that represents a 'step' and
@@ -83,6 +168,7 @@ public class ConcourseModel {
}
public Node getResourceNameNode() {
Assert.isLegal("put".equals(stepType) || "get".equals(stepType));
Node node = NodeUtil.getProperty(step, "resource");
return node!=null ? node : NodeUtil.getProperty(step, stepType);
}
@@ -111,6 +197,12 @@ public class ConcourseModel {
}
public static final YamlPath JOBS_PATH = new YamlPath(
anyChild(),
valueAt("jobs"),
anyChild()
);
public static final YamlPath JOB_NAMES_PATH = new YamlPath(
anyChild(),
valueAt("jobs"),
@@ -144,6 +236,8 @@ public class ConcourseModel {
private final Supplier<SnippetBuilder> snippetBuilderFactory;
private YBeanUnionType stepType;
public ConcourseModel(SimpleLanguageServer languageServer) {
Yaml yaml = new Yaml();
this.parser = new YamlParser(yaml);
@@ -326,12 +420,13 @@ public class ConcourseModel {
return astTypes;
}
public StepModel newStep(String stepType, MappingNode stepNode) {
return new StepModel(stepType, stepNode);
}
public void setResourceTypeRegistry(ResourceTypeRegistry resourceTypes) {
this.resourceTypes = resourceTypes;
}
public void setStepType(YBeanUnionType step) {
Assert.isNull("stepType already set", this.stepType);
this.stepType = step;
}
}

View File

@@ -102,7 +102,7 @@ public class PipelineYmlSchema implements YamlSchema {
.parseWith(ValueParsers.integerAtLeast(1));
public final YAtomicType t_resource_name;
public final YAtomicType t_job_name;
public final AbstractType t_job_name;
public final YAtomicType t_resource_type_name;
public final YType t_mime_type = f.yatomic("MimeType")
.parseWith(ValueParsers.NE_STRING)
@@ -189,7 +189,7 @@ public class PipelineYmlSchema implements YamlSchema {
(DynamicSchemaContext dc) -> {
return models.getJobNames(dc);
}
);
).require(models::passedJobHasInteractionWithResource);
YAtomicType resourceNameDef = f.yatomic("Resource Name");
resourceNameDef.parseWith(ConcourseValueParsers.resourceNameDef(models));
@@ -290,7 +290,7 @@ public class PipelineYmlSchema implements YamlSchema {
putStep.require((DynamicSchemaContext dc, Node parent, Node _map, YType type, IProblemCollector problems) -> {
if (_map instanceof MappingNode) {
MappingNode map = (MappingNode) _map;
StepModel step = models.newStep("put", map);
StepModel step = models.newStep(map);
String resourceName = step.getResourceName();
if (resourceName!=null) {
ResourceModel resource = models.getResource(dc.getDocument(), resourceName);
@@ -342,6 +342,7 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(step, subStep, "tags", t_strings);
addProp(step, subStep, "timeout", t_duration);
}
models.setStepType(step);
AbstractType job = f.ybean("Job");
addProp(job, "name", jobNameDef).isRequired(true);

View File

@@ -651,8 +651,8 @@ public class ConcourseEditorTest {
{
List<Diagnostic> problems = editor.assertProblems(
"config|Only one of [config, file]",
"config|[platform, run] are required",
"config|One of [image_resource, image]",
"config|[platform, run] are required",
"file|Only one of [config, file]"
);
//All of the problems in this example are property contraint violations! So all should be warnings.
@@ -1615,8 +1615,8 @@ public class ConcourseEditorTest {
" access_key_id: the-key"
);
editor.assertProblems(
"source|'bucket' is required",
"source|One of [regexp, versioned_file] is required"
"source|One of [regexp, versioned_file] is required",
"source|'bucket' is required"
);
editor = harness.newEditor(
@@ -3116,7 +3116,7 @@ public class ConcourseEditorTest {
);
}
@Ignore @Test public void reconcilerJobFromPassedAttributeMustInteractWithResource() throws Exception {
@Test public void reconcilerJobFromPassedAttributeMustInteractWithResource() throws Exception {
Editor editor;
editor = harness.newEditor(
@@ -3141,7 +3141,7 @@ public class ConcourseEditorTest {
" - build-it # <- bad\n"
);
editor.assertProblems(
"build-it^ # <- bad|Job 'build-it' doesn't interact with the resource 'version'"
"build-it^ # <- bad|Job 'build-it' does not interact with resource 'version'"
);
editor = harness.newEditor(
@@ -3166,7 +3166,7 @@ public class ConcourseEditorTest {
" - build-it # <- good\n"
);
editor.assertProblems(
"build-it^ # <- bad|Job 'build-it' doesn't interact with the resource 'source-repo'"
"build-it^ # <- bad|Job 'build-it' does not interact with resource 'source-repo'"
);
//Check that we find interactions in steps that are at the top-level of the plan: