Concourse: reconciler checks for duplicate resource names
This commit is contained in:
@@ -27,11 +27,15 @@ import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
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.Multiset;
|
||||
import com.google.common.collect.Multisets;
|
||||
|
||||
/**
|
||||
* ConcourseModels is responsible for extracting various bits of information
|
||||
* out of .yml documents and caching them for use by various tools (reconcile engine
|
||||
@@ -76,7 +80,7 @@ public class ConcourseModel {
|
||||
* names (e.g. because there hasn't been a successful parse yet and current document contents
|
||||
* can not be parsed).
|
||||
*/
|
||||
public Set<String> getResourceNames(IDocument doc) {
|
||||
public Multiset<String> getResourceNames(IDocument doc) {
|
||||
return getStringsFromAst(doc, RESOURCE_NAMES_PATH);
|
||||
}
|
||||
|
||||
@@ -90,18 +94,18 @@ public class ConcourseModel {
|
||||
* names (e.g. because there hasn't been a successful parse yet and current document contents
|
||||
* can not be parsed).
|
||||
*/
|
||||
public Set<String> getJobNames(IDocument doc) {
|
||||
public Multiset<String> getJobNames(IDocument doc) {
|
||||
return getStringsFromAst(doc, JOB_NAMES_PATH);
|
||||
}
|
||||
|
||||
private Set<String> getStringsFromAst(IDocument doc, YamlPath path) {
|
||||
private Multiset<String> getStringsFromAst(IDocument doc, YamlPath path) {
|
||||
return getFromAst(doc, (ast) -> {
|
||||
Node root = ast.get(0);
|
||||
return path
|
||||
.traverseAmbiguously(root)
|
||||
.map(NodeUtil::asScalar)
|
||||
.filter((string) -> string!=null)
|
||||
.collect(Collectors.toSet());
|
||||
.collect(CollectorUtil.toMultiset());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,9 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
return models.getJobNames(dc.getDocument());
|
||||
}
|
||||
);
|
||||
|
||||
YAtomicType resourceNameDef = f.yatomic("Resource Name");
|
||||
resourceNameDef.parseWith(ValueParsers.resourceNameDef(models));
|
||||
|
||||
YBeanType getStep = f.ybean("GetStep");
|
||||
prop(getStep, "get", resourceName);
|
||||
@@ -157,7 +160,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
prop(step, "timeout", t_duration);
|
||||
|
||||
YBeanType resource = f.ybean("Resource");
|
||||
prop(resource, "name", t_ne_string);
|
||||
prop(resource, "name", resourceNameDef);
|
||||
prop(resource, "type", t_resource_type_name);
|
||||
prop(resource, "source", t_any);
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.RegexpParser;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware;
|
||||
|
||||
import com.google.common.collect.Multiset;
|
||||
|
||||
/**
|
||||
* Methods and constants to create/get parsers for some atomic types
|
||||
@@ -33,6 +36,19 @@ public class ValueParsers {
|
||||
|
||||
public static final ValueParser POS_INTEGER = integerRange(0, null);
|
||||
|
||||
public static final SchemaContextAware<ValueParser> resourceNameDef(ConcourseModel models) {
|
||||
return (dc) -> {
|
||||
Multiset<String> resourceNames = models.getResourceNames(dc.getDocument());
|
||||
return (String input) -> {
|
||||
if (resourceNames.count(input)<=1) {
|
||||
//okay
|
||||
return resourceNames;
|
||||
}
|
||||
throw new IllegalArgumentException("Duplicate resource name '"+input+"'");
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
public static ValueParser integerAtLeast(final Integer lowerBound) {
|
||||
return integerRange(lowerBound, null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 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.concourse.util;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* Stuff missing from {@link Collectors} that we implement ourself.
|
||||
*/
|
||||
public class CollectorUtil {
|
||||
|
||||
/**
|
||||
* Collects elements into a ImmutableMutiset (the set is converted to an immutable one
|
||||
* at the end. Accumulating / combining is done with a mutable Multiset because that involves
|
||||
* less copying.)
|
||||
*/
|
||||
public static <T> Collector<T, HashMultiset<T>, ImmutableMultiset<T>> toMultiset() {
|
||||
return new Collector<T, HashMultiset<T>, ImmutableMultiset<T>>() {
|
||||
|
||||
@Override
|
||||
public Supplier<HashMultiset<T>> supplier() {
|
||||
return HashMultiset::create;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiConsumer<HashMultiset<T>, T> accumulator() {
|
||||
return (a, e) -> a.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryOperator<HashMultiset<T>> combiner() {
|
||||
return (a1, a2) -> {
|
||||
a1.addAll(a2);
|
||||
return a1;
|
||||
};
|
||||
}
|
||||
@Override
|
||||
public Function<HashMultiset<T>, ImmutableMultiset<T>> finisher() {
|
||||
return ImmutableMultiset::copyOf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Collector.Characteristics> characteristics() {
|
||||
return ImmutableSet.of(Collector.Characteristics.UNORDERED);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -479,6 +479,29 @@ public class PipelineYamlEditorTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reconcileDuplicateResourceNames() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"resources:\n" +
|
||||
"- name: sts4\n" +
|
||||
" type: git\n" +
|
||||
" source:\n" +
|
||||
" repository: https://github.com/kdvolder/somestuff\n" +
|
||||
"- name: utils\n" +
|
||||
" type: git\n" +
|
||||
" source:\n" +
|
||||
" repository: https://github.com/kdvolder/someutils\n" +
|
||||
"- name: sts4\n" +
|
||||
" type: git\n" +
|
||||
" source:\n" +
|
||||
" repository: https://github.com/kdvolder/extras\n"
|
||||
);
|
||||
editor.assertProblems(
|
||||
"sts4|Duplicate resource name",
|
||||
"sts4|Duplicate resource name"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completionsResourceReferences() throws Exception {
|
||||
assertContextualCompletions(
|
||||
|
||||
Reference in New Issue
Block a user