Verify constraints around 'health-check-http-endpoint' prop
This commit is contained in:
@@ -43,6 +43,16 @@ public interface YamlTraversal {
|
||||
*/
|
||||
<T extends YamlNavigable<T>> T traverse(T startNode);
|
||||
|
||||
default Node traverseNode(Node root) {
|
||||
if (root!=null) {
|
||||
ASTCursor cursor = traverse(new NodeCursor(root));
|
||||
if (cursor instanceof NodeCursor) {
|
||||
return ((NodeCursor)cursor).getNode();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
default Node traverseToNode(YamlFileAST root) {
|
||||
ASTCursor cursor = traverse(new ASTRootCursor(root));
|
||||
if (cursor instanceof NodeCursor) {
|
||||
|
||||
@@ -95,7 +95,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
if (nodes!=null && !nodes.isEmpty()) {
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
Node node = nodes.get(i);
|
||||
reconcile(ast.getDocument(), new YamlPath(YamlPathSegment.valueAt(i)), /*parent*/null, node, schema.getTopLevelType());
|
||||
reconcile(ast, new YamlPath(YamlPathSegment.valueAt(i)), /*parent*/null, node, schema.getTopLevelType());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -121,9 +121,10 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
return allOf(ast, node);
|
||||
}
|
||||
|
||||
private void reconcile(IDocument doc, YamlPath path, Node parent, Node node, YType type) {
|
||||
private void reconcile(YamlFileAST ast, YamlPath path, Node parent, Node node, YType type) {
|
||||
// IDocument doc = ast.getDocument();
|
||||
if (type!=null) {
|
||||
DynamicSchemaContext schemaContext = new ASTDynamicSchemaContext(doc, path, node);
|
||||
DynamicSchemaContext schemaContext = new ASTDynamicSchemaContext(ast, path, node);
|
||||
type = typeUtil.inferMoreSpecificType(type, schemaContext);
|
||||
if (typeCollector!=null) {
|
||||
typeCollector.accept(node, type);
|
||||
@@ -136,8 +137,8 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
if (typeUtil.isMap(type)) {
|
||||
for (NodeTuple entry : map.getValue()) {
|
||||
String key = NodeUtil.asScalar(entry.getKeyNode());
|
||||
reconcile(doc, keyAt(path, key), map, entry.getKeyNode(), typeUtil.getKeyType(type));
|
||||
reconcile(doc, valueAt(path, key), map, entry.getValueNode(), typeUtil.getDomainType(type));
|
||||
reconcile(ast, keyAt(path, key), map, entry.getKeyNode(), typeUtil.getKeyType(type));
|
||||
reconcile(ast, valueAt(path, key), map, entry.getValueNode(), typeUtil.getDomainType(type));
|
||||
}
|
||||
} else if (typeUtil.isBean(type)) {
|
||||
Map<String, YTypedProperty> beanProperties = typeUtil.getPropertiesMap(type);
|
||||
@@ -155,7 +156,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
if (prop.isDeprecated()) {
|
||||
problems.accept(YamlSchemaProblems.deprecatedProperty(keyNode, type, prop));
|
||||
}
|
||||
reconcile(doc, valueAt(path, key), map, entry.getValueNode(), prop.getType());
|
||||
reconcile(ast, valueAt(path, key), map, entry.getValueNode(), prop.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,7 +169,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
if (typeUtil.isSequencable(type)) {
|
||||
for (int i = 0; i < seq.getValue().size(); i++) {
|
||||
Node el = seq.getValue().get(i);
|
||||
reconcile(doc, valueAt(path, i), seq, el, typeUtil.getDomainType(type));
|
||||
reconcile(ast, valueAt(path, i), seq, el, typeUtil.getDomainType(type));
|
||||
}
|
||||
} else {
|
||||
expectTypeButFoundSequence(type, node);
|
||||
@@ -185,7 +186,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ProblemType problemType = getProblemType(e);
|
||||
DocumentRegion region = getRegion(e, doc, node);
|
||||
DocumentRegion region = getRegion(e, ast.getDocument(), node);
|
||||
String msg = getMessage(e);
|
||||
valueParseError(type, region, msg, problemType, getValueReplacement(e));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
@@ -27,11 +28,11 @@ import org.yaml.snakeyaml.nodes.Node;
|
||||
public class ASTDynamicSchemaContext extends CachingSchemaContext {
|
||||
|
||||
private MappingNode mapNode;
|
||||
private IDocument doc;
|
||||
private YamlPath path;
|
||||
private YamlFileAST ast;
|
||||
|
||||
public ASTDynamicSchemaContext(IDocument doc, YamlPath path, Node node) {
|
||||
this.doc = doc;
|
||||
public ASTDynamicSchemaContext(YamlFileAST ast, YamlPath path, Node node) {
|
||||
this.ast = ast;
|
||||
this.path = path;
|
||||
this.mapNode = as(MappingNode.class, node);
|
||||
}
|
||||
@@ -51,11 +52,16 @@ public class ASTDynamicSchemaContext extends CachingSchemaContext {
|
||||
|
||||
@Override
|
||||
public IDocument getDocument() {
|
||||
return doc;
|
||||
return ast.getDocument();
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlPath getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlFileAST getAST() {
|
||||
return ast;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.yaml.schema;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -30,6 +31,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
public interface DynamicSchemaContext {
|
||||
|
||||
DynamicSchemaContext NULL = new DynamicSchemaContext() {
|
||||
|
||||
@Override
|
||||
public Set<String> getDefinedProperties() {
|
||||
return ImmutableSet.of();
|
||||
@@ -47,6 +49,12 @@ public interface DynamicSchemaContext {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the enitre AST of the current document. May be null if the AST is not
|
||||
* available (e.g. because of parsing errors)
|
||||
*/
|
||||
default YamlFileAST getAST() { return null; }
|
||||
|
||||
/**
|
||||
* Returns the set of property names that are already defined in the current context.
|
||||
* <p>
|
||||
|
||||
@@ -10,13 +10,9 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.schema.constraints;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
/**
|
||||
@@ -28,10 +24,10 @@ import org.yaml.snakeyaml.nodes.Node;
|
||||
public interface Constraint {
|
||||
|
||||
/**
|
||||
* Implemetors gain access to various bits of context information passed as parameters and
|
||||
* Implementors gain access to various bits of context information passed as parameters and
|
||||
* are supposed to use this information in whatever way they like to check if the
|
||||
* constraint is satisfied. When the constrain is not satisfied they should report any
|
||||
* violations by adding problems to the provide {@link IProblemCollector}.
|
||||
* constraint is satisfied. When the constraint is not satisfied they should report any
|
||||
* violations by adding problems to the provided {@link IProblemCollector}.
|
||||
*
|
||||
* @param node The node being validated
|
||||
* @param type The inferred type of the node.
|
||||
|
||||
@@ -17,13 +17,9 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTy
|
||||
|
||||
public class ManifestYamlSchemaProblemsTypes {
|
||||
|
||||
public static final ProblemType UNKNOWN_SERVICES_PROBLEM = problemType("UnknownServicesProblem",
|
||||
ProblemSeverity.WARNING);
|
||||
|
||||
public static final ProblemType UNKNOWN_DOMAIN_PROBLEM = problemType("UnknownDomainProblem",
|
||||
ProblemSeverity.WARNING);
|
||||
|
||||
public static final ProblemType UNKNOWN_STACK_PROBLEM = problemType("UnknownStackProblem",
|
||||
ProblemSeverity.WARNING);;
|
||||
public static final ProblemType UNKNOWN_SERVICES_PROBLEM = problemType("UnknownServicesProblem", ProblemSeverity.WARNING);
|
||||
public static final ProblemType UNKNOWN_DOMAIN_PROBLEM = problemType("UnknownDomainProblem", ProblemSeverity.WARNING);
|
||||
public static final ProblemType UNKNOWN_STACK_PROBLEM = problemType("UnknownStackProblem", ProblemSeverity.WARNING);
|
||||
public static final ProblemType IGNORED_PROPERTY = problemType("IgnoredProperty", ProblemSeverity.WARNING);
|
||||
|
||||
}
|
||||
|
||||
@@ -14,10 +14,17 @@ import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.util.IntegerRange;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParsers;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
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.reconcile.YamlSchemaProblems;
|
||||
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;
|
||||
@@ -27,6 +34,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPro
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
@@ -35,6 +43,9 @@ import com.google.common.collect.ImmutableSet;
|
||||
*/
|
||||
public class ManifestYmlSchema implements YamlSchema {
|
||||
|
||||
private static final String HEALTH_CHECK_HTTP_ENDPOINT_PROP = "health-check-http-endpoint";
|
||||
private static final String HEALTH_CHECK_TYPE_PROP = "health-check-type";
|
||||
|
||||
private final AbstractType TOPLEVEL_TYPE;
|
||||
private final YTypeUtil TYPE_UTIL;
|
||||
|
||||
@@ -47,6 +58,40 @@ public class ManifestYmlSchema implements YamlSchema {
|
||||
return IntegerRange.exactly(1);
|
||||
}
|
||||
|
||||
private void verify_heatth_check_http_end_point_constraint(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) {
|
||||
YamlFileAST ast = dc.getAST();
|
||||
if (ast!=null) {
|
||||
Node markerNode = YamlPathSegment.keyAt(HEALTH_CHECK_HTTP_ENDPOINT_PROP).traverseNode(node);
|
||||
if (markerNode != null) {
|
||||
String healthCheckType = getEffectiveHealthCheckType(ast, dc.getPath(), node);
|
||||
if (!"http".equals(healthCheckType)) {
|
||||
problems.accept(YamlSchemaProblems.problem(ManifestYamlSchemaProblemsTypes.IGNORED_PROPERTY,
|
||||
"This has no effect unless `"+HEALTH_CHECK_TYPE_PROP+"` is `http` (but it is currently set to `"+healthCheckType+"`)", markerNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the actual health-check-type that applies to a given node, taking into account
|
||||
* inheritance from parent node, and default value.
|
||||
*/
|
||||
private String getEffectiveHealthCheckType(YamlFileAST ast, YamlPath path, Node node) {
|
||||
String explicit = NodeUtil.getScalarProperty(node, HEALTH_CHECK_TYPE_PROP);
|
||||
if (explicit!=null) {
|
||||
return explicit;
|
||||
}
|
||||
if (path.size()>2) {
|
||||
//Must consider inherited props!
|
||||
YamlPath parentPath = path.dropLast(2);
|
||||
Node parent = parentPath.traverseToNode(ast);
|
||||
String inherited = NodeUtil.getScalarProperty(parent, HEALTH_CHECK_TYPE_PROP);
|
||||
if (inherited!=null) {
|
||||
return inherited;
|
||||
}
|
||||
}
|
||||
return "port";
|
||||
}
|
||||
|
||||
public ManifestYmlSchema(ManifestYmlHintProviders providers) {
|
||||
Callable<Collection<YValueHint>> buildpackProvider = providers.getBuildpackProviders();
|
||||
@@ -60,8 +105,10 @@ public class ManifestYmlSchema implements YamlSchema {
|
||||
|
||||
// define schema types
|
||||
TOPLEVEL_TYPE = f.ybean("Cloudfoundry Manifest");
|
||||
TOPLEVEL_TYPE.require(this::verify_heatth_check_http_end_point_constraint);
|
||||
|
||||
AbstractType application = f.ybean("Application");
|
||||
application.require(this::verify_heatth_check_http_end_point_constraint);
|
||||
YAtomicType t_path = f.yatomic("Path");
|
||||
|
||||
YAtomicType t_buildpack = f.yatomic("Buildpack");
|
||||
@@ -144,8 +191,8 @@ public class ManifestYmlSchema implements YamlSchema {
|
||||
f.yprop("services", f.yseq(t_service)),
|
||||
f.yprop("stack", t_stack),
|
||||
f.yprop("timeout", t_pos_integer),
|
||||
f.yprop("health-check-type", t_health_check_type),
|
||||
f.yprop("health-check-http-endpoint", t_ne_string)
|
||||
f.yprop(HEALTH_CHECK_TYPE_PROP, t_health_check_type),
|
||||
f.yprop(HEALTH_CHECK_HTTP_ENDPOINT_PROP, t_ne_string)
|
||||
};
|
||||
|
||||
for (YTypedPropertyImpl prop : props) {
|
||||
|
||||
@@ -429,8 +429,7 @@ public class ManifestYamlEditorTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reconcileHealthCheckType() throws Exception {
|
||||
@Test public void reconcileHealthCheckType() throws Exception {
|
||||
Editor editor;
|
||||
Diagnostic problem;
|
||||
|
||||
@@ -464,6 +463,63 @@ public class ManifestYamlEditorTest {
|
||||
);
|
||||
editor.assertProblems(/*NONE*/);
|
||||
}
|
||||
|
||||
@Test public void reconcileHealthHttpEndPointIgnoredWarning() throws Exception {
|
||||
Editor editor;
|
||||
Diagnostic problem;
|
||||
|
||||
editor = harness.newEditor(
|
||||
"applications:\n" +
|
||||
"- name: my-app\n" +
|
||||
" health-check-type: process\n" +
|
||||
" health-check-http-endpoint: /health"
|
||||
);
|
||||
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `process`)");
|
||||
|
||||
editor = harness.newEditor(
|
||||
"health-check-type: http\n" +
|
||||
"applications:\n" +
|
||||
"- name: my-app\n" +
|
||||
" health-check-http-endpoint: /health"
|
||||
);
|
||||
editor.assertProblems(/*NONE*/);
|
||||
|
||||
editor = harness.newEditor(
|
||||
"applications:\n" +
|
||||
"- name: my-app\n" +
|
||||
" health-check-http-endpoint: /health"
|
||||
);
|
||||
problem = editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `port`)").get(0);
|
||||
assertEquals(DiagnosticSeverity.Warning, problem.getSeverity());
|
||||
|
||||
editor = harness.newEditor(
|
||||
"applications:\n" +
|
||||
"- name: my-app\n" +
|
||||
" health-check-type: http\n" +
|
||||
" health-check-http-endpoint: /health"
|
||||
);
|
||||
editor.assertProblems(/*NONE*/);
|
||||
|
||||
editor = harness.newEditor(
|
||||
"health-check-type: http\n" +
|
||||
"applications:\n" +
|
||||
"- name: my-app\n" +
|
||||
" health-check-type: process\n" +
|
||||
" health-check-http-endpoint: /health"
|
||||
);
|
||||
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `process`)");
|
||||
|
||||
editor = harness.newEditor(
|
||||
"health-check-http-endpoint: /health"
|
||||
);
|
||||
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `port`)");
|
||||
|
||||
editor = harness.newEditor(
|
||||
"health-check-type: process\n" +
|
||||
"health-check-http-endpoint: /health"
|
||||
);
|
||||
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `process`)");
|
||||
}
|
||||
|
||||
@Test public void deprecatedHealthCheckTypeQuickfix() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
|
||||
Reference in New Issue
Block a user