Extremely 'minimal' support for bosh deployment manifest editing

This commit is contained in:
Kris De Volder
2017-07-10 13:39:51 -07:00
parent 97c6f05490
commit e1825d0daf
206 changed files with 252 additions and 7317 deletions

View File

@@ -59,7 +59,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
ICompletionEngine bootCompletionEngine = new BootJavaCompletionEngine(javaProjectFinder, indexProvider);
completionEngine = new VscodeCompletionEngineAdapter(this, bootCompletionEngine);
completionEngine.setMaxCompletionsNumber(100);
completionEngine.setMaxCompletions(100);
documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion);
@@ -71,7 +71,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
}
public void setMaxCompletionsNumber(int number) {
completionEngine.setMaxCompletionsNumber(number);
completionEngine.setMaxCompletions(number);
}
}

View File

@@ -113,7 +113,7 @@ public class BootPropertiesLanguageServer extends SimpleLanguageServer {
ICompletionEngine propertiesCompletionEngine = getCompletionEngine();
completionEngine = new VscodeCompletionEngineAdapter(this, propertiesCompletionEngine);
completionEngine.setMaxCompletionsNumber(100);
completionEngine.setMaxCompletions(100);
documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion);
@@ -156,7 +156,7 @@ public class BootPropertiesLanguageServer extends SimpleLanguageServer {
}
public void setMaxCompletionsNumber(int number) {
completionEngine.setMaxCompletionsNumber(number);
completionEngine.setMaxCompletions(number);
}
public void setHoverType(HoverType type) {

View File

@@ -0,0 +1,86 @@
/*******************************************************************************
* 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.bosh;
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.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.YAtomicType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPropertyImpl;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
/**
* @author Kris De Volder
*/
public class BoshDeploymentManifestSchema implements YamlSchema {
private final AbstractType TOPLEVEL_TYPE;
private final YTypeUtil TYPE_UTIL;
public final YTypeFactory f = new YTypeFactory();
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_strings = f.yseq(t_string);
public final YAtomicType t_boolean = f.yenum("boolean", "true", "false");
public final YType t_any = f.yany("Object");
public final YType t_params = f.ymap(t_string, t_any);
public final YType t_string_params = f.ymap(t_string, t_string);
public final YType t_pos_integer = f.yatomic("Positive Integer")
.parseWith(ValueParsers.POS_INTEGER);
public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer")
.parseWith(ValueParsers.integerAtLeast(1));
public BoshDeploymentManifestSchema() {
TYPE_UTIL = f.TYPE_UTIL;
TOPLEVEL_TYPE = f.ybean("BoshDeploymentManifest");
addProp(TOPLEVEL_TYPE, "name", t_ne_string);
}
@Override
public YType getTopLevelType() {
return TOPLEVEL_TYPE;
}
@Override
public YTypeUtil getTypeUtil() {
return TYPE_UTIL;
}
private YTypedPropertyImpl prop(AbstractType beanType, String name, YType type) {
YTypedPropertyImpl prop = f.yprop(name, type);
prop.setDescriptionProvider(descriptionFor(beanType, name));
return prop;
}
public static Renderable descriptionFor(YType owner, String propName) {
String typeName = owner.toString();
return Renderables.fromClasspath(BoshDeploymentManifestSchema.class, "/desc/"+typeName+"/"+propName);
}
private YTypedPropertyImpl addProp(AbstractType bean, String name, YType type) {
return addProp(bean, bean, name, type);
}
private YTypedPropertyImpl addProp(AbstractType superType, AbstractType bean, String name, YType type) {
YTypedPropertyImpl p = prop(superType, name, type);
bean.addProperty(p);
return p;
}
}

View File

@@ -0,0 +1,84 @@
/*******************************************************************************
* Copyright (c) 2016, 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.bosh;
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter;
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.LazyCompletionResolver;
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngine;
import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
import org.springframework.ide.vscode.commons.yaml.completion.SchemaBasedYamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngine;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions;
import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider;
import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.yaml.snakeyaml.Yaml;
public class BoshLanguageServer extends SimpleLanguageServer {
private Yaml yaml = new Yaml();
private final LazyCompletionResolver completionResolver = new LazyCompletionResolver(); //Set to null to disable lazy resolving
private final VscodeCompletionEngineAdapter completionEngine;
public BoshLanguageServer() {
super("vscode-bosh");
YamlASTProvider parser = new YamlParser(yaml);
SimpleTextDocumentService documents = getTextDocumentService();
YamlSchema schema = new BoshDeploymentManifestSchema();
YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT;
YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema);
YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider, YamlCompletionEngineOptions.DEFAULT);
completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine);
completionEngine.setLazyCompletionResolver(completionResolver);
HoverInfoProvider infoProvider = new YamlHoverInfoProvider(parser, structureProvider, contextProvider);
VscodeHoverEngine hoverEngine = new VscodeHoverEngineAdapter(this, infoProvider);
YamlQuickfixes quickfixes = new YamlQuickfixes(getQuickfixRegistry(), getTextDocumentService(), structureProvider);
IReconcileEngine engine = new YamlSchemaBasedReconcileEngine(parser, schema, quickfixes);
documents.onDidChangeContent(params -> {
validateOnDocumentChange(engine, params.getDocument());
});
documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion);
documents.onHover(hoverEngine ::getHover);
}
private void validateOnDocumentChange(IReconcileEngine engine, TextDocument doc) {
if (LanguageId.BOSH_DEPLOYMENT.equals(doc.getLanguageId())) {
validateWith(doc.getId(), engine);
} else {
validateWith(doc.getId(), IReconcileEngine.NULL);
}
}
@Override
public boolean hasLazyCompletionResolver() {
return completionResolver!=null;
}
public BoshLanguageServer setMaxCompletions(int maxCompletions) {
completionEngine.setMaxCompletions(maxCompletions);
return this;
}
}

View File

@@ -9,7 +9,7 @@
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
package org.springframework.ide.vscode.bosh;
import java.io.IOException;
@@ -17,9 +17,7 @@ import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions;
public class Main {
private static final YamlCompletionEngineOptions OPTIONS = YamlCompletionEngineOptions.DEFAULT;
public static void main(String[] args) throws IOException, InterruptedException {
LaunguageServerApp.start(() -> new ConcourseLanguageServer(OPTIONS));
public static void main(String[] args) throws IOException, InterruptedException {
LaunguageServerApp.start(() -> new BoshLanguageServer());
}
}

View File

@@ -1,131 +0,0 @@
/*******************************************************************************
* 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.concourse;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.reconcile.ITypeCollector;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
/**
* An implementation of {@link ITypeCollector} which keeps track of the
* types of 'interesting' nodes in the ast.
*
* @author Kris De Volder
*/
public class ASTTypeCache implements ITypeCollector {
public interface NodeTypes {
Collection<Node> getNodes(YType type);
Map<Node, YType> getTypes();
}
/**
* Wraps around a {@link ImmutableMap}<Node, Type> and lazy builds the inverse
* map as needed.
*/
private static class NodeTypesImpl implements NodeTypes {
private ImmutableMap<Node, YType> node2type;
private Multimap<YType, Node> type2node = null; //lazy initialized when used.
public NodeTypesImpl(ImmutableMap<Node, YType> node2type) {
this.node2type = node2type;
}
@Override
public synchronized Collection<Node> getNodes(YType type) {
if (type2node==null) {
ImmutableMultimap.Builder<YType, Node> builder = ImmutableMultimap.builder();
for (Entry<Node, YType> e : node2type.entrySet()) {
builder.put(e.getValue(), e.getKey());
}
type2node = builder.build();
}
return type2node.get(type);
}
@Override
public Map<Node, YType> getTypes() {
return node2type;
}
}
/**
* Set upon commencing a reconciler session.
*/
private YamlFileAST currentAst = null;
/**
* Collects types for the current session.
*/
private ImmutableMap.Builder<Node, YType> currentTypes = null;
private final Set<YType> interestingTypes = new HashSet<>();
private final Map<String, NodeTypes> typeIndex = new HashMap<>();
@Override
public void beginCollecting(YamlFileAST ast) {
Assert.isNull("A session is already active. Concurrency isn't supported by ITypeCollector protocol", currentTypes);
this.currentAst = ast;
this.currentTypes = ImmutableMap.builder();
}
@Override
public synchronized void endCollecting(YamlFileAST ast) {
Assert.isLegal(currentAst==ast);
String uri = ast.getDocument().getUri();
typeIndex.put(uri, new NodeTypesImpl(currentTypes.build()));
this.currentAst = null;
this.currentTypes = null;
}
@Override
public void accept(Node node, YType type) {
if (interestingTypes.contains(type)) {
currentTypes.put(node, type);
}
}
public synchronized YType getType(YamlFileAST ast, Node node) {
NodeTypes types = typeIndex.get(ast.getDocument().getUri());
if (types!=null) {
return types.getTypes().get(node);
}
return null;
}
/**
* Declares a given YType as 'interesting'. This means that nodes of this type will be
* added to the index.
*/
public void addInterestingType(YType type) {
this.interestingTypes.add(type);
}
public synchronized NodeTypes getNodeTypes(String uri) {
return typeIndex.get(uri);
}
}

View File

@@ -1,111 +0,0 @@
/*******************************************************************************
* 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.concourse;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.springframework.ide.vscode.commons.languageserver.definition.SimpleDefinitionFinder;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
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.schema.YType;
import org.yaml.snakeyaml.nodes.Node;
import reactor.core.publisher.Flux;
public class ConcourseDefinitionFinder extends SimpleDefinitionFinder<ConcourseLanguageServer> {
@FunctionalInterface
private interface Handler {
Flux<Location> handle(Node refNode, TextDocument doc, YamlFileAST ast);
}
private final ConcourseModel models;
private ASTTypeCache astTypes;
private Map<YType, Handler> handlers = new HashMap<>();
public ConcourseDefinitionFinder(ConcourseLanguageServer server, ConcourseModel models, PipelineYmlSchema schema) {
super(server);
this.models = models;
this.astTypes = models.getAstTypeCache();
findByPath(schema.t_resource_name, ConcourseModel.RESOURCE_NAMES_PATH);
findByPath(schema.t_maybe_resource_name, ConcourseModel.RESOURCE_NAMES_PATH);
findByPath(schema.t_job_name, ConcourseModel.JOB_NAMES_PATH);
findByPath(schema.t_resource_type_name, ConcourseModel.RESOURCE_TYPE_NAMES_PATH);
}
/**
* Add a handler that finds the definitions for a target node within the same document
* by following a {@link YamlPath} to find candidate nodes.
*
* @param refType the type inferred by the reconciler for the target node.
* @param definitionsPath Path that points to all nodes within the same file corresponding
* to definitions of nodes of the given type.
*/
private void findByPath(YType refType, YamlPath definitionsPath) {
astTypes.addInterestingType(refType);
Handler handler = (Node refNode, TextDocument doc, YamlFileAST ast) -> {
String name = NodeUtil.asScalar(refNode);
if (name!=null) {
return Flux.fromStream(definitionsPath.traverseAmbiguously(ast))
.filter((node) -> name.equals(NodeUtil.asScalar(node)))
.map((node) -> toLocation(doc, node))
.filter(Optional::isPresent)
.map(Optional::get);
}
return Flux.empty();
};
handlers.put(refType, handler);
}
@Override
protected Flux<Location> findDefinitions(TextDocumentPositionParams params) {
try {
TextDocument doc = server.getTextDocumentService().get(params);
if (doc!=null) {
YamlFileAST ast = models.getSafeAst(doc, false);
if (ast!=null) {
Node refNode = ast.findNode(doc.toOffset(params.getPosition()));
if (refNode!=null) {
YType type = astTypes.getType(ast, refNode);
if (type!=null) {
Handler handler = handlers.get(type);
if (handler!=null) {
return handler.handle(refNode, doc, ast);
}
}
}
}
}
} catch (Exception e) {
Log.log(e);
}
return Flux.empty();
}
Optional<Location> toLocation(TextDocument doc, Node node) {
int start = node.getStartMark().getIndex();
int end = node.getEndMark().getIndex();
try {
return Optional.of(new Location(doc.getUri(), doc.toRange(start, end-start)));
} catch (BadLocationException e) {
Log.log(e);
return Optional.empty();
}
}
}

View File

@@ -1,95 +0,0 @@
/*******************************************************************************
* 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.concourse;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;
/**
* Finds symbols in a concourse document. This relies on type information cached
* during reconcile and stored in the {@link ConcourseModel}. Therefore,
* this handler only works if invoked after a reconcile.
*
* @author Kris De Volder
*/
public class ConcourseDocumentSymbolHandler implements DocumentSymbolHandler {
private ASTTypeCache astTypeCache;
private Set<YType> definitionTypes;
private SimpleTextDocumentService documents;
public ConcourseDocumentSymbolHandler(SimpleTextDocumentService documents, ASTTypeCache astTypeCache, Collection<YType> definitionTypes) {
Assert.isTrue(!definitionTypes.isEmpty()); // If there's no interesting types then you are better of using DocumentSymbolHandler.NO_SYMBOLS
this.documents = documents;
this.astTypeCache = astTypeCache;
this.definitionTypes = ImmutableSet.copyOf(definitionTypes);
for (YType yType : definitionTypes) {
astTypeCache.addInterestingType(yType);
}
}
@Override
public List<? extends SymbolInformation> handle(DocumentSymbolParams params) {
Builder<SymbolInformation> builder = ImmutableList.builder();
TextDocument doc = documents.getDocument(params.getTextDocument().getUri());
for (Entry<Node, YType> entry : astTypeCache.getNodeTypes(params.getTextDocument().getUri()).getTypes().entrySet()) {
if (definitionTypes.contains(entry.getValue())) {
try {
builder.add(createSymbol(doc, entry.getKey(), entry.getValue()));
} catch (Exception e) {
Log.log(e);
}
}
}
return builder.build();
}
protected SymbolInformation createSymbol(TextDocument doc, Node node, YType type) throws BadLocationException {
DocumentRegion region = NodeUtil.region(doc, node);
Location location = new Location(doc.getUri(), doc.toRange(region.getStart(), region.getLength()));
SymbolInformation symbol = new SymbolInformation();
symbol.setName(region.toString());
symbol.setKind(symbolKind(type));
symbol.setLocation(location);
symbol.setContainerName(containerName(type));
return symbol;
}
protected String containerName(YType type) {
return type.toString().replaceAll("(\\s)*[Nn]ame", "");
}
protected SymbolKind symbolKind(YType type) {
return SymbolKind.String; //TODO: try to return something different for different types of symbols
}
}

View File

@@ -1,179 +0,0 @@
/*******************************************************************************
* 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;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter;
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.LazyCompletionResolver;
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.completion.SchemaBasedYamlAssistContextProvider;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngine;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions;
import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider;
import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import com.google.common.collect.ImmutableList;
public class ConcourseLanguageServer extends SimpleLanguageServer {
private final YamlCompletionEngineOptions COMPLETION_OPTIONS;
YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT;
SimpleTextDocumentService documents = getTextDocumentService();
ConcourseModel models = new ConcourseModel(this);
YamlASTProvider currentAsts = models.getAstProvider(false);
private SchemaSpecificPieces forPipelines;
private SchemaSpecificPieces forTasks;
private final YamlQuickfixes yamlQuickfixes;
private final LazyCompletionResolver completionResolver = new LazyCompletionResolver(); //Set this to null to disable lazy completion resolving
private class SchemaSpecificPieces {
final VscodeCompletionEngineAdapter completionEngine;
final VscodeHoverEngineAdapter hoverEngine;
final YamlSchemaBasedReconcileEngine reconcileEngine;
final DocumentSymbolHandler symbolHandler;
SchemaSpecificPieces(YamlSchema schema, List<YType> definitionTypes) {
SchemaBasedYamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema);
YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider, COMPLETION_OPTIONS);
this.completionEngine = new VscodeCompletionEngineAdapter(ConcourseLanguageServer.this, yamlCompletionEngine);
this.completionEngine.setLazyCompletionResolver(completionResolver);
HoverInfoProvider infoProvider = new YamlHoverInfoProvider(currentAsts, structureProvider, contextProvider);
this.hoverEngine = new VscodeHoverEngineAdapter(ConcourseLanguageServer.this, infoProvider);
this.reconcileEngine = new YamlSchemaBasedReconcileEngine(currentAsts, schema, yamlQuickfixes);
reconcileEngine.setTypeCollector(models.getAstTypeCache());
this.symbolHandler = CollectionUtil.hasElements(definitionTypes)
? new ConcourseDocumentSymbolHandler(documents, models.getAstTypeCache(), definitionTypes)
: DocumentSymbolHandler.NO_SYMBOLS;
}
public void setMaxCompletions(int max) {
completionEngine.setMaxCompletionsNumber(max);
}
}
@Override
public boolean hasLazyCompletionResolver() {
return completionResolver!=null;
}
public ConcourseLanguageServer(YamlCompletionEngineOptions completionOptions) {
super("vscode-concourse");
this.COMPLETION_OPTIONS = completionOptions;
PipelineYmlSchema pipelineSchema = new PipelineYmlSchema(models);
this.yamlQuickfixes = new YamlQuickfixes(getQuickfixRegistry(), documents, structureProvider);
this.forPipelines = new SchemaSpecificPieces(pipelineSchema, pipelineSchema.getDefinitionTypes());
this.forTasks = new SchemaSpecificPieces(pipelineSchema.getTaskSchema(), null);
ConcourseDefinitionFinder definitionFinder = new ConcourseDefinitionFinder(this, models, pipelineSchema);
// SimpleWorkspaceService workspace = getWorkspaceService();
documents.onDidChangeContent(params -> {
TextDocument doc = params.getDocument();
if (LanguageId.CONCOURSE_PIPELINE.equals(doc.getLanguageId())) {
validateWith(doc.getId(), forPipelines.reconcileEngine);
} else if (LanguageId.CONCOURSE_TASK.equals(doc.getLanguageId())) {
validateWith(doc.getId(), forTasks.reconcileEngine);
} else {
validateWith(doc.getId(), IReconcileEngine.NULL);
}
});
// workspace.onDidChangeConfiguraton(settings -> {
// System.out.println("Config changed: "+params);
// Integer val = settings.getInt("languageServerExample", "maxNumberOfProblems");
// if (val!=null) {
// maxProblems = ((Number) val).intValue();
// for (TextDocument doc : documents.getAll()) {
// validateDocument(documents, doc);
// }
// }
// });
documents.onCompletion(params -> {
TextDocument doc = documents.get(params);
if (doc!=null) {
if (LanguageId.CONCOURSE_PIPELINE.equals(doc.getLanguageId())) {
return forPipelines.completionEngine.getCompletions(params);
} else if (LanguageId.CONCOURSE_TASK.equals(doc.getLanguageId())) {
return forTasks.completionEngine.getCompletions(params);
}
}
return CompletableFuture.completedFuture(new CompletionList(false, ImmutableList.of()));
});
documents.onCompletionResolve(item -> {
completionResolver.resolveNow(item);
return CompletableFuture.completedFuture(item);
});
documents.onHover(params -> {
TextDocument doc = documents.get(params);
if (doc!=null) {
if (LanguageId.CONCOURSE_PIPELINE.equals(doc.getLanguageId())) {
return forPipelines.hoverEngine.getHover(params);
} else if (LanguageId.CONCOURSE_TASK.equals(doc.getLanguageId())) {
return forTasks.hoverEngine.getHover(params);
}
}
return SimpleTextDocumentService.NO_HOVER;
});
documents.onDefinition(definitionFinder);
documents.onDocumentSymbol((params) -> {
DocumentSymbolHandler handler = DocumentSymbolHandler.NO_SYMBOLS;
TextDocument doc = documents.getDocument(params.getTextDocument().getUri());
if (doc!=null) {
if (LanguageId.CONCOURSE_PIPELINE.equals(doc.getLanguageId())) {
handler = forPipelines.symbolHandler;
} else if (LanguageId.CONCOURSE_TASK.equals(doc.getLanguageId())) {
handler = forTasks.symbolHandler;
}
}
return handler.handle(params);
});
}
@Override
protected DiagnosticSeverity getDiagnosticSeverity(ReconcileProblem problem) {
ProblemType type = problem.getType();
if (YamlSchemaProblems.PROPERTY_CONSTRAINT.contains(type)) {
return DiagnosticSeverity.Warning;
}
return super.getDiagnosticSeverity(problem);
}
public SimpleLanguageServer setMaxCompletions(int max) {
forPipelines.setMaxCompletions(max);
forTasks.setMaxCompletions(max);
return this;
}
}

View File

@@ -1,460 +0,0 @@
/*******************************************************************************
* 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;
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.Optional;
import java.util.Set;
import java.util.function.Function;
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.StringUtil;
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.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.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.constraints.Constraint;
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.util.Streams;
import org.springframework.ide.vscode.concourse.ASTTypeCache.NodeTypes;
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.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableMultiset.Builder;
import com.google.common.collect.Multiset;
/**
* ConcourseModels is responsible for extracting various bits of information
* out of .yml documents and caching them for use by various tools (reconcile engine
* and completion engine).
*/
public class ConcourseModel {
/**
* Verification of a 'isUsed' contraint. Basically this consults the ast-type cache, (which should be
* fully populated at the end reconciling) to see if the nodes of any nodes of a given type (representing
* a 'use' of something, contain the value of the current node (which is supposed to be a definition of
* that same type of something).
*/
public Constraint isUsed(YType refType, String entityTypeName) {
getAstTypeCache().addInterestingType(refType); //ensure the type is tracked in the type-cache
return new Constraint() {
@Override
public void verify(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) {
String defName = NodeUtil.asScalar(node);
if (StringUtil.hasText(defName)) { //Avoid silly 'not used' errors for empty names (will have an other error already).
NodeTypes nodeTypes = getAstTypeCache().getNodeTypes(dc.getDocument().getUri());
if (nodeTypes!=null) {
Optional<Node> reference = nodeTypes.getNodes(refType).stream()
.filter(refNode -> defName.equals(NodeUtil.asScalar(refNode)))
.findAny();
if (!reference.isPresent()) {
problems.accept(YamlSchemaProblems.problem(PipelineYmlSchemaProblems.UNUSED_RESOURCE, "Unused '"+entityTypeName+"'", node));
}
}
}
}
};
}
/**
* 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 && getResource(dc.getDocument(), 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) {
return Streams.getSingle(
getFromAst(doc, ast ->
JOBS_PATH.traverseAmbiguously(ast)
.filter(node -> jobName.equals(NodeUtil.getScalarProperty(node, "name")))
.map(JobModel::new)
)
);
}
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
* provides methods for accessing information from the node.
*/
public static class StepModel {
private final String stepType;
private final MappingNode step;
public StepModel(String stepType, MappingNode step) {
this.stepType = stepType;
this.step = step;
}
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);
}
public String getResourceName() {
return NodeUtil.asScalar(getResourceNameNode());
}
}
public static class ResourceModel {
private final Node resource;
public ResourceModel(Node resource) {
this.resource = resource;
}
public String getType() {
return NodeUtil.getScalarProperty(resource, "type");
}
public boolean hasSourceProperty(String propName) {
YamlPath path = new YamlPath(YamlPathSegment.valueAt("source"), YamlPathSegment.keyAt(propName));
return path.traverseAmbiguously(resource).findFirst().isPresent();
}
}
public static final YamlPath JOBS_PATH = new YamlPath(
anyChild(),
valueAt("jobs"),
anyChild()
);
public static final YamlPath JOB_NAMES_PATH = new YamlPath(
anyChild(),
valueAt("jobs"),
anyChild(),
valueAt("name")
);
public static final YamlPath RESOURCE_TYPE_NAMES_PATH = new YamlPath(
anyChild(),
valueAt("resource_types"),
anyChild(),
valueAt("name")
);
public static final YamlPath RESOURCES_PATH = new YamlPath(
anyChild(), // skip over the root node which contains multiple doces
valueAt("resources"),
anyChild()
);
public static final YamlPath RESOURCE_NAMES_PATH = RESOURCES_PATH.append(
valueAt("name")
);
private final YamlParser parser;
private final StaleFallbackCache<String, YamlFileAST> asts = new StaleFallbackCache<>();
private final ASTTypeCache astTypes = new ASTTypeCache();
private ResourceTypeRegistry resourceTypes;
private final Supplier<SnippetBuilder> snippetBuilderFactory;
private YBeanUnionType stepType;
public ConcourseModel(SimpleLanguageServer languageServer) {
Yaml yaml = new Yaml();
this.parser = new YamlParser(yaml);
this.snippetBuilderFactory = languageServer::createSnippetBuilder;
}
/**
* Returns the resource names that are defined by given IDocument. If the contents
* of IDocument is not currently parseable then this may return stale information
* retained from a previous successful parse.
* <p>
* It may also return null if its not currently possible to obtain the list of resource
* names (e.g. because there hasn't been a successful parse yet and current document contents
* can not be parsed).
*/
public Multiset<String> getResourceNames(DynamicSchemaContext dc) {
return getStringsFromAst(dc.getDocument(), RESOURCE_NAMES_PATH);
}
/**
* Get the resource type tag associated with a given resourceName in the given document.
* <p>
* If the content of IDocument is not currently parseable then this may return stale information
* retained from a previous successful parse.
* <p>
* It may also return null if its not currently possible to obtain type of the resource. E.g
* because there is no such resource, the resource has no valid type tag, or the document
* was never successfully parsed.
*/
public String getResourceType(IDocument doc, String resourceName) {
ResourceModel resource = getResource(doc, resourceName);
if (resource!=null) {
return resource.getType();
}
return null;
}
public ResourceModel getResource(IDocument doc, String resourceName) {
return getFromAst(doc, (ast) -> {
Node resource = RESOURCES_PATH.traverseAmbiguously(new ASTRootCursor(ast))
.map((cursor) -> ((NodeCursor)cursor).getNode())
.filter((resourceNode) -> resourceName.equals(NodeUtil.getScalarProperty(resourceNode, "name")))
.findFirst().orElse(null);
if (resource!=null) {
return new ResourceModel(resource);
}
return null;
});
}
/**
* Returns the job names that are defined by given IDocument. If the contents
* of IDocument is not currently parseable then this may return stale information
* retained from a previous successful parse.
* <p>
* It may also return null if its not currently possible to obtain the list of resource
* names (e.g. because there hasn't been a successful parse yet and current document contents
* can not be parsed).
*/
public Multiset<String> getJobNames(DynamicSchemaContext dc) {
return getStringsFromAst(dc.getDocument(), JOB_NAMES_PATH);
}
private Multiset<String> getStringsFromAst(IDocument doc, YamlPath path) {
return getFromAst(doc, (ast) -> {
return path
.traverseAmbiguously(ast)
.map(NodeUtil::asScalar)
.filter((string) -> string!=null)
.collect(CollectorUtil.toMultiset());
});
}
public Multiset<String> getResourceTypeNames(DynamicSchemaContext dc) {
Collection<YValueHint> hints = getResourceTypeNameHints(dc);
if (hints!=null) {
return ImmutableMultiset.copyOf(YTypeFactory.values(hints));
}
return null;
}
public Collection<YValueHint> getResourceTypeNameHints(DynamicSchemaContext dc) {
IDocument doc = dc.getDocument();
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.stream(PipelineYmlSchema.BUILT_IN_RESOURCE_TYPES)
.map(h -> addExtraInsertion(h, dc))
.collect(Collectors.toList())
);
return builder.build();
}
return null;
}
public Node getParentPropertyNode(String propName, DynamicSchemaContext dc) {
YamlPath path = dc.getPath();
if (path!=null) {
YamlFileAST root = this.getSafeAst(dc.getDocument());
if (root!=null) {
return path.dropLast().append(YamlPathSegment.valueAt(propName)).traverseToNode(root);
}
}
return null;
}
private YValueHint addExtraInsertion(YValueHint h, DynamicSchemaContext dc) {
return new BasicYValueHint(h.getValue(), h.getLabel()).setExtraInsertion(() -> {
String resourceTypeName = h.getValue();
AbstractType sourceType = (AbstractType) resourceTypes.getSourceType(resourceTypeName);
if (sourceType!=null && getParentPropertyNode("source", dc)==null) { //don't auto insert what's already there!
List<YTypedProperty> requiredProps = sourceType.getProperties().stream().filter(p -> p.isRequired()).collect(Collectors.toList());
if (!requiredProps.isEmpty()) {
SnippetBuilder snippet = snippetBuilderFactory.get();
snippet.text("\nsource:");
for (YTypedProperty p : requiredProps) {
snippet.text("\n "+p.getName()+": ");
snippet.placeHolder();
}
return snippet.toString();
}
}
return null;
});
}
private <T> T getFromAst(IDocument doc, Function<YamlFileAST, T> astFunction) {
try {
if (doc!=null) {
String uri = doc.getUri();
if (uri!=null) {
YamlFileAST ast = getAst(doc, true);
return astFunction.apply(ast);
}
}
} catch (YAMLException e) {
// ignore: found garbage in the doc. Can't compute stuff and that's to be expected.
} catch (Exception e) {
Log.log(e);
}
return null;
}
public YamlFileAST getSafeAst(IDocument doc) {
return getSafeAst(doc, true);
}
public YamlFileAST getAst(IDocument doc, boolean allowStaleAst) throws Exception {
return getAstProvider(allowStaleAst).getAST(doc);
}
public YamlASTProvider getAstProvider(boolean allowStaleAsts) {
return (IDocument doc) -> {
String uri = doc.getUri();
if (uri!=null) {
return asts.get(uri, doc.getVersion(), allowStaleAsts, () -> {
return parser.getAST(doc);
});
}
return null;
};
}
public YamlFileAST getSafeAst(IDocument doc, boolean allowStaleAst) {
if (doc!=null) {
try {
return getAst(doc, allowStaleAst);
} catch (Exception e) {
//ignored
}
}
return null;
}
public ASTTypeCache getAstTypeCache() {
return astTypes;
}
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

@@ -1,110 +0,0 @@
/*******************************************************************************
* 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;
import java.util.function.Function;
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.yaml.schema.DynamicSchemaContext;
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
* used in manifest yml schema.
*
* @author Kris De Volder
*/
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");
}
public static final SchemaContextAware<ValueParser> jobNameDef(ConcourseModel models) {
return acceptOnlyUniqueNames(models::getJobNames, "job name");
}
public static SchemaContextAware<ValueParser> resourceTypeNameDef(ConcourseModel models) {
return acceptOnlyUniqueNames(models::getResourceTypeNames, "resource-type name");
}
public static SchemaContextAware<ValueParser> acceptOnlyUniqueNames(
Function<DynamicSchemaContext, Multiset<String>> getDefinedNameCounts,
String typeName
) {
return acceptOnlyUniqueNames(getDefinedNameCounts, typeName, false);
}
public static SchemaContextAware<ValueParser> acceptOnlyUniqueNames(
Function<DynamicSchemaContext, Multiset<String>> getDefinedNameCounts,
String typeName,
boolean allowEmptyName
) {
return (dc) -> {
return (String input) -> {
if (!allowEmptyName && !StringUtil.hasText(input)) {
throw new ValueParseException("'"+typeName +"' should not be blank");
}
Multiset<String> resourceNames = getDefinedNameCounts.apply(dc);
if (resourceNames.count(input)<=1) {
//okay
return input;
}
throw new ValueParseException("Duplicate "+typeName+" '"+input+"'");
};
};
};
public static ValueParser DURATION = new RegexpParser(
"^(([0-9]+(.[0-9]+)?)(ns|us|µs|ms|s|h|m))+$",
"Duration",
" A duration string is a sequence of decimal numbers, each with "
+ "optional fraction and a unit suffix, such as '300ms', '1.5h' or"
+ " '2h45m'. Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', "
+ "'m', 'h'."
);
public static final ValueParser TIME_OF_DAY = new RegexpParser(
createTimeRegexp(),
"Time",
"Supported time formats are: 3:04 PM, 3PM, 3 PM, 15:04, and 1504. "
+ "Deprecation: an offset may be appended, e.g. +0700 or -0400, but "
+ "you should use location instead."
);
private static String createTimeRegexp() {
String hours = "([0-2]?[0-9])";
String minutes = "([0-6][0-9])";
String time = hours+"((:"+minutes+")|"+minutes+")?";
String pm = "(\\s?[AP]M)?";
String zone = "(\\s(\\+|\\-)[0-9][0-9][0-9][0-9])?";
return time + pm + zone;
}
}

View File

@@ -1,724 +0,0 @@
/*******************************************************************************
* 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;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.MimeTypes;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
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.util.text.LanguageId;
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.BasicYValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware;
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.YAtomicType;
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 org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
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.YamlSchema;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
import org.springframework.ide.vscode.concourse.ConcourseModel.ResourceModel;
import org.springframework.ide.vscode.concourse.ConcourseModel.StepModel;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.ImmutableList;
/**
* @author Kris De Volder
*/
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.")
};
private final AbstractType TOPLEVEL_TYPE;
private final YTypeUtil TYPE_UTIL;
public final YTypeFactory f = new YTypeFactory();
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_strings = f.yseq(t_string);
public final YType t_pair = f.ybean("NameValuePair",
f.yprop("name", t_string),
f.yprop("value", t_string)
);
public final YType t_pair_list = f.yseq(t_pair);
public final YAtomicType t_boolean = f.yenum("boolean", "true", "false");
public final YType t_any = f.yany("Object");
public final YType t_params = f.ymap(t_string, t_any);
public final YType t_string_params = f.ymap(t_string, t_string);
public final YType t_pos_integer = f.yatomic("Positive Integer")
.parseWith(ValueParsers.POS_INTEGER);
public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer")
.parseWith(ValueParsers.integerAtLeast(1));
public final AbstractType t_resource_name;
public final YAtomicType t_maybe_resource_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)
.addHints(MimeTypes.getKnownMimeTypes());
public final YType t_duration = f.yatomic("Duration")
.parseWith(ConcourseValueParsers.DURATION);
public final YType t_time_of_day = f.yatomic("TimeOfDay")
.parseWith(ConcourseValueParsers.TIME_OF_DAY);
public final YType t_location = f.yatomic("Location")
//Note: we could have used f.yenum here too. But it saves memory if we don't keep the large set of ValueHints in memory.
// That's why we attach custom hint provider and parser here that do essentially the same thing.
.addHintProvider(() -> {
return ZoneId.getAvailableZoneIds().stream()
.map(BasicYValueHint::new)
.collect(Collectors.toList());
})
.parseWith(ValueParser.of((zoneId) -> {
if (!ZoneId.getAvailableZoneIds().contains(zoneId)) {
throw new ValueParseException("Unknown 'Location'. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones");
}
return zoneId;
}));
public final AbstractType task;
private final ResourceTypeRegistry resourceTypes = new ResourceTypeRegistry();
private final ConcourseModel models;
public final YType t_semver = f.yatomic("Semver")
.parseWith(ValueParsers.NE_STRING); //TODO: use real semver parser.
public final YType t_s3_region = f.yenum("S3Region",
//See: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html
"us-west-1", "us-west-2",
"ca-central-1", "EU", "eu-west-1",
"eu-west-2", "eu-central-1",
"ap-south-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ap-northeast-2",
"sa-east-1",
"us-east-2"
);
public final YType t_day = f.yenum("Day",
//See https://github.com/concourse/time-resource#source-configuration
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
);
private List<YType> definitionTypes = new ArrayList<>();
public PipelineYmlSchema(ConcourseModel models) {
this.models = models;
models.setResourceTypeRegistry(resourceTypes);
TYPE_UTIL = f.TYPE_UTIL;
// define schema types
TOPLEVEL_TYPE = f.ybean("Pipeline");
YAtomicType t_version = f.yatomic("Version");
t_version.addHints("latest", "every");
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);
}
);
t_resource_name = f.yenum("Resource Name",
(parseString, validValues) -> {
return "The '"+parseString+"' resource does not exist. Existing resources: "+validValues;
},
(DynamicSchemaContext dc) -> {
return (models.getResourceNames(dc));
}
);
t_maybe_resource_name = f.yatomic("ResourceName | TaskOutput");
t_maybe_resource_name.addHintProvider((DynamicSchemaContext dc) -> {
//Putting the Callable into a local variable is strange, but the compiler doesn't like it if
// we return it directly. Too much complexity for Java type-inference?
Callable<Collection<YValueHint>> callable = () -> YTypeFactory.hints(models.getResourceNames(dc));
return callable;
});
t_maybe_resource_name.parseWith(ValueParsers.NE_STRING);
t_job_name = f.yenum("Job Name",
(parseString, validValues) -> {
return "The '"+parseString+"' Job does not exist. Existing jobs: "+validValues;
},
(DynamicSchemaContext dc) -> {
return models.getJobNames(dc);
}
).require(models::passedJobHasInteractionWithResource);
YAtomicType t_resource_name_def = f.yatomic("Resource Name");
t_resource_name_def.parseWith(ConcourseValueParsers.resourceNameDef(models));
t_resource_name_def.require(models.isUsed(t_resource_name, "Resource"));
YAtomicType jobNameDef = f.yatomic("Job Name");
jobNameDef.parseWith(ConcourseValueParsers.jobNameDef(models));
YAtomicType resourceTypeNameDef = f.yatomic("ResourceType Name");
resourceTypeNameDef.parseWith(ConcourseValueParsers.resourceTypeNameDef(models));
YType resourceSource = f.contextAware("ResourceSource", (dc) ->
resourceTypes.getSourceType(getResourceTypeTag(models, dc))
);
AbstractType t_resource = f.ybean("Resource");
addProp(t_resource, "name", t_resource_name_def).isPrimary(true);
addProp(t_resource, "type", t_resource_type_name).isRequired(true);
addProp(t_resource, "source", resourceSource);
addProp(t_resource, "check_every", t_duration);
addProp(t_resource, "webhook_token", t_ne_string);
AbstractType t_image_resource = f.ybean("ImageResource");
for (YTypedProperty p : t_resource.getProperties()) {
if (!"name".equals(p.getName())) {
t_image_resource.addProperty(p);
}
}
YAtomicType t_platform = f.yenum("Platform", "windows", "linux", "darwin");
t_platform.parseWith(ValueParsers.NE_STRING); //no errors because in theory platform are just strings.
AbstractType t_input = f.ybean("TaskInput");
addProp(t_input, "name", t_ne_string).isPrimary(true);
addProp(t_input, "path", t_ne_string);
AbstractType t_output = f.ybean("TaskOutput");
addProp(t_output, "name", t_ne_string).isPrimary(true);
addProp(t_output, "path", t_ne_string);
AbstractType t_command = f.ybean("Command");
addProp(t_command, "path", t_ne_string).isRequired(true);
addProp(t_command, "args", t_strings);
addProp(t_command, "dir", t_ne_string);
addProp(t_command, "user", t_string);
task = f.ybean("TaskConfig");
addProp(task, "platform", t_platform).isRequired(true);
addProp(task, "image_resource", t_image_resource);
addProp(task, "rootfs_uri", t_ne_string);
addProp(task, "image", t_ne_string).isDeprecated("The 'image' property in 'TaskConfig' is renamed to 'rootfs_uri' in Concourse 3.0");
addProp(task, "inputs", f.yseq(t_input));
addProp(task, "outputs", f.yseq(t_output));
addProp(task, "run", t_command).isRequired(true);
addProp(task, "params", t_string_params);
task.require(Constraints.schemaContextAware((DynamicSchemaContext dc) -> {
LanguageId languageId = dc.getDocument().getLanguageId();
if (LanguageId.CONCOURSE_PIPELINE.equals(languageId)) {
Node parentImageDef = models.getParentPropertyNode("image", dc);
if (parentImageDef==null) {
return Constraints.requireOneOf("image_resource", "rootfs_uri", "image");
} else {
return Constraints.deprecated((name) ->
"Deprecated: This attribute in the task config will be ignored! "+
"The 'image' attribute on the task itself takes precedence.",
"image_resource", "rootfs_uri", "image"
);
}
} else {
return Constraints.requireAtMostOneOf("image_resource", "rootfs_uri", "image");
}
}));
AbstractType t_put_get_name = f.contextAware("Name", (dc) -> {
if (models.getParentPropertyNode("resource", dc)!=null) {
return null;
} else {
return t_resource_name;
}
})
.treatAsAtomic()
.parseWith(ValueParsers.NE_STRING);
YBeanType getStep = f.ybean("GetStep");
addProp(getStep, "get", t_put_get_name);
addProp(getStep, "resource", t_resource_name);
addProp(getStep, "version", t_version);
addProp(getStep, "passed", f.yseq(t_job_name));
addProp(getStep, "params", f.contextAware("GetParams", (dc) ->
resourceTypes.getInParamsType(getResourceType("get", models, dc))
));
addProp(getStep, "trigger", t_boolean);
YBeanType putStep = f.ybean("PutStep");
addProp(putStep, "put", t_put_get_name);
addProp(putStep, "resource", t_resource_name);
addProp(putStep, "params", f.contextAware("PutParams", (dc) ->
resourceTypes.getOutParamsType(getResourceType("put", models, dc))
));
addProp(putStep, "get_params", f.contextAware("GetParams", (dc) ->
resourceTypes.getInParamsType(getResourceType("put", models, dc))
));
putStep.require((DynamicSchemaContext dc, Node parent, Node _map, YType type, IProblemCollector problems) -> {
if (_map instanceof MappingNode) {
MappingNode map = (MappingNode) _map;
StepModel step = models.newStep(map);
String resourceName = step.getResourceName();
if (resourceName!=null) {
ResourceModel resource = models.getResource(dc.getDocument(), resourceName);
if (resource!=null) {
if ("git".equals(resource.getType()) && !resource.hasSourceProperty("branch")) {
problems.accept(YamlSchemaProblems.schemaProblem(
"Resource of type 'git' is used in a 'put' step, so it should define 'branch' attribute in its 'source', but it doesn't.",
step.getResourceNameNode()
));
}
}
}
}
});
YBeanType taskStep = f.ybean("TaskStep");
addProp(taskStep, "task", t_ne_string);
addProp(taskStep, "file", t_string);
addProp(taskStep, "config", task);
addProp(taskStep, "privileged", t_boolean);
addProp(taskStep, "params", t_params);
addProp(taskStep, "image", t_resource_name);
addProp(taskStep, "input_mapping", f.ymap(t_ne_string, t_maybe_resource_name));
addProp(taskStep, "output_mapping", t_string_params);
taskStep.requireOneOf("config", "file");
YBeanType aggregateStep = f.ybean("AggregateStep");
YBeanType doStep = f.ybean("DoStep");
YBeanType tryStep = f.ybean("TryStep");
YBeanType[] stepTypes = {
getStep,
putStep,
taskStep,
aggregateStep,
doStep,
tryStep
};
YBeanUnionType step = f.yunion("Step", stepTypes);
addProp(aggregateStep, "aggregate", f.yseq(step));
addProp(doStep, "do", f.yseq(step));
addProp(tryStep, "try", step);
// shared properties applicable for any subtype of Step:
for (AbstractType subStep : stepTypes) {
addProp(step, subStep, "on_success", step);
addProp(step, subStep, "on_failure", step);
addProp(step, subStep, "ensure", step);
addProp(step, subStep, "attempts", t_strictly_pos_integer);
addProp(step, subStep, "tags", t_strings);
addProp(step, subStep, "timeout", t_duration);
}
models.setStepType(step);
AbstractType job = f.ybean("Job");
addProp(job, "name", jobNameDef).isPrimary(true);
addProp(job, "plan", f.yseq(step)).isRequired(true);
addProp(job, "serial", t_boolean);
addProp(job, "build_logs_to_retain", t_pos_integer);
addProp(job, "serial_groups", t_strings);
addProp(job, "max_in_flight", t_pos_integer);
addProp(job, "public", t_boolean);
addProp(job, "disable_manual_trigger", t_boolean);
addProp(job, "interruptible", t_boolean);
addProp(job, "ensure", step);
addProp(job, "on_failure", step);
addProp(job, "on_success", step);
AbstractType resourceType = f.ybean("ResourceType");
addProp(resourceType, "name", resourceTypeNameDef).isPrimary(true);
addProp(resourceType, "type", t_resource_type_name).isRequired(true);
addProp(resourceType, "source", resourceSource);
AbstractType group = f.ybean("Group");
addProp(group, "name", t_ne_string).isPrimary(true);
addProp(group, "resources", f.yseq(t_resource_name));
addProp(group, "jobs", f.yseq(t_job_name));
addProp(TOPLEVEL_TYPE, "resources", f.yseq(t_resource));
addProp(TOPLEVEL_TYPE, "jobs", f.yseq(job));
addProp(TOPLEVEL_TYPE, "resource_types", f.yseq(resourceType));
addProp(TOPLEVEL_TYPE, "groups", f.yseq(group));
definitionTypes = ImmutableList.of(
jobNameDef,
resourceTypeNameDef,
t_resource_name_def
);
initializeDefaultResourceTypes();
}
private static YValueHint hint(String value, String description) {
return YTypeFactory.hint(value, value + " - " + description);
}
private void initializeDefaultResourceTypes() {
// git :
{
AbstractType source = f.ybean("GitSource");
addProp(source, "uri", t_ne_string).isPrimary(true);
addProp(source, "branch", t_ne_string); //It's more complicated than that! Its only required in 'put' step. So we'll check this as a contrain in put steps!
addProp(source, "private_key", t_ne_string);
addProp(source, "username", t_ne_string);
addProp(source, "password", t_string);
addProp(source, "paths", t_strings);
addProp(source, "ignore_paths", t_strings);
addProp(source, "skip_ssl_verification", t_boolean);
addProp(source, "tag_filter", t_string);
addProp(source, "git_config", t_pair_list);
addProp(source, "disable_ci_skip", t_boolean);
addProp(source, "commit_verification_keys", t_strings);
addProp(source, "commit_verification_key_ids", t_strings);
addProp(source, "gpg_keyserver", t_string);
AbstractType get = f.ybean("GitGetParams");
addProp(get, "depth", t_pos_integer);
addProp(get, "submodules", f.yany("GitSubmodules").addHints("all", "none"));
addProp(get, "disable_git_lfs", t_boolean);
addProp(get, "fetch", t_strings); //Warning: t_strings is just a guess. This property is undocumented. The example I've seen seem to use list of git branch/tag names.
AbstractType put = f.ybean("GitPutParams");
addProp(put, "repository", t_ne_string).isPrimary(true);
addProp(put, "rebase", t_boolean);
addProp(put, "tag", t_ne_string);
addProp(put, "only_tag", t_boolean);
addProp(put, "tag_prefix", t_string);
addProp(put, "force", t_boolean);
addProp(put, "annotate", t_ne_string);
resourceTypes.def("git", source, get, put);
}
//docker-image:
{
AbstractType source = f.ybean("DockerImageSource");
addProp(source, "repository", t_ne_string).isPrimary(true);
addProp(source, "tag", t_ne_string);
addProp(source, "username", t_ne_string);
addProp(source, "password", t_ne_string);
addProp(source, "aws_access_key_id", t_ne_string);
addProp(source, "aws_secret_access_key", t_ne_string);
addProp(source, "insecure_registries", t_strings);
addProp(source, "registry_mirror", t_ne_string);
addProp(source, "ca_certs", f.yseq(f.ybean("CaCertsEntry",
f.yprop("domain", t_ne_string),
f.yprop("cert", t_ne_string)
)));
addProp(source, "client_certs", f.yseq(f.ybean("ClientCertsEntry",
f.yprop("domain", t_ne_string),
f.yprop("key", t_ne_string),
f.yprop("cert", t_ne_string)
)));
AbstractType get = f.ybean("DockerImageGetParams");
addProp(get, "save", t_boolean);
addProp(get, "rootfs", t_boolean);
addProp(get, "skip_download", t_boolean);
AbstractType put = f.ybean("DockerImagePutParams");
addProp(put, "build", t_ne_string);
addProp(put, "load", t_ne_string);
addProp(put, "dockerfile", t_ne_string);
addProp(put, "cache", t_boolean);
addProp(put, "cache_tag", t_ne_string);
addProp(put, "load_base", t_ne_string);
addProp(put, "load_file", t_ne_string);
addProp(put, "load_repository", t_ne_string);
addProp(put, "load_tag", t_ne_string);
addProp(put, "import_file", t_ne_string);
addProp(put, "pull_repository", t_ne_string).isDeprecated(true);
addProp(put, "pull_tag", t_ne_string).isDeprecated(true);
addProp(put, "tag", t_ne_string);
addProp(put, "tag_prefix", t_ne_string);
addProp(put, "tag_as_latest", t_boolean);
addProp(put, "build_args", t_string_params);
addProp(put, "build_args_file", t_ne_string);
resourceTypes.def("docker-image", source, get, put);
}
//s3
{
YType t_canned_acl = f.yenum("S3CannedAcl",
//See http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
"private", "public-read", "public-read-write", "aws-exec-read",
"authenticated-read", "bucket-owner-read", "bucket-owner-full-control",
"log-delivery-write"
);
AbstractType source = f.ybean("S3Source");
addProp(source, "bucket", t_ne_string).isPrimary(true);
addProp(source, "access_key_id", t_ne_string);
addProp(source, "secret_access_key", t_ne_string);
addProp(source, "region_name", t_s3_region);
addProp(source, "private", t_boolean);
addProp(source, "cloudfront_url", t_ne_string);
addProp(source, "endpoint", t_ne_string);
addProp(source, "disable_ssl", t_boolean);
addProp(source, "server_side_encryption", t_ne_string);
addProp(source, "sse_kms_key_id", t_ne_string);
addProp(source, "use_v2_signing", t_boolean);
addProp(source, "regexp", t_ne_string);
addProp(source, "versioned_file", t_ne_string);
source.requireOneOf("regexp", "versioned_file");
AbstractType get = f.ybean("S3GetParams");
//Note: S3GetParams intentionally has no properties since no params are expected according to the docs.
AbstractType put = f.ybean("S3PutParams");
addProp(put, "file", t_ne_string).isPrimary(true);
addProp(put, "acl", t_canned_acl);
addProp(put, "content_type", t_mime_type);
resourceTypes.def("s3", source, get, put);
}
//pool
{
AbstractType source = f.ybean("PoolSource");
addProp(source, "uri", t_ne_string).isRequired(true);
addProp(source, "branch", t_ne_string).isRequired(true);
addProp(source, "pool", t_ne_string).isRequired(true);
addProp(source, "private_key", t_ne_string);
addProp(source, "username", t_ne_string);
addProp(source, "password", t_string);
addProp(source, "retry_delay", t_duration);
AbstractType get = f.ybean("PoolGetParams");
//get params deliberately left empty
AbstractType put = f.ybean("PoolPutParams");
addProp(put, "acquire", t_boolean);
addProp(put, "claim", t_ne_string);
addProp(put, "release", t_ne_string);
addProp(put, "add", t_ne_string);
addProp(put, "add_claimed", t_ne_string);
addProp(put, "remove", t_ne_string);
put.requireOneOf(put.getPropertyNames());
resourceTypes.def("pool", source, get, put);
}
//semver
{
AbstractType git_source = f.ybean("GitSemverSource");
addProp(git_source, "uri", t_ne_string).isPrimary(true);
addProp(git_source, "branch", t_ne_string).isRequired(true);
addProp(git_source, "file", t_ne_string).isRequired(true);
addProp(git_source, "private_key", t_ne_string);
addProp(git_source, "username", t_ne_string);
addProp(git_source, "password", t_ne_string);
addProp(git_source, "git_user", t_ne_string);
AbstractType s3_source = f.ybean("S3SemverSource");
addProp(s3_source, "bucket", t_ne_string).isPrimary(true);
addProp(s3_source, "key", t_ne_string).isRequired(true);
addProp(s3_source, "access_key_id", t_ne_string).isRequired(true);
addProp(s3_source, "secret_access_key", t_ne_string).isRequired(true);
addProp(s3_source, "region_name", t_s3_region);
addProp(s3_source, "endpoint", t_ne_string);
addProp(s3_source, "disable_ssl", t_boolean);
AbstractType swift_source = f.ybean("SwiftSemverSource");
addProp(swift_source, "openstack", t_any).isPrimary(true);
AbstractType[] driverSpecificSources = {
git_source, s3_source, swift_source
};
AbstractType source = f.contextAware("SemverSource", (dc) -> {
switch (getSemverDriverName(dc)) {
case "git":
return git_source;
case "s3":
return s3_source;
case "swift":
return swift_source;
default:
return null;
}
}).treatAsBean();
addProp(source, "initial_version", t_semver);
addProp(source, "driver", f.yenum("SemverDriver", "git", "s3", "swift")).isPrimary(true, false);
for (AbstractType s : driverSpecificSources) {
for (YTypedProperty p : source.getProperties()) {
s.addProperty(p);
}
}
AbstractType get = f.ybean("SemverGetParams");
addProp(get, "bump", f.yenum("SemverBump", "major", "minor", "patch", "final"));
addProp(get, "pre", t_ne_string);
AbstractType put = f.ybean("SemverPutParams");
for (YTypedProperty p : get.getProperties()) {
put.addProperty(p);
}
addProp(put, "file", t_ne_string);
resourceTypes.def("semver", source, get, put);
}
//time:
{
AbstractType source = f.ybean("TimeSource");
addProp(source, "interval", t_duration);
addProp(source, "location", t_location);
addProp(source, "start", t_time_of_day);
addProp(source, "stop", t_time_of_day);
addProp(source, "days", f.yseq(t_day));
AbstractType get = f.ybean("TimeGetParams");
//get params deliberately left empty
AbstractType put = f.ybean("TimePutParams");
//put params deliberately left empty
resourceTypes.def("time", source, get, put);
}
}
private String getSemverDriverName(DynamicSchemaContext dc) {
String driver = getSiblingPropertyValue(dc, "driver");
return driver!=null ? driver : "s3";
}
private Node getResourceNameNode(String resourceNameProp, DynamicSchemaContext dc) {
Node resourceName = models.getParentPropertyNode("resource", dc);
if (resourceName==null) {
resourceName = models.getParentPropertyNode(resourceNameProp, dc);
}
return resourceName;
}
private String getResourceName(String resourceNameProp, DynamicSchemaContext dc) {
Node resourceName = getResourceNameNode(resourceNameProp, dc);
return NodeUtil.asScalar(resourceName);
}
private String getResourceType(String resourceNameProp, ConcourseModel models, DynamicSchemaContext dc) {
String resourceName = getResourceName(resourceNameProp, dc);
if (resourceName!=null) {
return models.getResourceType(dc.getDocument(), resourceName);
}
return null;
}
private String getResourceTypeTag(ConcourseModel models, DynamicSchemaContext dc) {
return getParentPropertyValue("type", models, dc);
}
private String getParentPropertyValue(String propName, ConcourseModel models, DynamicSchemaContext dc) {
return NodeUtil.asScalar(models.getParentPropertyNode(propName, dc));
}
private String getSiblingPropertyValue(DynamicSchemaContext dc, String propName) {
YamlPath path = dc.getPath();
if (path!=null) {
YamlFileAST root = models.getSafeAst(dc.getDocument());
if (root!=null) {
return NodeUtil.asScalar(path.append(YamlPathSegment.valueAt(propName)).traverseToNode(root));
}
}
return null;
}
private YTypedPropertyImpl prop(AbstractType beanType, String name, YType type) {
YTypedPropertyImpl prop = f.yprop(name, type);
prop.setDescriptionProvider(descriptionFor(beanType, name));
return prop;
}
private YTypedPropertyImpl addProp(AbstractType superType, AbstractType bean, String name, YType type) {
YTypedPropertyImpl p = prop(superType, name, type);
bean.addProperty(p);
return p;
}
private YTypedPropertyImpl addProp(AbstractType bean, String name, YType type) {
return addProp(bean, bean, name, type);
}
public static Renderable descriptionFor(YType owner, String propName) {
String typeName = owner.toString();
return Renderables.fromClasspath(PipelineYmlSchema.class, "/desc/"+typeName+"/"+propName);
}
@Override
public AbstractType getTopLevelType() {
return TOPLEVEL_TYPE;
}
@Override
public YTypeUtil getTypeUtil() {
return TYPE_UTIL;
}
public YamlSchema getTaskSchema() {
return new YamlSchema() {
@Override
public YTypeUtil getTypeUtil() {
return TYPE_UTIL;
}
@Override
public YType getTopLevelType() {
return task;
}
@Override
public String toString() {
return "TaskYamlSchema";
}
};
}
public List<YType> getDefinitionTypes() {
return definitionTypes;
}
}

View File

@@ -1,20 +0,0 @@
/*******************************************************************************
* 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.concourse;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.*;
public class PipelineYmlSchemaProblems {
protected static final ProblemType UNUSED_RESOURCE = problemType("PipelineYamlUnusedResource", ProblemSeverity.ERROR);
}

View File

@@ -1,93 +0,0 @@
/*******************************************************************************
* 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.concourse;
import java.util.HashMap;
import java.util.Map;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
/**
* Keeps track of known resource types.
*
* @author Kris De Volder
*/
public class ResourceTypeRegistry {
private static class ResourceTypeInfo {
private final AbstractType source;
private final AbstractType in;
private final AbstractType out;
public ResourceTypeInfo(AbstractType source, AbstractType in, AbstractType out) {
super();
this.source = source;
this.in = in;
this.out = out;
}
public AbstractType getSource() {
return source;
}
public AbstractType getIn() {
return in;
}
public AbstractType getOut() {
return out;
}
}
private Map<String, ResourceTypeInfo> resourceTypes = new HashMap<>();
public ResourceTypeRegistry() {
}
public void def(String resourceTypeName, AbstractType source, AbstractType in, AbstractType out) {
Assert.isLegal(!resourceTypes.containsKey(resourceTypeName), "Multiple definitions for '"+resourceTypeName+"'");
resourceTypes.put(resourceTypeName, new ResourceTypeInfo(source, in, out));
}
public YType getSourceType(String typeTag) {
if (typeTag!=null) {
ResourceTypeInfo v = resourceTypes.get(typeTag);
if (v!=null) {
return v.getSource();
}
}
return null;
}
public YType getInParamsType(String typeTag) {
if (typeTag!=null) {
ResourceTypeInfo v = resourceTypes.get(typeTag);
if (v!=null) {
return v.getIn();
}
}
return null;
}
public YType getOutParamsType(String typeTag) {
if (typeTag!=null) {
ResourceTypeInfo v = resourceTypes.get(typeTag);
if (v!=null) {
return v.getOut();
}
}
return null;
}
}

View File

@@ -1,135 +0,0 @@
/*******************************************************************************
* 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.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
/**
* A simple cache implementation that provides an option for lookups to fallback
* to a 'stale' cache entry when computing a current one fails.
*/
public class StaleFallbackCache<K, V>{
private static class Versioned<T> {
int version;
T it;
public Versioned(int version, T it) {
super();
this.version = version;
this.it = it;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((it == null) ? 0 : it.hashCode());
result = prime * result + version;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Versioned other = (Versioned) obj;
if (it == null) {
if (other.it != null)
return false;
} else if (!it.equals(other.it))
return false;
if (version != other.version)
return false;
return true;
}
@Override
public String toString() {
return "Versioned [version=" + version + ", it=" + it + "]";
}
}
Map<K, V> staleEntries = new HashMap<>();
Cache<K, Versioned<CompletableFuture<V>>> latestEntries = CacheBuilder.newBuilder().build();
public synchronized V get(K key, int version, boolean allowStaleEntries, Callable<? extends V> valueLoader) throws Exception {
Versioned<CompletableFuture<V>> latest = latestEntries.get(key, () -> new Versioned<>(version, load(valueLoader)));
if (latest.version!=version) {
latestEntries.invalidate(key);
keepStaleBackup(key, latest);
latest = latestEntries.get(key, () -> new Versioned<>(version, load(valueLoader)));
}
if (!allowStaleEntries) {
return future_get(version, latest);
} else {
if (latest.it.isCompletedExceptionally()) {
V staleValue = staleEntries.get(key);
if (staleValue!=null) {
return staleValue;
}
}
return future_get(latest.it);
}
}
/**
* Called when a stale entry is found in the 'latest' map. This method is
* responsible for determining if the entry should be kept as a staleBackup,
* and store it.
*/
private void keepStaleBackup(K key, Versioned<CompletableFuture<V>> latest) {
try {
staleEntries.put(key, latest.it.get());
} catch (InterruptedException | ExecutionException e) {
//ignore: This means its a 'bad' entry and so we don't want to keep it
// as a 'stale backup'.
}
}
private V future_get(int wantedVersion, Versioned<CompletableFuture<V>> versioned) throws Exception {
Assert.isLegal(wantedVersion==versioned.version);
return future_get(versioned.it);
}
private V future_get(CompletableFuture<V> f) throws Exception {
try {
return f.get();
} catch (InterruptedException e) {
throw e;
} catch (ExecutionException e) {
throw ExceptionUtil.exception(e.getCause());
}
}
private CompletableFuture<V> load(Callable<? extends V> valueLoader) {
CompletableFuture<V> future = new CompletableFuture<V>();
try {
V value = valueLoader.call();
Assert.isNotNull(value);
future.complete(value);
} catch (Throwable e) {
future.completeExceptionally(e);
}
return future;
}
}

View File

@@ -1,19 +0,0 @@
<p>Performs the given steps in parallel.</p><p>If any sub-steps in an aggregate result in an error, the aggregate step as a
whole is considered to have errored.</p><p>Similarly, when aggregating <a href="task-step.html"><code>task</code></a> steps, if any
<em>fail</em>, the aggregate step will fail. This is useful for build matrixes:</p><div class="highlight"><pre class="verbatim"><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">plan</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">get</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">some-repo</span><span class="t">
</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">aggregate</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"> </span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">task</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">unit-windows</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">file</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">some-repo/ci/windows.yml</span><span class="t">
</span><span class="t"> </span><span class="nv">-</span><span class="sp"> </span><span class="py">task</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">unit-linux</span><span class="t">
</span><span class="t"> </span><span class="py">file</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">some-repo/ci/linux.yml</span><span class="t">
</span><span class="t"> </span><span class="nv">-</span><span class="sp"> </span><span class="py">task</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">unit-darwin</span><span class="t">
</span><span class="t"> </span><span class="py">file</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">some-repo/ci/darwin.yml</span></pre></div><p>The <code>aggregate</code> step is also useful for performing arbitrary steps in
parallel, for the sake of speeding up the build. It is often used to fetch
all dependent resources together:</p><div class="highlight"><pre class="verbatim"><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">plan</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">aggregate</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"> </span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">get</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">component-a</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">get</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">component-b</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">get</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">integration-suite</span><span class="t">
</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">task</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">integration</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">file</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">integration-suite/task.yml</span></pre></div>

View File

@@ -0,0 +1 @@
*Required*. The name of the deployment. A single Director can manage multiple deployments and distinguishes them by name.

View File

@@ -1 +0,0 @@
*Optional.* Arguments to pass to the command. Note that when executed with `fly`, any arguments passed to `fly` are appended to this array.

View File

@@ -1 +0,0 @@
*Optional.* A directory, relative to the initial working directory, to set as the working directory when running the script.

View File

@@ -1 +0,0 @@
*Required.* The command to execute, relative to the task's working directory. For a script living in a resource's repo, you must specify the full path to the resource, i.e. `my-resource/scripts/test`.

View File

@@ -1 +0,0 @@
*Optional.* Explicitly set the user to run as. If not specified, this defaults to the user configured by the task's image. If not specified there, it's up to the Garden backend, and may be e.g. `root` on Linux.

View File

@@ -1,14 +0,0 @@
Run steps in series.
do: [step]
Simply performs the given steps serially, with the same semantics as if they were at the top level step listing.
This can be used to perform multiple steps serially in the branch of an `aggregate` step:
plan:
- aggregate:
- task: unit
- do:
- get: something-else
- task: something-else-unit

View File

@@ -1 +0,0 @@
*Optional.* Place a `.tar` file of the image in the destination.

View File

@@ -1 +0,0 @@
*Optional.* Place a `docker save`d image in the destination.

View File

@@ -1,2 +0,0 @@
*Optional.* Skip `docker pull` of image. Artifacts based
on the image will not be present.

View File

@@ -1 +0,0 @@
*Optional.* The path of a directory containing a `Dockerfile` to build.

View File

@@ -1,10 +0,0 @@
*Optional.* A map of Docker build arguments.
Example:
```yaml
build_args:
do_thing: true
how_many_things: 2
email: me@yopmail.com
```

View File

@@ -1,8 +0,0 @@
Optional.* Path to a JSON file containing Docker build
arguments.
Example file contents:
```yaml
{ "email": "me@yopmail.com", "how_many_things": 1, "do_thing": false }
```

View File

@@ -1,10 +0,0 @@
*Optional.* Default `false`. When the `build` parameter is set,
first pull `image:tag` from the Docker registry (so as to use cached
intermediate images when building). This will cause the resource to fail
if it is set to `true` and the image does not exist yet.
Note: Since docker 1.10 docker images [do not contain all necessary metadata to
restore the build cache](https://github.com/docker/docker/issues/20316).
Additional metadata needs to be saved and re-applied after a docker pull to have
subsequent builds skip identical intermediate layers. This additional
metadata is stored as a very small separate image (`image:${cache_tag}-buildcache`)
in the repository of this resource.

View File

@@ -1,5 +0,0 @@
*Optional.* Default `tag`. The specific tag to pull before
building when `cache` parameter is set. Instead of pulling the same tag
that's going to be built, this allows picking a different tag like
`latest` or the previous version. This will cause the resource to fail
if it is set to a tag that does not exist yet.

View File

@@ -1,2 +0,0 @@
*Optional.* The path of the `Dockerfile` in the directory if
it's not at the root of the directory.

View File

@@ -1 +0,0 @@
*Optional.* A path to a file to `docker import` and then push.

View File

@@ -1,2 +0,0 @@
*Optional.* The path of a directory containing an image that was
fetched using this same resource with `save: true`.

View File

@@ -1,3 +0,0 @@
*Optional.* A path to a directory containing an image to `docker load`
before running `docker build`. The directory must have `image`,
`image-id`, `repository`, and `tag` present, i.e. the tree produced by `/in`.

View File

@@ -1,2 +0,0 @@
*Optional.* A path to a file to `docker load` and then push.
Requires `load_repository`.

View File

@@ -1 +0,0 @@
*Optional.* The repository of the image loaded from `load_file`.

View File

@@ -1 +0,0 @@
*Optional.* Default `latest`. The tag of image loaded from `load_file`.

View File

@@ -1,2 +0,0 @@
*Optional.* **DEPRECATED. Use `get` and `load` instead.** A
path to a repository to pull down, and then push to this resource.

View File

@@ -1,2 +0,0 @@
*Optional.* **DEPRECATED. Use `get` and `load` instead.** Default
`latest`. The tag of the repository to pull down via `pull_repository`.

View File

@@ -1,2 +0,0 @@
*Optional.* The value should be a path to a file containing the name
of the tag.

View File

@@ -1,2 +0,0 @@
*Optional.* Default `false`. If true, the pushed image will
be tagged as `latest` in addition to whatever other tag was specified.

View File

@@ -1,3 +0,0 @@
*Optional.* If specified, the tag read from the file will be
prepended with this string. This is useful for adding `v` in front of version
numbers.

View File

@@ -1,2 +0,0 @@
*Optional.* AWS access key to use for acquiring ECR
credentials.

View File

@@ -1,2 +0,0 @@
*Optional.* AWS secret key to use for acquiring ECR
credentials.

View File

@@ -1,25 +0,0 @@
*Optional.* An array of objects with the following format:
```yaml
ca_certs:
- domain: example.com:443
cert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
- domain: 10.244.6.2:443
cert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
```
Each entry specifies the x509 CA certificate for the trusted docker registry
residing at the specified domain. This is used to validate the certificate of
the docker registry when the registry's certificate is signed by a custom
authority (or itself).
The domain should match the first component of `repository`, including the
port. If the registry specified in `repository` does not use a custom cert,
adding `ca_certs` will break the check script. This option is overridden by
entries in `insecure_registries` with the same address or a matching CIDR.

View File

@@ -1,27 +0,0 @@
*Optional.* An array of objects with the following format:
```yaml
client_certs:
- domain: example.com:443
cert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
key: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
- domain: 10.244.6.2:443
cert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
key: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
```
Each entry specifies the x509 certificate and key to use for authenticating
against the docker registry residing at the specified domain. The domain
should match the first component of `repository`, including the port.

View File

@@ -1,3 +0,0 @@
*Optional.* An array of CIDRs or `host:port` addresses
to whitelist for insecure access (either `http` or unverified `https`).
This option overrides any entries in `ca_certs` with the same address.

View File

@@ -1 +0,0 @@
*Optional.* The password to use when authenticating.

View File

@@ -1 +0,0 @@
*Optional.* A URL pointing to a docker registry mirror service.

View File

@@ -1,2 +0,0 @@
*Required.* The name of the repository, e.g.
`concourse/docker-image-resource`.

View File

@@ -1 +0,0 @@
*Optional.* The tag to track. Defaults to `latest`.

View File

@@ -1 +0,0 @@
*Optional.* The username to authenticate with when pushing.

View File

@@ -1,21 +0,0 @@
Fetches a resource, making it available to subsequent steps via the given name.
For example, the following plan fetches a version number via the `semver` resource, bumps it to the next release candidate, and `put`s it back.
```
plan:
- get: version
params:
bump: minor
rc: true
- put: version
params:
version: version/number
```
```
get: string
```
*Required.* The logical name of the resource being fetched. This name satisfies logical inputs to a [Task](https://concourse.ci/concepts.html#tasks), and may be referenced within the plan itself (e.g. in the `file` attribute of a `task` step).

View File

@@ -1,3 +0,0 @@
<p><em>Optional.</em> A map of arbitrary configuration to forward to the
resource. Refer to the resource type's documentation to see what it
supports.</p>

View File

@@ -1,15 +0,0 @@
<p><em>Optional.</em> When specified, only the versions of the resource that
made it through the given list of jobs will be considered when triggering
and fetching.</p><p>Note that if multiple <code>get</code>s are configured with <code>passed</code>
constraints, all of the mentioned jobs are correlated. That is, with the
following set of inputs:</p><div class="highlight"><pre class="verbatim"><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">plan</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">get</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">a</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">passed</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="pi">[</span><span class="nv"></span><span class="nv">a-unit</span><span class="t"></span><span class="pi">,</span><span class="t"> </span><span class="nv"></span><span class="nv">integration</span><span class="t"></span><span class="pi">]</span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">get</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">b</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">passed</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="pi">[</span><span class="nv"></span><span class="nv">b-unit</span><span class="t"></span><span class="pi">,</span><span class="t"> </span><span class="nv"></span><span class="nv">integration</span><span class="t"></span><span class="pi">]</span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">get</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">x</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">passed</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="pi">[</span><span class="nv"></span><span class="nv">integration</span><span class="t"></span><span class="pi">]</span></pre></div><p>This means "give me the versions of <code>a</code>, <code>b</code>, and <code>x</code> that
have passed the <em>same build</em> of <code>integration</code>, with the same
version of <code>a</code> passing <code>a-unit</code> and the same version of
<code>b</code> passing <code>b-unit</code>."</p><p>This is crucial to being able to implement safe "fan-in" semantics as
things progress through a pipeline.</p>

View File

@@ -1,2 +0,0 @@
<p><em>Optional. Defaults to <code>name</code>.</em> The resource to fetch, as
configured in <a href="configuring-resources.html"><code>resources</code></a>.</p>

View File

@@ -1,4 +0,0 @@
<p><em>Optional. Default <code>false</code>.</em> Set to <code>true</code> to auto-trigger
new builds of the plan's job whenever this step has new versions available,
as specified by the <code>resource</code> and any <code>passed</code> constraints.</p><p>Otherwise, if no <code>get</code> steps set this to <code>true</code>, the job can only
be manually triggered.</p>

View File

@@ -1,11 +0,0 @@
<p><em>Optional. Defaults to <code>latest</code>.</em> The version of the resource to
fetch.</p><p>If set to <code>latest</code>, scheduling will just find the latest available
version of a resource and use it, allowing versions to be skipped. This is
usually what you want, e.g. if someone pushes 100 git commits.</p><p>If set to <code>every</code>, builds will walk through all available versions of
the resource. Note that if <code>passed</code> is also configured, it will only
step through the versions satisfying the constraints.</p><p>If set to a specific version (e.g. <code>{ref: abcdef123}</code>), only that
version will be used. Note that the version must be available and detected by
the resource, otherwise the input will never be satisfied. You may want to
use <a href="fly-check-resource.html"><code>check-resource</code></a> to force detection of resource versions,
if you need to use an older one that was never detected (as all newly
configured resources start from the latest version).</p>

View File

@@ -1,3 +0,0 @@
*Optional.* If a positive integer is given, *shallow* clone the
repository using the `--depth` option. Using this flag voids your warranty.
Some things will stop working unless we have the entire history.

View File

@@ -1 +0,0 @@
*Optional.* If `true`, will not fetch Git LFS files.

View File

@@ -1,3 +0,0 @@
*Optional.* If `none`, submodules will not be fetched. If specified as
a list of paths, only the given paths will be fetched. If not specified,
or if `all` is explicitly specified, all submodules are fetched.

View File

@@ -1,5 +0,0 @@
*Optional.* If specified the tag will be an
[annotated](https://git-scm.com/book/en/v2/Git-Basics-Tagging#Annotated-Tags)
tag rather than a
[lightweight](https://git-scm.com/book/en/v2/Git-Basics-Tagging#Lightweight-Tags)
tag. The value should be a path to a file containing the annotation message.

View File

@@ -1,2 +0,0 @@
*Optional.* When set to 'true' this will force the branch to be
pushed regardless of the upstream state.

View File

@@ -1 +0,0 @@
*Optional.* When set to 'true' push only the tags of a repo.

View File

@@ -1,2 +0,0 @@
*Optional.* If pushing fails with non-fast-forward, continuously
attempt rebasing and pushing.

View File

@@ -1 +0,0 @@
*Required.* The path of the repository to push to the source.

View File

@@ -1,2 +0,0 @@
*Optional.* If this is set then HEAD will be tagged. The value should be
a path to a file containing the name of the tag.

View File

@@ -1,3 +0,0 @@
*Optional.* If specified, the tag read from the file will be
prepended with this string. This is useful for adding `v` in front of
version numbers.

View File

@@ -1 +0,0 @@
*Required.* The branch the file lives on.

View File

@@ -1 +0,0 @@
*Required.* The name of the file in the repository.

View File

@@ -1,2 +0,0 @@
*Optional.* The git identity to use when pushing to the
repository support RFC 5322 address of the form "Gogh Fir \<gf@example.com\>" or "foo@example.com".

View File

@@ -1 +0,0 @@
*Optional.* Password for HTTP(S) auth when pulling/pushing.

View File

@@ -1 +0,0 @@
*Optional.* The SSH private key to use when pulling from/pushing to to the repository.

View File

@@ -1 +0,0 @@
*Required.* The repository URL.

View File

@@ -1,3 +0,0 @@
*Optional.* Username for HTTP(S) auth when pulling/pushing.
This is needed when only HTTP/HTTPS protocol for git is available (which does not support private key auth)
and auth is required.

View File

@@ -1,3 +0,0 @@
The branch to track. This is *optional* if the resource is
only used in `get` steps (default value in this case is `master`).
However, it is *required* when used in a `put` step.

View File

@@ -1,9 +0,0 @@
*Optional.* Array of GPG public key ids that
the resource will check against to verify the commit (details below). The
corresponding keys will be fetched from the key server specified in
`gpg_keyserver`. The ids can be short id, long id or fingerprint.
If `commit_verification_keys` or `commit_verification_key_ids` is specified in
the source configuration, it will additionally verify that the resulting commit
has been GPG signed by one of the specified keys. It will error if this is not
the case.

View File

@@ -1,7 +0,0 @@
*Optional.* Array of GPG public keys that the
resource will check against to verify the commit.
If `commit_verification_keys` or `commit_verification_key_ids` is specified in
the source configuration, it will additionally verify that the resulting commit
has been GPG signed by one of the specified keys. It will error if this is not
the case.

View File

@@ -1,2 +0,0 @@
*Optional.* Allows for commits that have been labeled with `[ci skip]` or `[skip ci]`
previously to be discovered by the resource.

View File

@@ -1,7 +0,0 @@
*Optional.* If specified as (list of pairs `name` and `value`)
it will configure git global options, setting each name with each value.
This can be useful to set options like `credential.helper` or similar.
See the [`git-config(1)` manual page](https://www.kernel.org/pub/software/scm/git/docs/git-config.html)
for more information and documentation of existing git options.

View File

@@ -1,2 +0,0 @@
*Optional.* GPG keyserver to download the public keys from.
Defaults to `hkp:///keys.gnupg.net/`.

View File

@@ -1,10 +0,0 @@
*Optional.* A list of glob patterns. The inverse of `paths`; changes
to the specified files are ignored.
Note that if you want to push commits that change these files via a `put`,
the commit will still be "detected", as [`check` and `put` both introduce
versions](https://concourse.ci/pipeline-mechanics.html#collecting-versions).
To avoid this you should define a second resource that you use for commits
that change files that you don't want to feed back into your pipeline - think
of one as read-only (with `ignore_paths`) and one as write-only (which
shouldn't need it).

View File

@@ -1,3 +0,0 @@
*Optional.* Password for HTTP(S) auth when pulling/pushing.
Note: You can also use pipeline templating to hide this password in source control. (For more information: https://concourse.ci/fly-set-pipeline.html)

View File

@@ -1,2 +0,0 @@
*Optional.* If specified (as a list of glob patterns), only changes
to the specified files will yield new versions from `check`.

View File

@@ -1,12 +0,0 @@
*Optional.* Private key to use when pulling/pushing.
Example:
private_key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtCS10/f7W7lkQaSgD/mVeaSOvSF9ql4hf/zfMwfVGgHWjj+W
<Lots more text>
DWiJL+OFeg9kawcUL6hQ8JeXPhlImG6RTUffma9+iGQyyBMCGd1l
-----END RSA PRIVATE KEY-----
Note: You can also use pipeline templating to hide this private key in source control. (For more information: https://concourse.ci/fly-set-pipeline.html)

View File

@@ -1,2 +0,0 @@
*Optional.* Skips git ssl verification by exporting
`GIT_SSL_NO_VERIFY=true`.

View File

@@ -1,4 +0,0 @@
`tag_filter`: *Optional.* If specified, the resource will only detect commits
that have a tag matching the specified expression. Patterns are
[glob(7)](http://man7.org/linux/man-pages/man7/glob.7.html) compatible (as
in, bash compatible).

View File

@@ -1 +0,0 @@
*Required.* The location of the repository.

View File

@@ -1,3 +0,0 @@
*Optional.* Username for HTTP(S) auth when pulling/pushing.
This is needed when only HTTP/HTTPS protocol for git is available (which does not support private key auth)
and auth is required.

View File

@@ -1 +0,0 @@
*Optional.* A list of jobs that should appear in this group. A job may appear in multiple groups. Neighbours of jobs in the current group will also appear on the same page in order to give context of the location of the group in the pipeline.

View File

@@ -1 +0,0 @@
*Required.* The name of the group. This should be short and simple as it will be used as the tab name for navigation.

View File

@@ -1 +0,0 @@
*Optional.* A list of resources that should appear in this group. Resources that are inputs or outputs of jobs in the group are automatically added; they do not have to be explicitly listed here.

View File

@@ -1,10 +0,0 @@
<p><em>Optional.</em> If configured, only the last specified number of builds
will have their build logs persisted. This is useful if you have a job that
runs periodically but after some amount of time the logs aren't worth keeping
around.</p><p>Example:</p><div class="highlight"><pre class="verbatim"><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">jobs</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">name</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">smoke-tests</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">build_logs_to_retain</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="mi">100</span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="nv"></span><span class="py">plan</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"></span><span class="t"></span><span class="t">
</span><span class="t"> </span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">get</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="mi">10</span><span class="nv"></span><span class="nv">m</span><span class="t">
</span><span class="t"> </span><span class="t"></span><span class="t"></span><span class="pi">-</span><span class="t"></span><span class="t"> </span><span class="t"></span><span class="nv"></span><span class="py">task</span><span class="t"></span><span class="pi">:</span><span class="t"></span><span class="t"> </span><span class="nv"></span><span class="nv">smoke-tests</span><span class="t">
</span><span class="t"> </span><span class="nv">#</span><span class="sp"> </span><span class="nv">...</span></pre></div>

View File

@@ -1,3 +0,0 @@
<p><em>Optional. Default <code>false</code>.</em> If set to <code>true</code>, manual
triggering of the job (via the web UI or <a href="fly-trigger-job.html"><code>trigger-job</code></a>) will be
disabled.</p>

View File

@@ -1 +0,0 @@
*Optional*. Step to execute regardless of whether the job succeeds, fails, or errors. Equivalent to the `ensure` step attribute.

View File

@@ -1 +0,0 @@
*Optional*. *Default* false. Normally, when a worker is shutting down it will wait for builds with containers running on that worker to finish before exiting. If this value is set to `true`, the worker will not wait on the builds of this job. You may want this if e.g. you have a self-deploying Concourse or long-running-but-low-importance jobs.

View File

@@ -1,3 +0,0 @@
<p><em>Optional.</em> If set, specifies a maximum number of builds to run at a
time. If <code>serial</code> or <code>serial_groups</code> are set, they take precedence
and force this value to be <code>1</code>.</p>

View File

@@ -1,2 +0,0 @@
<p><em>Required.</em> The name of the job. This should be short; it will show up
in URLs.</p>

View File

@@ -1 +0,0 @@
*Optional*. Step to execute when the job fails. Equivalent to the `on_failure` step attribute.

View File

@@ -1 +0,0 @@
*Optional*. Step to execute when the job succeeds. Equivalent to the `on_success` step attribute.

Some files were not shown because too many files have changed in this diff Show More