resource-type name reconciling

This commit is contained in:
Kris De Volder
2017-01-24 17:48:58 -08:00
parent e43da3d6d4
commit f70d4b584d
8 changed files with 221 additions and 72 deletions

View File

@@ -18,9 +18,6 @@ package org.springframework.ide.vscode.commons.languageserver.reconcile;
*/
public class ReconcileException extends Exception implements ProblemTypeProvider {
/**
*
*/
private static final long serialVersionUID = 1L;
private final ProblemType problemType;

View File

@@ -23,15 +23,20 @@ import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import javax.inject.Provider;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multiset;
/**
* Static utility method for creating YType objects representing either
@@ -607,6 +612,21 @@ public class YTypeFactory {
return new YTypedPropertyImpl(name, type);
}
public YAtomicType yenumFromHints(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<YValueHint>> values) {
YAtomicType t = yatomic(name);
t.addHintProvider((dc) -> () -> values.withContext(dc));
t.parseWith((DynamicSchemaContext dc) -> {
Collection<String> strings = YTypeFactory.values(values.withContext(dc));
return new EnumValueParser("blah", strings) {
@Override
protected String createErrorMessage(String parseString, Collection<String> values) {
return errorMessageFormatter.apply(parseString, values);
}
};
});
return t;
}
public YAtomicType yenum(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
YAtomicType t = yatomic(name);
t.addHintProvider((dc) -> {
@@ -629,6 +649,10 @@ public class YTypeFactory {
return t;
}
public static Collection<String> values(Collection<YValueHint> hints) {
return hints.stream().map(YValueHint::getValue).collect(Collectors.toList());
}
public YAtomicType yenum(String name, String... values) {
YAtomicType t = yatomic(name);
t.addHints(values);
@@ -636,8 +660,35 @@ public class YTypeFactory {
return t;
}
public YValueHint hint(String value, String label) {
public static Callable<Collection<String>> valuesFromHintProvider(Callable<Collection<YValueHint>> hintProvider) {
Callable<Collection<String>> values = () -> {
Collection<YValueHint> hints = hintProvider.call();
if (hints != null) {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (YValueHint hint : hints ) {
builder.add(hint.getValue());
}
return builder.build();
}
return null;
};
return values;
}
public static YValueHint hint(String value, String label) {
return new BasicYValueHint(value, label);
}
public static YValueHint hint(String value) {
return new BasicYValueHint(value);
}
public static Collection<YValueHint> hints(Collection<String> values) {
if (values!=null) {
return values.stream().map(YTypeFactory::hint).collect(Collectors.toList());
}
return null;
}
}

View File

@@ -13,7 +13,12 @@ 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.valueAt;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@@ -28,14 +33,22 @@ 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.schema.YTypeFactory;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
import org.springframework.ide.vscode.concourse.util.CollectorUtil;
import org.springframework.ide.vscode.concourse.util.StaleFallbackCache;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableMultiset.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multiset;
import reactor.core.publisher.Flux;
/**
* ConcourseModels is responsible for extracting various bits of information
* out of .yml documents and caching them for use by various tools (reconcile engine
@@ -55,6 +68,12 @@ public class ConcourseModel {
valueAt("name")
);
private static final YamlPath RESOURCE_TYPE_NAMES_PATH = new YamlPath(
valueAt("resource_types"),
anyChild(),
valueAt("name")
);
private static final YamlPath RESOURCES_PATH = new YamlPath(
anyChild(), // skip over the root node which contains multiple doces
valueAt("resources"),
@@ -140,6 +159,26 @@ public class ConcourseModel {
});
}
public Multiset<String> getResourceTypeNames(IDocument doc) {
Collection<YValueHint> hints = getResourceTypeNameHints(doc);
if (hints!=null) {
return ImmutableMultiset.copyOf(YTypeFactory.values(hints));
}
return null;
}
public Collection<YValueHint> getResourceTypeNameHints(IDocument doc) {
Multiset<String> userDefined = getStringsFromAst(doc, RESOURCE_TYPE_NAMES_PATH);
if (userDefined!=null) {
Builder<YValueHint> builder = ImmutableMultiset.builder();
builder.addAll(YTypeFactory.hints(userDefined));
builder.addAll(Arrays.asList(PipelineYmlSchema.BUILT_IN_RESOURCE_TYPES));
return builder.build();
}
return null;
}
private <T> T getFromAst(IDocument doc, Function<YamlFileAST, T> astFunction) {
try {
if (doc!=null) {

View File

@@ -10,14 +10,20 @@
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.function.Function;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
import org.springframework.ide.vscode.commons.util.RegexpParser;
import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multiset;
import com.google.common.collect.ImmutableSet.Builder;
/**
* Methods and constants to create/get parsers for some atomic types
@@ -27,6 +33,17 @@ import com.google.common.collect.Multiset;
*/
public class ConcourseValueParsers {
// public static final SchemaContextAware<ValueParser> resourceTypeName(ConcourseModel models) {
// return (dc) -> {
// return new EnumValueParser("ResourceType Name", models.getResourceTypeNames(dc.getDocument())) {
// @Override
// protected String createErrorMessage(String value, Collection<String> validValues) {
// return "The '"+value+"' Resource Type does not exist. Existing resource types: "+validValues;
// }
// };
// };
// };
public static final SchemaContextAware<ValueParser> resourceNameDef(ConcourseModel models) {
return acceptOnlyUniqueNames(models::getResourceNames, "resource name");
}
@@ -61,6 +78,4 @@ public class ConcourseValueParsers {
);
}

View File

@@ -10,6 +10,9 @@
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.ValueParsers;
@@ -25,7 +28,11 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicTy
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanUnionType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPropertyImpl;
import reactor.core.publisher.Flux;
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;
/**
@@ -33,6 +40,33 @@ import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
*/
public class PipelineYmlSchema implements YamlSchema {
//TODO: the infos for composing this should probably be integrated somehow in the ResourceTypeRegistry so
// we only have a list of built-in resource types in a single place.
public static final YValueHint[] BUILT_IN_RESOURCE_TYPES = {
hint("git", "The 'git' resource can pull and push to git repositories."),
hint("hg", "The 'hg' resource can pull and push to Mercurial repositories."),
hint("time", "The 'time' resource can start jobs on a schedule or timestamp outputs."),
hint("s3", "The 's3' resource can fetch from and upload to S3 buckets."),
hint("archive", "The 'archive' resource can fetch and extract .tar.gz archives."),
hint("semver", "The 'semver' resource can set or bump version numbers."),
hint("github-release", "The 'github-release' resource can fetch and publish versioned GitHub resources."),
hint("docker-image", "The 'docker-image' resource can fetch, build, and push Docker images."),
hint("tracker", "The 'tracker' resource can deliver stories and bugs on Pivotal Tracker."),
hint("pool", "The 'pool' resource allows you to configure how to serialize use of an external system. "
+ "This lets you prevent test interference or overwork on shared systems."),
hint("cf", "The cf resource can deploy an application to Cloud Foundry."),
hint("bosh-io-release", "The bosh-io-release resource can track and fetch new BOSH releases from bosh.io."),
hint("bosh-io-stemcell", "The bosh-io-stemcell resource can track and fetch new BOSH stemcells from bosh.io."),
hint("bosh-deployment", "The bosh-deployment resource can deploy BOSH stemcells and releases."),
hint("vagrant-cloud", "The vagrant-cloud resource can fetch and publish Vagrant boxes to Atlas.")
};
public static final Set<String> BUILT_IN_RESOURCE_TYPE_NAMES = Flux.fromArray(PipelineYmlSchema.BUILT_IN_RESOURCE_TYPES)
.map(YValueHint::getValue)
.collect(Collectors.toSet())
.block();
private final YBeanType TOPLEVEL_TYPE;
private final YTypeUtil TYPE_UTIL;
@@ -76,32 +110,13 @@ public class PipelineYmlSchema implements YamlSchema {
YAtomicType t_image_type = f.yatomic("ImageType");
t_image_type.addHints("docker_image");
YAtomicType t_resource_type_name = f.yatomic("ResourceType Name");
t_resource_type_name.addHints(
f.hint("archive", "archive - The 'archive' resource can fetch and extract .tar.gz archives."),
f.hint("git", "git - The 'git' resource can pull and push to git repositories"),
f.hint("s3", "s3 - The 's3' resource can fetch from and upload to S3 buckets."),
f.hint("semver", "semver - The 'semver' resource can set or bump version numbers."),
f.hint("time", "time - The 'time' resource can start jobs on a schedule or timestamp outputs."),
f.hint("docker-image", "docker-image - The 'docker-image' resource can fetch, build, and push Docker images")
//TODO: add more resource types and descriptions.
//
// The github-release resource can fetch and publish versioned GitHub resources.
//
//
// The tracker resource can deliver stories and bugs on Pivotal Tracker
//
// The pool resource allows you to configure how to serialize use of an external system. This lets you prevent test interference or overwork on shared systems.
//
// The cf resource can deploy an application to Cloud Foundry.
//
// The bosh-io-release resource can track and fetch new BOSH releases from bosh.io.
//
// The bosh-io-stemcell resource can track and fetch new BOSH stemcells from bosh.io.
//
// The bosh-deployment resource can deploy BOSH stemcells and releases.
//
// The vagrant-cloud r
YAtomicType t_resource_type_name = f.yenumFromHints("ResourceType Name",
(parseString, validValues) -> {
return "The '"+parseString+"' Resource Type does not exist. Existing types: "+validValues;
},
(DynamicSchemaContext dc) -> {
return models.getResourceTypeNameHints(dc.getDocument());
}
);
this.t_resource_name = f.yenum("Resource Name",
@@ -109,7 +124,7 @@ public class PipelineYmlSchema implements YamlSchema {
return "The '"+parseString+"' resource does not exist. Existing resources: "+validValues;
},
(DynamicSchemaContext dc) -> {
return models.getResourceNames(dc.getDocument());
return (models.getResourceNames(dc.getDocument()));
}
);
@@ -221,6 +236,10 @@ public class PipelineYmlSchema implements YamlSchema {
initializeDefaultResourceTypes();
}
private static YValueHint hint(String value, String description) {
return YTypeFactory.hint(value, value + " - " + description);
}
private void initializeDefaultResourceTypes() {
// git :
{

View File

@@ -17,7 +17,6 @@ import java.io.InputStream;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.junit.Before;
import org.junit.Test;
@@ -396,22 +395,26 @@ public class PipelineYamlEditorTest {
@Test
public void valueCompletions() throws Exception {
String [] builtInResourceTypes = {
"git", "hg", "time", "s3",
"archive", "semver", "github-release",
"docker-image", "tracker", "pool", "cf", "bosh-io-release",
"bosh-io-stemcell", "bosh-deployment", "vagrant-cloud"
};
Arrays.sort(builtInResourceTypes);
String[] expectedCompletions = new String[builtInResourceTypes.length];
for (int i = 0; i < expectedCompletions.length; i++) {
expectedCompletions[i] =
"resources:\n" +
"- type: "+builtInResourceTypes[i]+"<*>";
}
assertCompletions(
"resources:\n" +
"- type: <*>"
, //=>
"resources:\n" +
"- type: archive<*>",
"resources:\n" +
"- type: docker-image<*>",
"resources:\n" +
"- type: git<*>",
"resources:\n" +
"- type: s3<*>",
"resources:\n" +
"- type: semver<*>",
"resources:\n" +
"- type: time<*>"
expectedCompletions
);
assertCompletions(
"jobs:\n" +
@@ -1051,9 +1054,9 @@ public class PipelineYamlEditorTest {
//addProp(resource, "name", resourceNameDef).isRequired(true);
editor = harness.newEditor(
"resources:\n" +
"- type: foo"
"- type: git"
);
editor.assertProblems("type: foo|'name' is required");
editor.assertProblems("type: git|'name' is required");
//addProp(resource, "type", t_resource_type_name).isRequired(true);
editor = harness.newEditor(
@@ -1312,6 +1315,54 @@ public class PipelineYamlEditorTest {
);
}
@Test public void reconcileResourceTypeNames() throws Exception {
String userDefinedResourceTypesSnippet =
"resource_types:\n" +
"- name: s3-multi\n" +
" type: docker-image\n" +
" source:\n" +
" repository: kdvolder/s3-resource-simple\n" +
"- name: slack-notification\n" +
" type: docker-image\n" +
" source:\n" +
" repository: cfcommunity/slack-notification-resource\n" +
" tag: latest\n";
String[] goodNames = {
//user-defined:
"s3-multi", "slack-notification",
//built-in:
"git", "hg", "time", "s3",
"archive", "semver", "github-release",
"docker-image", "tracker", "pool", "cf", "bosh-io-release",
"bosh-io-stemcell", "bosh-deployment", "vagrant-cloud"
};
String[] badNames = {
"bogus", "wrong", "not-defined-resource-type"
};
//All the bad names are detected and flagged:
for (String badName : badNames) {
Editor editor = harness.newEditor(
userDefinedResourceTypesSnippet +
"resources:\n" +
"- name: the-resource\n" +
" type: "+badName
);
editor.assertProblems(badName+"|Resource Type does not exist");
}
//All the good names are accepted:
for (String goodName : goodNames) {
Editor editor = harness.newEditor(
userDefinedResourceTypesSnippet +
"resources:\n" +
"- name: the-resource\n" +
" type: "+goodName
);
editor.assertProblems(/*None*/);
}
}
//////////////////////////////////////////////////////////////////////////////
private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception {

View File

@@ -68,7 +68,7 @@ public class ManifestYmlSchema implements YamlSchema {
if (servicesProvider != null) {
t_service_string.addHintProvider(servicesProvider);
t_service_string.parseWith(new CFServicesValueParser(t_service_string.toString(),
ManifestYmlValueParsers.getValuesFromHints(servicesProvider)));
YTypeFactory.valuesFromHintProvider(servicesProvider)));
}
YType t_services = f.yseq(t_service_string);

View File

@@ -92,27 +92,4 @@ public class ManifestYmlValueParsers {
};
}
public static ValueParser fromHints(String typeName, Callable<Collection<YValueHint>> hintProvider) {
Callable<Collection<String>> values = getValuesFromHints(hintProvider);
return new EnumValueParser(typeName, values);
}
public static Callable<Collection<String>> getValuesFromHints(Callable<Collection<YValueHint>> hintProvider) {
Callable<Collection<String>> values= () -> {
Collection<YValueHint> hints = hintProvider.call();
if (hints != null) {
Builder<String> builder = ImmutableSet.builder();
for (YValueHint hint : hints ) {
builder.add(hint.getValue());
}
return builder.build();
}
return null;
};
return values;
}
}