diff --git a/.gitignore b/.gitignore index 5bddf45e7..23729308f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*\~ *.class **/classpath.txt **/.idea diff --git a/headless-services/manifest-yaml-language-server/build.sh b/headless-services/manifest-yaml-language-server/build.sh new file mode 100755 index 000000000..4ad0dde33 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +# Use maven to build fat jar of the language server +../mvnw -U -f ../pom.xml -pl manifest-yaml-language-server -am clean install + diff --git a/vscode-extensions/vscode-manifest-yaml/pom.xml b/headless-services/manifest-yaml-language-server/pom.xml similarity index 95% rename from vscode-extensions/vscode-manifest-yaml/pom.xml rename to headless-services/manifest-yaml-language-server/pom.xml index fef0dca84..6b8473241 100644 --- a/vscode-extensions/vscode-manifest-yaml/pom.xml +++ b/headless-services/manifest-yaml-language-server/pom.xml @@ -2,14 +2,14 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - vscode-manifest-yaml + manifest-yaml-language-server jar org.springframework.ide.vscode commons-parent 0.0.1-SNAPSHOT - ../../headless-services/commons/pom.xml + ../commons/pom.xml 0.0.3-SNAPSHOT diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java new file mode 100644 index 000000000..82f3a9a64 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ConnectionException; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException; +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; +import org.springframework.ide.vscode.commons.util.ValueParseException; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; + +import com.google.common.collect.ImmutableList; + +public abstract class AbstractCFHintsProvider implements Callable> { + + public static final String EMPTY_VALUE = ""; + protected final CFTargetCache targetCache; + + private static final Logger logger = Logger.getLogger(AbstractCFHintsProvider.class.getName()); + + public AbstractCFHintsProvider(CFTargetCache targetCache) { + Assert.isNotNull(targetCache); + this.targetCache = targetCache; + } + + /** + * Used in error messages. For example "Failed to get ${type-name}s from Cloudfoundry". + * @return + */ + protected abstract String getTypeName(); + + @Override + public Collection call() throws Exception { + + try { + List targets = targetCache.getOrCreate(); + + // Do NOT wrap the results in another list. Allow null values to return + // as the reconcile framework expects null if hints failed to be resolved + return getHints(targets); + } catch (Throwable e) { + // Convert any error into something readable to the user as it may + // appear in the content assist + // UI. Do NOT wrap the original exception as the framework may look + // for the deepest cause when + // resolving the error message. Instead, log the full error, and + // only throw a + // new exception with a "nicer" message + Throwable errorNoAppending = getErrorNoAppending(e); + if (errorNoAppending != null) { + // Do not log the no-targets exception as it may be encountered + // frequently + // if a user does not have a CF client installed + throw new ValueParseException(ExceptionUtil.getMessageNoAppendedInformation(errorNoAppending)); + } else { + // Log any other error + logger.log(Level.SEVERE, ExceptionUtil.getMessage(e), e); + throw new ValueParseException( + "Failed to get "+getTypeName()+"s from Cloud Foundry: "+ExceptionUtil.getMessage(e)); + } + } + } + + /** + * + * @param e + * @return an error that requires no additional information when showing its + * message, or null if no such error is found + */ + protected Throwable getErrorNoAppending(Throwable e) { + return ExceptionUtil.findThrowable(e, + ImmutableList.of(NoTargetsException.class, ConnectionException.class)); + } + + /** + * + * @return non-null list of hints. Return empty if no hints available + */ + abstract protected Collection getHints(List targets) throws Exception; + +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/CFServicesValueParser.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/CFServicesValueParser.java new file mode 100644 index 000000000..e3fe8e29a --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/CFServicesValueParser.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException; +import org.springframework.ide.vscode.commons.util.EnumValueParser; +import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; + +public class CFServicesValueParser extends EnumValueParser { + + public CFServicesValueParser(String typeName, Callable> values) { + super(typeName, values); + } + + @Override + protected String createErrorMessage(String parseString, Collection values) { + return "There is no service instance called '" + parseString + "'. Available service instances are: " + values; + } + + @Override + protected String createBlankTextErrorMessage() { + return "At least one service instance name must be specified"; + } + + protected Exception errorOnParse(String message) { + // Parse errors should be indicated differently than regular schema + // problems (e.g. unknown service may be a warning) + return new ReconcileException(message, ManifestYamlSchemaProblemsTypes.UNKNOWN_SERVICES_PROBLEM); + } + + protected Exception errorOnBlank(String message) { + // Blank errors should be regular schema problems + return new ReconcileException(message, YamlSchemaProblems.SCHEMA_PROBLEM); + } +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java new file mode 100644 index 000000000..4c1ae9c84 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.io.IOException; + +import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; + +public class Main { + SimpleLanguageServer server = new ManifestYamlLanguageServer(); + + public static void main(String[] args) throws IOException, InterruptedException { + LaunguageServerApp.start(ManifestYamlLanguageServer::new); + } +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFBuildpacksProvider.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFBuildpacksProvider.java new file mode 100644 index 000000000..77f50cda4 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFBuildpacksProvider.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; +import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; + +public class ManifestYamlCFBuildpacksProvider extends AbstractCFHintsProvider { + + public ManifestYamlCFBuildpacksProvider(CFTargetCache cache) { + super(cache); + } + + @Override + public Collection getHints(List targets) throws Exception { + + List hints = new ArrayList<>(); + + for (CFTarget cfTarget : targets) { + + List buildpacks = cfTarget.getBuildpacks(); + if (buildpacks != null && !buildpacks.isEmpty()) { + + for (CFBuildpack buildpack : buildpacks) { + String name = buildpack.getName(); + String label = getBuildpackLabel(cfTarget, buildpack); + YValueHint hint = new BasicYValueHint(name, label); + if (!hints.contains(hint)) { + hints.add(hint); + } + } + return hints; + } + } + // Contract for the reconciler: return null if values cannot be + // resolved. Otherwise + // return non-empty list of buildpacks. For CF targets, a non-empty list + // of buildpacks is + // typically expected. + return !hints.isEmpty() ? hints : null; + } + + protected String getBuildpackLabel(CFTarget target, CFBuildpack buildpack) { + return buildpack.getName() + " (" + target.getName() + ")"; + } + + @Override + protected String getTypeName() { + return "Buildpack"; + } +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFDomainsProvider.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFDomainsProvider.java new file mode 100644 index 000000000..4617dbcf9 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFDomainsProvider.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; +import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; + +public class ManifestYamlCFDomainsProvider extends AbstractCFHintsProvider { + + public ManifestYamlCFDomainsProvider(CFTargetCache cache) { + super(cache); + } + + @Override + public Collection getHints(List targets) throws Exception { + + List hints = new ArrayList<>(); + + for (CFTarget cfTarget : targets) { + + List domains = cfTarget.getDomains(); + if (domains != null && !domains.isEmpty()) { + + for (CFDomain domain : domains) { + String name = domain.getName(); + String label = getLabel(cfTarget, domain); + YValueHint hint = new BasicYValueHint(name, label); + if (!hints.contains(hint)) { + hints.add(hint); + } + } + return hints; + } + } + // Contract for the reconciler: return null if values cannot be + // resolved. Otherwise + // return non-empty list + return !hints.isEmpty() ? hints : null; + } + + protected String getLabel(CFTarget target, CFDomain domain) { + return domain.getName(); + } + + @Override + protected String getTypeName() { + return "Domain"; + } +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFServicesProvider.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFServicesProvider.java new file mode 100644 index 000000000..6be9fc591 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFServicesProvider.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; +import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; + +public class ManifestYamlCFServicesProvider extends AbstractCFHintsProvider { + + public ManifestYamlCFServicesProvider(CFTargetCache cache) { + super(cache); + } + + @Override + public Collection getHints(List targets) throws Exception { + + // NOTE: empty list of services is a VALID result. A CF target may have + // no service instances + // created, so if empty list is returned from the client, then RETURN empty list. don't + // return null + // for empty services cases + List hints = new ArrayList<>(); + + for (CFTarget cfTarget : targets) { + List services = cfTarget.getServices(); + if (services != null && !services.isEmpty()) { + + for (CFServiceInstance service : services) { + String name = service.getName(); + String label = getServiceLabel(cfTarget, service); + YValueHint hint = new BasicYValueHint(name, label); + if (!hints.contains(hint)) { + hints.add(hint); + } + } + return hints; + } + } + + return hints; + } + + private String getServiceLabel(CFTarget cfClientTarget, CFServiceInstance service) { + return service.getName() + " - " + service.getPlan() + " (" + cfClientTarget.getParams().getOrgName() + " - " + + cfClientTarget.getParams().getSpaceName() + ")"; + } + + @Override + protected String getTypeName() { + return "Service"; + } +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java new file mode 100644 index 000000000..bce95e518 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import org.eclipse.lsp4j.CompletionOptions; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.TextDocumentSyncKind; +import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliParamsProvider; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2; +import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngine; +import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; +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.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.hover.YamlHoverInfoProvider; +import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine; +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.structure.YamlStructureProvider; +import org.yaml.snakeyaml.Yaml; + +public class ManifestYamlLanguageServer extends SimpleLanguageServer { + + + private Yaml yaml = new Yaml(); + private YamlSchema schema; + private CFTargetCache cfTargetCache; + private final CloudFoundryClientFactory cfClientFactory; + private final ClientParamsProvider cfParamsProvider; + + public ManifestYamlLanguageServer() { + this(DefaultCloudFoundryClientFactoryV2.INSTANCE, new CfCliParamsProvider()); + } + + public ManifestYamlLanguageServer(CloudFoundryClientFactory cfClientFactory, ClientParamsProvider cfParamsProvider) { + this.cfClientFactory = cfClientFactory; + this.cfParamsProvider=cfParamsProvider; + SimpleTextDocumentService documents = getTextDocumentService(); + + YamlASTProvider parser = new YamlParser(yaml); + + schema = new ManifestYmlSchema(getHintProviders()); + + YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT; + YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema); + YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider); + VscodeCompletionEngine completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine); + HoverInfoProvider infoProvider = new YamlHoverInfoProvider(parser, structureProvider, contextProvider); + VscodeHoverEngine hoverEngine = new VscodeHoverEngineAdapter(this, infoProvider); + IReconcileEngine engine = new YamlSchemaBasedReconcileEngine(parser, schema); + +// SimpleWorkspaceService workspace = getWorkspaceService(); + documents.onDidChangeContent(params -> { + TextDocument doc = params.getDocument(); + validateWith(doc, engine); + }); + +// 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(completionEngine::getCompletions); + documents.onCompletionResolve(completionEngine::resolveCompletion); + documents.onHover(hoverEngine ::getHover); + } + + protected ManifestYmlHintProviders getHintProviders() { + Callable> buildPacksProvider = getBuildpacksProvider(); + Callable> servicesProvider = getServicesProvider(); + Callable> domainsProvider = getDomainsProvider(); + + return new ManifestYmlHintProviders() { + + @Override + public Callable> getServicesProvider() { + return servicesProvider; + } + + @Override + public Callable> getDomainsProvider() { + return domainsProvider; + } + + @Override + public Callable> getBuildpackProviders() { + return buildPacksProvider; + } + }; + } + + private CFTargetCache getCfTargetCache() { + if (cfTargetCache == null) { + ClientParamsProvider paramsProvider = cfParamsProvider; + CloudFoundryClientFactory clientFactory = cfClientFactory; + cfTargetCache = new CFTargetCache(paramsProvider, clientFactory, new ClientTimeouts()); + } + return cfTargetCache; + } + + private Callable> getBuildpacksProvider() { + return new ManifestYamlCFBuildpacksProvider(getCfTargetCache()); + } + + private Callable> getServicesProvider() { + return new ManifestYamlCFServicesProvider(getCfTargetCache()); + } + + private Callable> getDomainsProvider() { + return new ManifestYamlCFDomainsProvider(getCfTargetCache()); + } + + @Override + protected ServerCapabilities getServerCapabilities() { + ServerCapabilities c = new ServerCapabilities(); + + c.setTextDocumentSync(TextDocumentSyncKind.Incremental); + c.setHoverProvider(true); + + CompletionOptions completionProvider = new CompletionOptions(); + completionProvider.setResolveProvider(false); + c.setCompletionProvider(completionProvider); + + return c; + } +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlSchemaProblemsTypes.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlSchemaProblemsTypes.java new file mode 100644 index 000000000..477c8ad40 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlSchemaProblemsTypes.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.problemType; + +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; + +/** + * + */ +public class ManifestYamlSchemaProblemsTypes { + + public static final ProblemType UNKNOWN_SERVICES_PROBLEM = problemType("UnknownServicesProblem", + ProblemSeverity.WARNING); + + public static final ProblemType UNKNOWN_DOMAIN_PROBLEM = problemType("UnknownDomainProblem", + ProblemSeverity.WARNING); + +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlHintProviders.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlHintProviders.java new file mode 100644 index 000000000..09558ba5b --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlHintProviders.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; + +public interface ManifestYmlHintProviders { + + Callable> getBuildpackProviders(); + + Callable> getServicesProvider(); + + Callable> getDomainsProvider(); + +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java new file mode 100644 index 000000000..d10e05f27 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.Callable; + +import org.springframework.ide.vscode.commons.util.IntegerRange; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.util.ValueParsers; +import org.springframework.ide.vscode.commons.yaml.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.YTypedPropertyImpl; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; + +import com.google.common.collect.ImmutableSet; + +/** + * @author Kris De Volder + */ +public class ManifestYmlSchema implements YamlSchema { + + private final AbstractType TOPLEVEL_TYPE; + private final YTypeUtil TYPE_UTIL; + private final Callable> buildpackProvider; + + private static final Set TOPLEVEL_EXCLUDED = ImmutableSet.of( + "name", "host", "hosts" + ); + + @Override + public IntegerRange expectedNumberOfDocuments() { + return IntegerRange.exactly(1); + } + + + public ManifestYmlSchema(ManifestYmlHintProviders providers) { + this.buildpackProvider = providers.getBuildpackProviders(); + Callable> servicesProvider = providers.getServicesProvider(); + Callable> domainsProvider = providers.getDomainsProvider(); + + + YTypeFactory f = new YTypeFactory(); + TYPE_UTIL = f.TYPE_UTIL; + + // define schema types + TOPLEVEL_TYPE = f.ybean("Cloudfoundry Manifest"); + + AbstractType application = f.ybean("Application"); + YAtomicType t_path = f.yatomic("Path"); + + YAtomicType t_buildpack = f.yatomic("Buildpack"); + if (this.buildpackProvider != null) { + t_buildpack.addHintProvider(this.buildpackProvider); +// t_buildpack.parseWith(ManifestYmlValueParsers.fromHints(t_buildpack.toString(), buildpackProvider)); + } + + YAtomicType t_domain = f.yatomic("Domain"); + YAtomicType t_domains_string = f.yatomic("Domains"); + + if (domainsProvider != null) { + t_domain.addHintProvider(domainsProvider); + t_domains_string.addHintProvider(domainsProvider); + } + + YType t_domains = f.yseq(t_domains_string); + + YAtomicType t_service_string = f.yatomic("Service"); + if (servicesProvider != null) { + t_service_string.addHintProvider(servicesProvider); + t_service_string.parseWith(new CFServicesValueParser(t_service_string.toString(), + YTypeFactory.valuesFromHintProvider(servicesProvider))); + } + YType t_services = f.yseq(t_service_string); + + YAtomicType t_boolean = f.yenum("boolean", "true", "false"); + YAtomicType t_ne_string = f.yatomic("String"); + t_ne_string.parseWith(ValueParsers.NE_STRING); + YType t_string = f.yatomic("String"); + YType t_strings = f.yseq(t_string); + + // "routes" has nested required property "route": + // routes: + // - route: someroute.io + + YBeanType route = f.ybean("Route"); + YAtomicType t_route_string = f.yatomic("route"); + route.addProperty(f.yprop("route", t_route_string).isRequired(true)); + t_route_string.parseWith(new RouteValueParser(YTypeFactory.valuesFromHintProvider(domainsProvider))); + + YAtomicType t_memory = f.yatomic("Memory"); + t_memory.addHints("256M", "512M", "1024M"); + t_memory.parseWith(ManifestYmlValueParsers.MEMORY); + + YAtomicType t_health_check_type = f.yenum("Health Check Type", "none", "port"); + + YAtomicType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer"); + t_strictly_pos_integer.parseWith(ManifestYmlValueParsers.integerAtLeast(1)); + + YAtomicType t_pos_integer = f.yatomic("Positive Integer"); + t_pos_integer.parseWith(ManifestYmlValueParsers.POS_INTEGER); + + YType t_env = f.ymap(t_string, t_string); + + // define schema structure... + TOPLEVEL_TYPE.addProperty(f.yprop("applications", f.yseq(application))); + TOPLEVEL_TYPE.addProperty("inherit", t_string, descriptionFor("inherit")); + +// YAtomicType t_test_hanging = f.yatomic("Hanging"); +// t_test_hanging.addHintProvider(() -> { +// try { +// Thread.sleep(60_000); +// } catch (InterruptedException e) { +// LaunguageServerApp.LOG.info("test_hanging hint provider interrupted!"); +// throw e; +// } +// return YTypeFactory.hints(ImmutableList.of("very", "slow", "hints")); +// }); + + YTypedPropertyImpl[] props = { +// f.yprop("test_hanging", t_test_hanging), + f.yprop("buildpack", t_buildpack), + f.yprop("command", t_string), + f.yprop("disk_quota", t_memory), + f.yprop("domain", t_domain), + f.yprop("domains", t_domains), + f.yprop("env", t_env), + f.yprop("host", t_string), + f.yprop("hosts", t_strings), + f.yprop("instances", t_strictly_pos_integer), + f.yprop("memory", t_memory), + f.yprop("name", t_ne_string).isRequired(true), + f.yprop("no-hostname", t_boolean), + f.yprop("no-route", t_boolean), + f.yprop("path", t_path), + f.yprop("random-route", t_boolean), + f.yprop("routes", f.yseq(route)), + f.yprop("services", t_services), + f.yprop("stack", t_string), + f.yprop("timeout", t_pos_integer), + f.yprop("health-check-type", t_health_check_type) + }; + + for (YTypedPropertyImpl prop : props) { + prop.setDescriptionProvider(descriptionFor(prop)); + if (!TOPLEVEL_EXCLUDED.contains(prop.getName())) { + TOPLEVEL_TYPE.addProperty(prop); + } + application.addProperty(prop); + } + } + + private Renderable descriptionFor(String propName) { + return Renderables.fromClasspath(this.getClass(), "/description-by-prop-name/"+propName); + } + + private Renderable descriptionFor(YTypedPropertyImpl prop) { + return descriptionFor(prop.getName()); + } + + @Override + public AbstractType getTopLevelType() { + return TOPLEVEL_TYPE; + } + + @Override + public YTypeUtil getTypeUtil() { + return TYPE_UTIL; + } +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java new file mode 100644 index 000000000..612f5aa70 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.Callable; + +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.EnumValueParser; +import org.springframework.ide.vscode.commons.util.ValueParser; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.common.collect.Sets; + +/** + * Methods and constants to create/get parsers for some atomic types + * used in manifest yml schema. + * + * @author Kris De Volder + */ +public class ManifestYmlValueParsers { + + public static final ValueParser POS_INTEGER = integerRange(0, null); + + public static final ValueParser MEMORY = new ValueParser() { + + private final ImmutableSet GIGABYTE = ImmutableSet.of("G", "GB"); + private final ImmutableSet MEGABYTE = ImmutableSet.of("M", "MB"); + private final Set UNITS = Sets.union(GIGABYTE, MEGABYTE); + + @Override + public Object parse(String str) throws Exception { + str = str.trim(); + String unit = getUnit(str.toUpperCase()); + if (unit==null) { + throw new NumberFormatException( + "'"+str+"' doesn't end with a valid unit of memory ('M', 'MB', 'G' or 'GB')" + ); + } + str = str.substring(0, str.length()-unit.length()); + int unitSize = GIGABYTE.contains(unit)?1024:1; + int value = Integer.parseInt(str); + if (value<0) { + throw new NumberFormatException("Negative value is not allowed"); + } + return value * unitSize; + } + + private String getUnit(String str) { + for (String u : UNITS) { + if (str.endsWith(u)) { + return u; + } + } + return null; + } + }; + + public static ValueParser integerAtLeast(final Integer lowerBound) { + return integerRange(lowerBound, null); + } + + public static ValueParser integerRange(final Integer lowerBound, final Integer upperBound) { + Assert.isLegal(lowerBound==null || upperBound==null || lowerBound <= upperBound); + return new ValueParser() { + @Override + public Object parse(String str) throws Exception { + int value = Integer.parseInt(str); + if (lowerBound!=null && valueupperBound) { + throw new NumberFormatException("Value must be at most "+upperBound); + } + return value; + } + }; + } + +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/RouteValueParser.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/RouteValueParser.java new file mode 100644 index 000000000..67e329516 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/RouteValueParser.java @@ -0,0 +1,84 @@ +package org.springframework.ide.vscode.manifest.yaml; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFRoute; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ConnectionException; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException; +import org.springframework.ide.vscode.commons.util.RegexpParser; +import org.springframework.ide.vscode.commons.util.ValueParseException; + +public class RouteValueParser extends RegexpParser { + + private static final String ROUTE_REGEX = "^([\\da-z\\.-]+)(:\\d{1,5})?((\\/[\\dA-Za-z\\.-]+)*\\/?)?$"; + private static final String ROUTE_TYPE_NAME = "Route"; + private static final String ROUTE_DESCRIPTION = "HTTP or TCP application root route"; + private static final int MAX_PORT_NUMBER = 65535; + + private Callable> domains; + + public RouteValueParser(Callable> domains) { + super(ROUTE_REGEX, ROUTE_TYPE_NAME, ROUTE_DESCRIPTION); + this.domains = domains; + } + + private Matcher staticValidation(String str) throws Exception { + return (Matcher) super.parse(str); + } + + private Object dynamicValidation(String str, Matcher matcher) throws Exception { + try { + Collection cloudDomains = Collections.emptyList(); + try { + cloudDomains = domains == null ? Collections.emptyList() : domains.call(); + } catch (ValueParseException e) { + /* + * If domains hint provider throws exception it is + * ValueParserException not NoTargetsException. This means no + * communication with CF -> abort dyncamic validation + */ + return matcher; + } + // Ensure cloud domains is empty list instead of null + if (cloudDomains == null) { + cloudDomains = Collections.emptyList(); + } + CFRoute route = CFRoute.builder().from(str, cloudDomains).build(); + if (route.getDomain() == null || route.getDomain().isEmpty()) { + throw new ValueParseException("Domain is missing."); + } + if ((route.getPath() != null && !route.getPath().isEmpty()) && (route.getPort() != CFRoute.NO_PORT)) { + throw new ValueParseException( + "Unable to determine type of route. HTTP port may have a path but no port. TCP route may have port but no path."); + } + if (route.getPort() > MAX_PORT_NUMBER) { + String portAndColumn = matcher.group(2); + int start = str.indexOf(portAndColumn) + 1; + int end = start + portAndColumn.length() - 1; + throw new ValueParseException("Invalid port number. Port range must be between 1 and " + MAX_PORT_NUMBER, start, end); + } + if (!cloudDomains.contains(route.getDomain())) { + String hostDomain = matcher.group(1); + throw new ReconcileException("Unknown domain", ManifestYamlSchemaProblemsTypes.UNKNOWN_DOMAIN_PROBLEM, hostDomain.lastIndexOf(route.getDomain()), hostDomain.length()); + } + return route; + } catch (ConnectionException | NoTargetsException e) { + // No connection to CF? Abort dynamic validation + return matcher; + } + } + + @Override + public Object parse(String str) throws Exception { + Matcher matcher = staticValidation(str); + if (matcher != null) { + return dynamicValidation(str, matcher); + } + return null; + } + +} diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/buildpack.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/buildpack.html new file mode 100644 index 000000000..2f3fb4cb2 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/buildpack.html @@ -0,0 +1,11 @@ +

If your application requires a custom buildpack, you can use the buildpack attribute to specify its URL or name:

+ +
+---
+  ...
+  buildpack: buildpack_URL
+
+ +

Note: The cf buildpacks command lists the buildpacks that you can refer to by name in a manifest or a command line option.

+ +

The command line option that overrides this attribute is -b.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/buildpack.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/buildpack.md new file mode 100644 index 000000000..afc713276 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/buildpack.md @@ -0,0 +1,11 @@ +If your application requires a custom buildpack, you can use the `buildpack` attribute to specify its URL or name: + +``` +--- + ... + buildpack: buildpack_URL +``` + +**Note**: The `cf buildpacks` command lists the buildpacks that you can refer to by name in a manifest or a command line option. + +The command line option that overrides this attribute is `-b`. diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/command.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/command.html new file mode 100644 index 000000000..aab1e4651 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/command.html @@ -0,0 +1,34 @@ +

Some languages and frameworks require that you provide a custom command to start an application. Refer to the buildpack documentation to determine if you need to provide a custom start command.

+ +

You can provide the custom start command in your application manifest or on the command line.

+ +

To specify the custom start command in your application manifest, add it in the command: START-COMMAND format as the following example shows:

+ +
+---
+  ...
+  command: bundle exec rake VERBOSE=true
+
+ +

On the command line, use the -c option to specify the custom start command as the following example shows:

+ +
+$ cf push my-app -c "bundle exec rake VERBOSE=true"
+
+ +

Note: The -c option with a value of ‘null’ forces cf push to use the buildpack start command. See About Starting Applications for more information.

+ +

If you override the start command for a Buildpack application, Linux uses +bash -c YOUR-COMMAND to invoke your application. +If you override the start command for a Docker application, Linux uses sh -c YOUR-COMMAND to invoke your application. +Because of this, if you override a start command, you should prefix exec to the final command in your custom composite start command.

+ +

exec causes the last command to become the root process of your application. The Cloud Foundry Updates and Your Application section of the Considerations for Designing and Running an Application in the Cloud topic explains why your application should handle a termination signal during Cloud Foundry updates. +Without an exec statement, the parent process remains as the implied bash process, and does not propagate signals to your application process.

+ +

For example, both of the following composite start commands run database migrations when the first instance of the app starts, then start the app to serve requests, but they behave differently on graceful shutdown.

+ +
    +
  • bin/rake cf:on_first_instance db:migrate && bin/rails server -p $PORT -e $RAILS_ENV: The process tree is bash -> ruby, so on graceful shutdown only the bash process receives the TERM signal, and not the ruby process.

  • +
  • bin/rake cf:on_first_instance db:migrate && exec bin/rails server -p $PORT -e $RAILS_ENV: Because of the exec prefix on the final command, the ruby process invoked by rails takes over the bash process managing the execution of the composite command. The process tree is only ruby, so the ruby web server receives the TERM signal can shutdown gracefully for 10 seconds.

  • +
\ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/command.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/command.md new file mode 100644 index 000000000..2d17d293a --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/command.md @@ -0,0 +1,30 @@ +Some languages and frameworks require that you provide a custom command to start an application. Refer to the [buildpack](/buildpacks/) documentation to determine if you need to provide a custom start command. + +You can provide the custom start command in your application manifest or on the command line. + +To specify the custom start command in your application manifest, add it in the `command: START-COMMAND` format as the following example shows: + + +``` +--- + ... + command: bundle exec rake VERBOSE=true +``` + +On the command line, use the `-c` option to specify the custom start command as the following example shows: + +``` +$ cf push my-app -c "bundle exec rake VERBOSE=true" +``` + +**Note**: The `-c` option with a value of ‘null’ forces `cf push` to use the buildpack start command. See [About Starting Applications](./app-startup.html) for more information. + +If you override the start command for a Buildpack application, Linux uses `bash -c YOUR-COMMAND` to invoke your application. If you override the start command for a Docker application, Linux uses `sh -c YOUR-COMMAND` to invoke your application. Because of this, if you override a start command, you should prefix `exec` to the final command in your custom composite start command. + +`exec` causes the last command to become the root process of your application. The [Cloud Foundry Updates and Your Application](./prepare-to-deploy.html#moving-apps) section of the _Considerations for Designing and Running an Application in the Cloud_ topic explains why your application should handle a `termination signal` during Cloud Foundry updates. Without an `exec` statement, the parent process remains as the implied bash process, and does not propagate signals to your application process. + +For example, both of the following composite start commands run database migrations when the first instance of the app starts, then start the app to serve requests, but they behave differently on graceful shutdown. + +* `bin/rake cf:on_first_instance db:migrate && bin/rails server -p $PORT -e $RAILS_ENV`: The process tree is `bash -> ruby`, so on graceful shutdown only the `bash` process receives the TERM signal, and not the `ruby` process. + +* `bin/rake cf:on_first_instance db:migrate && exec bin/rails server -p $PORT -e $RAILS_ENV`: Because of the `exec` prefix on the final command, the `ruby` process invoked by `rails` takes over the `bash` process managing the execution of the composite command. The process tree is only `ruby`, so the ruby web server receives the TERM signal can shutdown gracefully for 10 seconds. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/disk_quota.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/disk_quota.html new file mode 100644 index 000000000..f2463dbea --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/disk_quota.html @@ -0,0 +1,9 @@ +

Use the disk_quota attribute to allocate the disk space for your app instance. This attribute requires a unit of measurement: M, MB, G, or GB, in upper case or lower case.

+ +
+---
+  ...
+  disk_quota: 1024M
+
+ +

The command line option that overrides this attribute is -k.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/disk_quota.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/disk_quota.md new file mode 100644 index 000000000..dfd347ccd --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/disk_quota.md @@ -0,0 +1,9 @@ +Use the `disk_quota` attribute to allocate the disk space for your app instance. This attribute requires a unit of measurement: `M`, `MB`, `G`, or `GB`, in upper case or lower case. + +``` +--- + ... + disk_quota: 1024M +``` + +The command line option that overrides this attribute is `-k`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domain.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domain.html new file mode 100644 index 000000000..8f1af1c01 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domain.html @@ -0,0 +1,27 @@ +

Every cf push deploys applications to one particular Cloud Foundry instance. +Every Cloud Foundry instance may have a shared domain set by an admin. +Unless you specify a domain, Cloud Foundry incorporates that shared domain in the route to your application.

+ +

You can use the domain attribute when you want your application to be served from a domain other than the default shared domain.

+ +
+---
+  ...
+  domain: unique-example.com
+
+ +

The command line option that overrides this attribute is -d.

+ +

The domains attribute

+ +

Use the domains attribute to provide multiple domains. If you define both domain and domains attributes, Cloud Foundry creates routes for domains defined in both of these fields.

+ +
+---
+  ...
+  domains:
+  - domain-example1.com
+  - domain-example2.org
+
+ +

The command line option that overrides this attribute is -d.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domain.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domain.md new file mode 100644 index 000000000..e25a36620 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domain.md @@ -0,0 +1,25 @@ +Every `cf push` deploys applications to one particular Cloud Foundry instance. Every Cloud Foundry instance may have a shared domain set by an admin. Unless you specify a domain, Cloud Foundry incorporates that shared domain in the route to your application. + +You can use the `domain` attribute when you want your application to be served from a domain other than the default shared domain. + +``` +--- + ... + domain: unique-example.com +``` + +The command line option that overrides this attribute is `-d`. + +### The domains attribute + +Use the `domains` attribute to provide multiple domains. If you define both `domain` and `domains` attributes, Cloud Foundry creates routes for domains defined in both of these fields. + +``` +--- + ... + domains: + - domain-example1.com + - domain-example2.org +``` + +The command line option that overrides this attribute is `-d`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domains.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domains.html new file mode 100644 index 000000000..0df6ef33b --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domains.html @@ -0,0 +1,10 @@ +

Use the domains attribute to provide multiple domains. If you define both domain and domains attributes, Cloud Foundry creates routes for domains defined in both of these fields.

+ +
---
+  ...
+  domains:
+  - domain-example1.com
+  - domain-example2.org
+
+ +

The command line option that overrides this attribute is -d.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domains.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domains.md new file mode 100644 index 000000000..d593178e6 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/domains.md @@ -0,0 +1,11 @@ +Use the `domains` attribute to provide multiple domains. If you define both `domain` and `domains` attributes, Cloud Foundry creates routes for domains defined in both of these fields. + +``` +--- + ... + domains: + - domain-example1.com + - domain-example2.org +``` + +The command line option that overrides this attribute is `-d`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/env.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/env.html new file mode 100644 index 000000000..4c35fe3d7 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/env.html @@ -0,0 +1,28 @@ +

The env block consists of a heading, then one or more environment variable/value pairs.

+ +

For example:

+ +
+---
+  ...
+  env:
+    RAILS_ENV: production
+    RACK_ENV: production
+
+ +

cf push deploys the application to a container on the server. The variables belong to the container environment.

+ +

While the application is running, Cloud Foundry allows you to operate on environment variables.

+ +
    +
  • View all variables: cf env my-app
  • +
  • Set an individual variable: cf set-env my-app my-variable_name my-variable_value
  • +
  • Unset an individual variable: cf unset-env my-app my-variable_name my-variable_value
  • +
+ +

Environment variables interact with manifests in the following ways:

+ +
    +
  • When you deploy an application for the first time, Cloud Foundry reads the variables described in the environment block of the manifest, and adds them to the environment of the container where the application is deployed.

  • +
  • When you stop and then restart an application, its environment variables persist.

  • +
diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/env.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/env.md new file mode 100644 index 000000000..88bd86d01 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/env.md @@ -0,0 +1,24 @@ +The `env` block consists of a heading, then one or more environment variable/value pairs. + +For example: + +``` +--- + ... + env: + RAILS_ENV: production + RACK_ENV: production +``` + +`cf push` deploys the application to a container on the server. The variables belong to the container environment. + +While the application is running, Cloud Foundry allows you to operate on environment variables. + +* View all variables: `cf env my-app` +* Set an individual variable: `cf set-env my-app my-variable_name my-variable_value` +* Unset an individual variable: `cf unset-env my-app my-variable_name my-variable_value` + +Environment variables interact with manifests in the following ways: + +* When you deploy an application for the first time, Cloud Foundry reads the variables described in the environment block of the manifest, and adds them to the environment of the container where the application is deployed. +* When you stop and then restart an application, its environment variables persist. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/health-check-type.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/health-check-type.html new file mode 100644 index 000000000..30be9affb --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/health-check-type.html @@ -0,0 +1,9 @@ +

Use the health-check-type attribute to set the health_check_type +flag to either port or none. If you do not provide +a health-check-type attribute, it defaults to port.

+ +
+---
+  ...
+  health-check-type: none
+
\ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/health-check-type.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/health-check-type.md new file mode 100644 index 000000000..fac9be7d7 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/health-check-type.md @@ -0,0 +1,9 @@ +Use the `health-check-type` attribute to set the `health_check_type` +flag to either `port` or `none`. If you do not provide a `health-check-type` +attribute, it defaults to `port`. + +``` +--- + ... + health-check-type: none +``` diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/host.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/host.html new file mode 100644 index 000000000..04de4aa7b --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/host.html @@ -0,0 +1,9 @@ +

Use the host attribute to provide a hostname, or subdomain, in the form of a string. This segment of a route helps to ensure that the route is unique. If you do not provide a hostname, the URL for the app takes the form of APP-NAME.DOMAIN.

+ +
+---
+  ...
+  host: my-app
+
+ +

The command line option that overrides this attribute is -n.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/host.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/host.md new file mode 100644 index 000000000..4658d50bf --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/host.md @@ -0,0 +1,9 @@ +Use the `host` attribute to provide a hostname, or subdomain, in the form of a string. This segment of a route helps to ensure that the route is unique. If you do not provide a hostname, the URL for the app takes the form of `APP-NAME.DOMAIN`. + +``` +--- + ... + host: my-app +``` + +The command line option that overrides this attribute is `-n`. diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/hosts.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/hosts.html new file mode 100644 index 000000000..de25f32ef --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/hosts.html @@ -0,0 +1,11 @@ +

Use the hosts attribute to provide multiple hostnames, or subdomains. Each hostname generates a unique route for the app. hosts can be used in conjunction with host. If you define both attributes, Cloud Foundry creates routes for hostnames defined in both host and hosts.

+ +
+---
+  ...
+  hosts:
+  - app_host1
+  - app_host2
+
+ +

The command line option that overrides this attribute is -n.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/hosts.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/hosts.md new file mode 100644 index 000000000..9b69c9cf2 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/hosts.md @@ -0,0 +1,11 @@ +Use the `hosts` attribute to provide multiple hostnames, or subdomains. Each hostname generates a unique route for the app. `hosts` can be used in conjunction with `host`. If you define both attributes, Cloud Foundry creates routes for hostnames defined in both `host` and `hosts`. + +``` +--- + ... + hosts: + - app_host1 + - app_host2 +``` + +The command line option that overrides this attribute is `-n`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/inherit.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/inherit.html new file mode 100644 index 000000000..184350b67 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/inherit.html @@ -0,0 +1,62 @@ +

A single manifest can describe multiple applications. Another powerful technique is to create multiple manifests with inheritance. Here, manifests have parent-child relationships such that children inherit descriptions from a parent. Children can use inherited descriptions as-is, extend them, or override them.

+ +

Content in the child manifest overrides content in the parent manifest, if the two conflict.

+ +

This technique helps in these and other scenarios:

+ +
    +
  • An application has a set of different deployment modes, such as debug, local, and public. Each deployment mode is described in child manifests that extend the settings in a base parent manifest.

  • +
  • An application is packaged with a basic configuration described by a parent manifest. Users can extend the basic configuration by creating child manifests that add new properties or override those in the parent manifest.

  • +
+ +

The benefits of multiple manifests with inheritance are similar to those of minimizing duplicated content within single manifests. With inheritance, though, we “promote” content by placing it in the parent manifest.

+ +

Every child manifest must contain an “inherit” line that points to the parent manifest. Place the inherit line immediately after the three dashes at the top of the child manifest. For example, every child of a parent manifest called base-manifest.yml begins like this:

+ +
---
+  ...
+  inherit: base-manifest.yml
+
+ +

You do not need to add anything to the parent manifest.

+ +

In the simple example below, a parent manifest gives each application minimal resources, while a production child manifest scales them up.

+ +

simple-base-manifest.yml

+ +
---
+path: .
+domain: shared-domain.com
+memory: 256M
+instances: 1
+services:
+- singular-backend
+
+# app-specific configuration
+applications:
+ - name: springtock
+   host: 765shower
+   path: ./april/build/libs/april-weather.war
+ - name: wintertick
+   host: 321flurry
+   path: ./december/target/december-weather.war
+
+ +

simple-prod-manifest.yml

+ +
---
+inherit: simple-base-manifest.yml
+applications:
+ - name:springstorm
+   memory: 512M
+   instances: 1
+   host: 765deluge
+   path: ./april/build/libs/april-weather.war
+ - name: winterblast
+   memory: 1G
+   instances: 2
+   host: 321blizzard
+   path: ./december/target/december-weather.war
+
+ +

Note: Inheritance can add an additional level of complexity to manifest creation and maintenance. Comments that precisely explain how the child manifest extends or overrides the descriptions in the parent manifest can alleviate this complexity. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/inherit.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/inherit.md new file mode 100644 index 000000000..5270dee89 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/inherit.md @@ -0,0 +1,63 @@ +A single manifest can describe multiple applications. Another powerful technique is to create multiple manifests with inheritance. Here, manifests have parent-child relationships such that children inherit descriptions from a parent. Children can use inherited descriptions as-is, extend them, or override them. + +Content in the child manifest overrides content in the parent manifest, if the two conflict. + +This technique helps in these and other scenarios: + +* An application has a set of different deployment modes, such as debug, local, and public. Each deployment mode is described in child manifests that extend the settings in a base parent manifest. + +* An application is packaged with a basic configuration described by a parent manifest. Users can extend the basic configuration by creating child manifests that add new properties or override those in the parent manifest. + +The benefits of multiple manifests with inheritance are similar to those of minimizing duplicated content within single manifests. With inheritance, though, we “promote” content by placing it in the parent manifest. + +Every child manifest must contain an “inherit” line that points to the parent manifest. Place the inherit line immediately after the three dashes at the top of the child manifest. For example, every child of a parent manifest called `base-manifest.yml` begins like this: + +``` +--- + ... + inherit: base-manifest.yml +``` +You do not need to add anything to the parent manifest. + +In the simple example below, a parent manifest gives each application minimal resources, while a production child manifest scales them up. + +**simple-base-manifest.yml** + +``` +--- +path: . +domain: shared-domain.com +memory: 256M +instances: 1 +services: +- singular-backend + +# app-specific configuration +applications: + - name: springtock + host: 765shower + path: ./april/build/libs/april-weather.war + - name: wintertick + host: 321flurry + path: ./december/target/december-weather.war +``` + +**simple-prod-manifest.yml** + +``` +--- +inherit: simple-base-manifest.yml +applications: + - name:springstorm + memory: 512M + instances: 1 + host: 765deluge + path: ./april/build/libs/april-weather.war + - name: winterblast + memory: 1G + instances: 2 + host: 321blizzard + path: ./december/target/december-weather.war +``` + +**Note**: Inheritance can add an additional level of complexity to manifest creation and maintenance. Comments that precisely explain how the child manifest extends or overrides the descriptions in the parent manifest can alleviate this complexity. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/instances.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/instances.html new file mode 100644 index 000000000..021991fa5 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/instances.html @@ -0,0 +1,11 @@ +

Use the instances attribute to specify the number of app instances that you want to start upon push:

+ +
+---
+  ...
+  instances: 2
+
+ +

We recommend that you run at least two instances of any apps for which fault tolerance matters.

+ +

The command line option that overrides this attribute is -i.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/instances.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/instances.md new file mode 100644 index 000000000..27bd30d5d --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/instances.md @@ -0,0 +1,11 @@ +Use the `instances` attribute to specify the number of app instances that you want to start upon push: + +``` +--- + ... + instances: 2 +``` + +We recommend that you run at least two instances of any apps for which fault tolerance matters. + +The command line option that overrides this attribute is `-i`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/memory.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/memory.html new file mode 100644 index 000000000..e25ee286e --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/memory.html @@ -0,0 +1,11 @@ +

Use the memory attribute to specify the memory limit for all instances of an app. This attribute requires a unit of measurement: M, MB, G, or GB, in upper case or lower case. For example:

+ +
+---
+  ...
+  memory: 1024M
+
+ +

The default memory limit is 1G. You might want to specify a smaller limit to conserve quota space if you know that your app instances do not require 1G of memory.

+ +

The command line option that overrides this attribute is -m.

\ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/memory.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/memory.md new file mode 100644 index 000000000..3efb02e41 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/memory.md @@ -0,0 +1,11 @@ +Use the `memory` attribute to specify the memory limit for all instances of an app. This attribute requires a unit of measurement: `M`, `MB`, `G`, or `GB`, in upper case or lower case. For example: + +``` +--- + ... + memory: 1024M +``` + +The default memory limit is 1G. You might want to specify a smaller limit to conserve quota space if you know that your app instances do not require 1G of memory. + +The command line option that overrides this attribute is `-m`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/name.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/name.html new file mode 100644 index 000000000..39d0aac6a --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/name.html @@ -0,0 +1,10 @@ +

The name attribute is the only required attribute +for an application in a manifest file.

+ +

This is an example of a minimal manifest:

+ +
+---
+applications:
+- name: nifty-gui
+
\ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/name.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/name.md new file mode 100644 index 000000000..09419ceee --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/name.md @@ -0,0 +1,9 @@ +The `name` attribute is the only required attribute for an application in a manifest file. + +This is an example of a minimal manifest: + +``` +--- +applications: +- name: nifty-gui +``` \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-hostname.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-hostname.html new file mode 100644 index 000000000..b3a6047de --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-hostname.html @@ -0,0 +1,9 @@ +

By default, if you do not provide a hostname, the URL for the app takes the form of APP-NAME.DOMAIN. If you want to override this and map the root domain to this app then you can set no-hostname as true.

+ +
+---
+  ...
+  no-hostname: true
+
+ +

The command line option that corresponds to this attribute is --no-hostname.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-hostname.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-hostname.md new file mode 100644 index 000000000..19b73ed31 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-hostname.md @@ -0,0 +1,9 @@ +By default, if you do not provide a hostname, the URL for the app takes the form of `APP-NAME.DOMAIN`. If you want to override this and map the root domain to this app then you can set no-hostname as true. + +``` +--- + ... + no-hostname: true +``` + +The command line option that corresponds to this attribute is `--no-hostname`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-route.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-route.html new file mode 100644 index 000000000..d631024fd --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-route.html @@ -0,0 +1,18 @@ +

By default, cf push assigns a route to every application. But some applications process data while running in the background, and should not be assigned routes.

+ +

You can use the no-route attribute with a value of true to prevent a route from being created for your application.

+ +
+---
+  ...
+  no-route: true
+
+ +

The command line option that corresponds to this attribute is --no-route.

+ +

If you find that an application which should not have a route does have one:

+ +
    +
  1. Remove the route using the cf unmap-route command.
  2. +
  3. Push the app again with the no-route: true attribute in the manifest or the --no-route command line option.
  4. +
diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-route.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-route.md new file mode 100644 index 000000000..7cef4e28f --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/no-route.md @@ -0,0 +1,16 @@ +By default, `cf push` assigns a route to every application. But some applications process data while running in the background, and should not be assigned routes. + +You can use the `no-route` attribute with a value of `true` to prevent a route from being created for your application. + +``` +--- + ... + no-route: true +``` + +The command line option that corresponds to this attribute is `--no-route`. + +If you find that an application which should not have a route does have one: + +1. Remove the route using the `cf unmap-route` command. +2. Push the app again with the `no-route: true` attribute in the manifest or the `--no-route` command line option. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/path.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/path.html new file mode 100644 index 000000000..a1b1cfb23 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/path.html @@ -0,0 +1,9 @@ +

You can use the path attribute to tell Cloud Foundry where to find your application. This is generally not necessary when you run cf push from the directory where an application is located.

+ +
+---
+  ...
+  path: path_to_application_bits
+
+ +

The command line option that overrides this attribute is -p.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/path.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/path.md new file mode 100644 index 000000000..24d48cb6a --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/path.md @@ -0,0 +1,9 @@ +You can use the `path` attribute to tell Cloud Foundry where to find your application. This is generally not necessary when you run `cf push` from the directory where an application is located. + +``` +--- + ... + path: path_to_application_bits +``` + +The command line option that overrides this attribute is `-p`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/random-route.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/random-route.html new file mode 100644 index 000000000..ef432da78 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/random-route.html @@ -0,0 +1,11 @@ +

Use the random-route attribute to create a URL that includes the app name and +random words. +Use this attribute to avoid URL collision when pushing the same app to multiple spaces, or to avoid managing app URLs.

+ +

The command line option that corresponds to this attribute is --random-route.

+ +
+---
+  ...
+  random-route: true
+
diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/random-route.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/random-route.md new file mode 100644 index 000000000..9a7792b77 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/random-route.md @@ -0,0 +1,9 @@ +Use the `random-route` attribute to create a URL that includes the app name and random words. Use this attribute to avoid URL collision when pushing the same app to multiple spaces, or to avoid managing app URLs. + +The command line option that corresponds to this attribute is `--random-route`. + +``` +--- + ... + random-route: true +``` \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/routes.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/routes.html new file mode 100644 index 000000000..49b219ea2 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/routes.html @@ -0,0 +1,12 @@ +

Use the routes attribute to provide multiple HTTP and TCP routes. Each route for this app is created if it does not already exist.

+

This attribute is a combination of push options that include --hostname, -d, and --route-path.

+
+---
+  ...
+  routes:
+  - route: example.com
+  - route: www.example.com/foo
+  - route: tcp-example.com:1234
+
+ +

The routes attribute cannot be used in conjunction with the following attributes: host, hosts, domain, domains, and no-hostname. An error will result.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/routes.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/routes.md new file mode 100644 index 000000000..754921e46 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/routes.md @@ -0,0 +1,14 @@ +Use the `routes` attribute to provide multiple HTTP and TCP routes. Each route for this app is created if it does not already exist. + +This attribute is a combination of `push` options that include `--hostname`, `-d`, and `--route-path`. + +``` +--- + ... + routes: + - route: example.com + - route: www.example.com/foo + - route: tcp-example.com:1234 +``` + +The `routes` attribute cannot be used in conjunction with the following attributes: `host`, `hosts`, `domain`, `domains`, and `no-hostname`. An error will result. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/services.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/services.html new file mode 100644 index 000000000..c10713143 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/services.html @@ -0,0 +1,18 @@ +

Applications can bind to services such as databases, messaging, and key-value stores.

+ +

Applications are deployed into App Spaces. An application can only bind to services instances that exist in the target App Space before the application is deployed.

+ +

The services block consists of a heading, then one or more service instance names.

+ +

Whoever creates the service chooses the service instance names. These names can convey logical information, as in backend_queue, describe the nature of the service, as in mysql_5.x, or do neither, as in the example below.

+ +
+---
+  ...
+  services:
+   - instance_ABC
+   - instance_XYZ
+
+ +

Binding to a service instance is a special case of setting an environment +variable, namely VCAP_SERVICES. diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/services.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/services.md new file mode 100644 index 000000000..5fb3e1cb9 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/services.md @@ -0,0 +1,17 @@ +Applications can bind to services such as databases, messaging, and key-value stores. + +Applications are deployed into App Spaces. An application can only bind to services instances that exist in the target App Space before the application is deployed. + +The `services` block consists of a heading, then one or more service instance names. + +Whoever creates the service chooses the service instance names. These names can convey logical information, as in `backend_queue`, describe the nature of the service, as in `mysql_5.x`, or do neither, as in the example below. + +``` +--- + ... + services: + - instance_ABC + - instance_XYZ +``` + +Binding to a service instance is a special case of setting an environment variable, namely `VCAP_SERVICES`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/stack.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/stack.html new file mode 100644 index 000000000..021e2788a --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/stack.html @@ -0,0 +1,11 @@ +

Use the stack attribute to specify which stack to deploy your application to.

+ +

To see a list of available stacks, run cf stacks from the cf cli.

+ +
+---
+  ...
+  stack: cflinuxfs2
+
+ +

The command line option that overrides this attribute is -s.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/stack.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/stack.md new file mode 100644 index 000000000..c46611e1d --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/stack.md @@ -0,0 +1,11 @@ +Use the `stack` attribute to specify which stack to deploy your application to. + +To see a list of available stacks, run `cf stacks` from the cf cli. + +``` +--- + ... + stack: cflinuxfs2 +``` + +The command line option that overrides this attribute is `-s`. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/timeout.html b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/timeout.html new file mode 100644 index 000000000..063e26405 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/timeout.html @@ -0,0 +1,14 @@ +

The timeout attribute defines the number of seconds Cloud Foundry allocates for starting your application.

+ +

For example:

+ +
+---
+  ...
+  timeout: 80
+
+ +

You can increase the timeout length for very large apps that require more time to start. The default timeout is 60 seconds with an upper bound of 180 seconds.

+

Note: Administrators can set the upper bound of the maximum_health_check_timeout property to any value. Any changes to Cloud Controller properties in the deployment manifest require running bosh deploy.

+ +

The command line option that overrides the timeout attribute for the shell is -t. Manifest values still apply to applications pushed to the deployment.

diff --git a/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/timeout.md b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/timeout.md new file mode 100644 index 000000000..bc6f8fa29 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/main/resources/description-by-prop-name/timeout.md @@ -0,0 +1,15 @@ +The `timeout` attribute defines the number of seconds Cloud Foundry allocates for starting your application. + +For example: + +``` +--- + ... + timeout: 80 +``` + +You can increase the timeout length for very large apps that require more time to start. The default timeout is 60 seconds with an upper bound of 180 seconds. + +**Note**: Administrators can set the upper bound of the `maximum_health_check_timeout` property to any value. Any changes to Cloud Controller properties in the deployment manifest require running `bosh deploy`. + +The command line option that overrides the timeout attribute for the shell is `-t`. Manifest values still apply to applications pushed to the deployment. \ No newline at end of file diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java new file mode 100644 index 000000000..573d82b8f --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java @@ -0,0 +1,1109 @@ +/******************************************************************************* +f * 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.manifest.yaml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance; +import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException; +import org.springframework.ide.vscode.languageserver.testharness.Editor; +import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; + +import com.google.common.collect.ImmutableList; + +public class ManifestYamlEditorTest { + + LanguageServerHarness harness; + MockCloudfoundry cloudfoundry = new MockCloudfoundry(); + + @Before public void setup() throws Exception { + harness = new LanguageServerHarness(()-> new ManifestYamlLanguageServer(cloudfoundry.factory, cloudfoundry.paramsProvider)); + harness.intialize(null); + } + + @Test public void testReconcileCatchesParseError() throws Exception { + + Editor editor = harness.newEditor( + "somemap: val\n"+ + "- sequence" + ); + editor.assertProblems( + "-|expected " + ); + } + + @Test public void reconcileRunsOnDocumentOpenAndChange() throws Exception { + LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); + harness.intialize(null); + + Editor editor = harness.newEditor( + "somemap: val\n"+ + "- sequence" + ); + + editor.assertProblems( + "-|expected " + ); + + editor.setText( + "- sequence\n" + + "zomemap: val" + ); + + editor.assertProblems( + "z|expected " + ); + } + + @Test + public void reconcileMisSpelledPropertyNames() throws Exception { + Editor editor; + + editor = harness.newEditor( + "memory: 1G\n" + + "aplications:\n" + + " - buildpack: zbuildpack\n" + + " domain: zdomain\n" + + " name: foo" + ); + editor.assertProblems("aplications|Unknown property"); + + //mispelled or not allowed at toplevel + editor = harness.newEditor( + "name: foo\n" + + "buildpeck: yahah\n" + + "memory: 1G\n" + + "memori: 1G\n" + ); + editor.assertProblems( + "name|Unknown property", + "buildpeck|Unknown property", + "memori|Unknown property" + ); + + //mispelled or not allowed as nested + editor = harness.newEditor( + "applications:\n" + + "- name: fine\n" + + " buildpeck: yahah\n" + + " memory: 1G\n" + + " memori: 1G\n" + + " applications: bad\n" + ); + editor.assertProblems( + "buildpeck|Unknown property", + "memori|Unknown property", + "applications|Unknown property" + ); + } + + @Test + public void reconcileStructuralProblems() throws Exception { + Editor editor; + + //forgot the 'applications:' heading + editor = harness.newEditor( + "- name: foo" + ); + editor.assertProblems( + "- name: foo|Expecting a 'Map' but found a 'Sequence'" + ); + + //forgot to make the '-' after applications + editor = harness.newEditor( + "applications:\n" + + " name: foo" + ); + editor.assertProblems( + "name: foo|Expecting a 'Sequence' but found a 'Map'" + ); + + //Using a 'composite' element where a scalar type is expected + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + "memory:\n"+ + "- bad sequence\n" + + "buildpack:\n" + + " bad: map\n" + ); + editor.assertProblems( + "- bad sequence|Expecting a 'Memory' but found a 'Sequence'", + "bad: map|Expecting a 'Buildpack' but found a 'Map'" + ); + } + + @Test + public void reconcileSimpleTypes() throws Exception { + Editor editor; + + //check for 'format' errors: + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: not a number\n" + + " no-route: notBool\n"+ + " memory: 1024\n" + + " disk_quota: 2048\n" + + " health-check-type: unhealthy" + ); + editor.assertProblems( + "not a number|NumberFormatException", + "notBool|boolean", + "1024|doesn't end with a valid unit of memory", + "2048|doesn't end with a valid unit of memory", + "unhealthy|Health Check Type" + ); + + //check for 'range' errors: + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: -3\n" + + " memory: -1024M\n" + + " disk_quota: -2048M\n" + ); + editor.assertProblems( + "-3|Value must be at least 1", + "-1024M|Negative value is not allowed", + "-2048M|Negative value is not allowed" + ); + + //check that correct values are indeed accepted + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: 2\n" + + " no-route: true\n"+ + " memory: 1024M\n" + + " disk_quota: 2048MB\n" + ); + editor.assertProblems(/*none*/); + + //check that correct values are indeed accepted + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: 2\n" + + " no-route: false\n" + + " memory: 1024m\n" + + " disk_quota: 2048mb\n" + ); + editor.assertProblems(/*none*/); + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: 2\n" + + " memory: 1G\n" + + " disk_quota: 2g\n" + ); + editor.assertProblems(/*none*/); + } + + @Test + public void noListIndent() throws Exception { + Editor editor; + editor = harness.newEditor("appl<*>"); + editor.assertCompletions( + "applications:\n"+ + "- <*>" + ); + } + + @Test + public void toplevelCompletions() throws Exception { + Editor editor; + editor = harness.newEditor("<*>"); + editor.assertCompletions( + "applications:\n"+ + "- <*>", + // --------------- + "buildpack: <*>", + // --------------- + "command: <*>", + // --------------- + "disk_quota: <*>", + // --------------- + "domain: <*>", + // --------------- + "domains:\n"+ + "- <*>", + // --------------- + "env:\n"+ + " <*>", + // --------------- + "health-check-type: <*>", + // --------------- +// "host: <*>", + // --------------- +// "hosts: \n"+ +// " - <*>", + // --------------- + "inherit: <*>", + // --------------- + "instances: <*>", + // --------------- + "memory: <*>", + // --------------- +// "name: <*>", + // --------------- + "no-hostname: <*>", + // --------------- + "no-route: <*>", + // --------------- + "path: <*>", + // --------------- + "random-route: <*>", + // --------------- + "routes:\n"+ + "- <*>", + // --------------- + "services:\n"+ + "- <*>", + // --------------- + "stack: <*>", + // --------------- + "timeout: <*>" + ); + + editor = harness.newEditor("ranro<*>"); + editor.assertCompletions( + "random-route: <*>" + ); + } + + @Test + public void nestedCompletions() throws Exception { + Editor editor; + editor = harness.newEditor( + "applications:\n" + + "- <*>" + ); + editor.assertCompletions( + // --------------- + "applications:\n" + + "- buildpack: <*>", + // --------------- + "applications:\n" + + "- command: <*>", + // --------------- + "applications:\n" + + "- disk_quota: <*>", + // --------------- + "applications:\n" + + "- domain: <*>", + // --------------- + "applications:\n" + + "- domains:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- env:\n"+ + " <*>", + // --------------- + "applications:\n" + + "- health-check-type: <*>", + // --------------- + "applications:\n" + + "- host: <*>", + // --------------- + "applications:\n" + + "- hosts:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- instances: <*>", + // --------------- + "applications:\n" + + "- memory: <*>", + // --------------- + "applications:\n" + + "- name: <*>", + // --------------- + "applications:\n" + + "- no-hostname: <*>", + // --------------- + "applications:\n" + + "- no-route: <*>", + // --------------- + "applications:\n" + + "- path: <*>", + // --------------- + "applications:\n" + + "- random-route: <*>", + // --------------- + "applications:\n" + + "- routes:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- services:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- stack: <*>", + // --------------- + "applications:\n" + + "- timeout: <*>" + ); + } + + @Test + public void completionDetailsAndDocs() throws Exception { + Editor editor = harness.newEditor( + "applications:\n" + + "- build<*>" + ); + editor.assertCompletionDetails("buildpack", "Buildpack", "If your application requires a custom buildpack"); + } + + @Test + public void valueCompletions() throws Exception { + assertCompletions("disk_quota: <*>", + "disk_quota: 1024M<*>", + "disk_quota: 256M<*>", + "disk_quota: 512M<*>" + ); + assertCompletions("memory: <*>", + "memory: 1024M<*>", + "memory: 256M<*>", + "memory: 512M<*>" + ); + assertCompletions("no-hostname: <*>", + "no-hostname: false<*>", + "no-hostname: true<*>" + ); + assertCompletions("no-route: <*>", + "no-route: false<*>", + "no-route: true<*>" + ); + assertCompletions("random-route: <*>", + "random-route: false<*>", + "random-route: true<*>" + ); + + assertCompletions("health-check-type: <*>", + "health-check-type: none<*>", + "health-check-type: port<*>" + ); + } + + @Test + public void hoverInfos() throws Exception { + Editor editor = harness.newEditor( + "memory: 1G\n" + + "#comment\n" + + "inherit: base-manifest.yml\n"+ + "applications:\n" + + "- buildpack: zbuildpack\n" + + " domain: zdomain\n" + + " name: foo\n" + + " command: java main.java\n" + + " disk_quota: 1024M\n" + + " domains:\n" + + " - pivotal.io\n" + + " - otherdomain.org\n" + + " env:\n" + + " RAILS_ENV: production\n" + + " RACK_ENV: production\n" + + " host: apppage\n" + + " hosts:\n" + + " - apppage2\n" + + " - appage3\n" + + " instances: 2\n" + + " no-hostname: true\n" + + " no-route: true\n" + + " path: somepath/app.jar\n" + + " random-route: true\n" + + " routes:\n" + + " - route: tcp-example.com:1234\n" + + " services:\n" + + " - instance_ABC\n" + + " - instance_XYZ\n" + + " stack: cflinuxfs2\n" + + " timeout: 80\n" + + " health-check-type: none\n" + ); + + editor.assertIsHoverRegion("memory"); + editor.assertIsHoverRegion("inherit"); + editor.assertIsHoverRegion("applications"); + editor.assertIsHoverRegion("buildpack"); + editor.assertIsHoverRegion("domain"); + editor.assertIsHoverRegion("name"); + editor.assertIsHoverRegion("command"); + editor.assertIsHoverRegion("disk_quota"); + editor.assertIsHoverRegion("domains"); + editor.assertIsHoverRegion("env"); + editor.assertIsHoverRegion("host"); + editor.assertIsHoverRegion("hosts"); + editor.assertIsHoverRegion("instances"); + editor.assertIsHoverRegion("no-hostname"); + editor.assertIsHoverRegion("no-route"); + editor.assertIsHoverRegion("path"); + editor.assertIsHoverRegion("random-route"); + editor.assertIsHoverRegion("routes"); + editor.assertIsHoverRegion("services"); + editor.assertIsHoverRegion("stack"); + editor.assertIsHoverRegion("timeout"); + editor.assertIsHoverRegion("health-check-type"); + + editor.assertHoverContains("memory", "Use the `memory` attribute to specify the memory limit"); + editor.assertHoverContains("1G", "Use the `memory` attribute to specify the memory limit"); + editor.assertHoverContains("inherit", "For example, every child of a parent manifest called `base-manifest.yml` begins like this"); + editor.assertHoverContains("buildpack", "use the `buildpack` attribute to specify its URL or name"); + editor.assertHoverContains("name", "The `name` attribute is the only required attribute for an application in a manifest file"); + editor.assertHoverContains("command", "On the command line, use the `-c` option to specify the custom start command as the following example shows"); + editor.assertHoverContains("disk_quota", "Use the `disk_quota` attribute to allocate the disk space for your app instance"); + editor.assertHoverContains("domain", "You can use the `domain` attribute when you want your application to be served"); + editor.assertHoverContains("domains", "Use the `domains` attribute to provide multiple domains"); + editor.assertHoverContains("env", "The `env` block consists of a heading, then one or more environment variable/value pairs"); + editor.assertHoverContains("host", "Use the `host` attribute to provide a hostname, or subdomain, in the form of a string"); + editor.assertHoverContains("hosts", "Use the `hosts` attribute to provide multiple hostnames, or subdomains"); + editor.assertHoverContains("instances", "Use the `instances` attribute to specify the number of app instances that you want to start upon push"); + editor.assertHoverContains("no-hostname", "By default, if you do not provide a hostname, the URL for the app takes the form of `APP-NAME.DOMAIN`"); + editor.assertHoverContains("no-route", "You can use the `no-route` attribute with a value of `true` to prevent a route from being created for your application"); + editor.assertHoverContains("path", "You can use the `path` attribute to tell Cloud Foundry where to find your application"); + editor.assertHoverContains("random-route", "Use the `random-route` attribute to create a URL that includes the app name and random words"); + editor.assertHoverContains("routes", "Each route for this app is created if it does not already exist"); + editor.assertHoverContains("services", "The `services` block consists of a heading, then one or more service instance names"); + editor.assertHoverContains("stack", "Use the `stack` attribute to specify which stack to deploy your application to."); + editor.assertHoverContains("timeout", "The `timeout` attribute defines the number of seconds Cloud Foundry allocates for starting your application"); + editor.assertHoverContains("health-check-type", "Use the `health-check-type` attribute to"); + } + + @Test + public void noHoverInfos() throws Exception { + Editor editor = harness.newEditor( + "#comment\n" + + "applications:\n" + + "- buildpack: zbuildpack\n" + + " name: foo\n" + + " domains:\n" + + " - pivotal.io\n" + + " - otherdomain.org\n" + + ); + editor.assertNoHover("comment"); + + // May fail in the future if hover support is added, but if hover support is added in the future, + // it is expected that these should start to fail, as right now they have no hover + editor.assertNoHover("pivotal.io"); + editor.assertNoHover("otherdomain.org"); + } + + @Test + public void reconcileDuplicateKeys() throws Exception { + Editor editor = harness.newEditor( + "#comment\n" + + "applications:\n" + + "- buildpack: zbuildpack\n" + + " name: foo\n" + + " domains:\n" + + " - pivotal.io\n" + + " domains:\n" + + " - otherdomain.org\n" + ); + editor.assertProblems( + "domains|Duplicate key", + "domains|Duplicate key" + ); + } + + @Test public void PT_137299017_extra_space_with_completion() throws Exception { + assertCompletions( + "applications:\n" + + "- name: foo\n" + + " random-route:<*>" + , // ==> + "applications:\n" + + "- name: foo\n" + + " random-route: false<*>" + , // -- + "applications:\n" + + "- name: foo\n" + + " random-route: true<*>" + ); + } + + @Test public void PT_137722057_extra_space_with_completion() throws Exception { + assertCompletions( + "applications:\n" + + "-<*>", + // ===> + "applications:\n" + + "- buildpack: <*>", + // --------------- + "applications:\n" + + "- command: <*>", + // --------------- + "applications:\n" + + "- disk_quota: <*>", + // --------------- + "applications:\n" + + "- domain: <*>", + // --------------- + "applications:\n" + + "- domains:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- env:\n"+ + " <*>", + // --------------- + "applications:\n" + + "- health-check-type: <*>", + // --------------- + "applications:\n" + + "- host: <*>", + // --------------- + "applications:\n" + + "- hosts:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- instances: <*>", + // --------------- + "applications:\n" + + "- memory: <*>", + // --------------- + "applications:\n" + + "- name: <*>", + // --------------- + "applications:\n" + + "- no-hostname: <*>", + // --------------- + "applications:\n" + + "- no-route: <*>", + // --------------- + "applications:\n" + + "- path: <*>", + // --------------- + "applications:\n" + + "- random-route: <*>", + // --------------- + "applications:\n" + + "- routes:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- services:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- stack: <*>", + // --------------- + "applications:\n" + + "- timeout: <*>" + ); + + //Second example + assertCompletions( + "applications:\n" + + "-<*>\n" + + "- name: test" + , // ==> + "applications:\n" + + "- buildpack: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- command: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- disk_quota: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- domain: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- domains:\n" + + " - <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- env:\n" + + " <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- health-check-type: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- host: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- hosts:\n" + + " - <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- instances: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- memory: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- name: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- no-hostname: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- no-route: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- path: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- random-route: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- routes:\n" + + " - <*>\n" + + "- name: test" + ,// --------------------- + "applications:\n" + + "- services:\n" + + " - <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- stack: <*>\n" + + "- name: test" + , // --------------------- + "applications:\n" + + "- timeout: <*>\n" + + "- name: test" + ); + + } + + @Test public void numberOfYamlDocumentsShouldBeExactlyOne() throws Exception { + Editor editor; + + { + //when the file is empty (there is no AST at all) + editor = harness.newEditor("#Emptyfile"); + editor.assertProblems("#Emptyfile|'Cloudfoundry Manifest' must have at least some Yaml content"); + } + + { + //when the file has too many documents... then highlight the '---' marker introducing the first document + //exceeding the range. + editor = harness.newEditor( + "---\n" + + "applications:\n"+ + "- name: foo\n" + + " bad-one: xx\n" + + "---\n" + + "applications:\n"+ + "- name: foo\n" + + " bad-two: xx" + ); + editor.assertProblems( + "bad-one|Unknown property", //should still reconcile the documents even thought there's too many of them! + "---|'Cloudfoundry Manifest' should not have more than 1 Yaml Document", + "bad-two|Unknown property" //should still reconcile the documents even thought there's too many of them! + ); + //also check the location of the marker since there are two occurrences of '---' in the editor text. + Diagnostic problem = editor.assertProblem("---"); + assertTrue(problem.getRange().getStart().getLine()>1); + } + + { + // Also check that looking for the '---' isn't confused by extra whitespace + editor = harness.newEditor( + "---\n" + + "applications:\n"+ + "- name: foo\n" + + " \n"+ + "---\n" + + " \n"+ + "applications:\n"+ + "- name: foo\n" + ); + editor.assertProblems( + "---|'Cloudfoundry Manifest' should not have more than 1 Yaml Document" + ); + //also check the location of the marker since there are two occurrences of '---' in the editor text. + Diagnostic problem = editor.assertProblem("---"); + assertTrue(problem.getRange().getStart().getLine()>1); + } + + } + + @Test public void namePropertyIsRequired() throws Exception { + Editor editor = harness.newEditor( + "applications:\n" + + "- name: this-is-good\n" + + "- memory: 1G\n" + + "- name:\n" + ); + editor.assertProblems( + "-^ memory: 1G|Property 'name' is required", + "|should not be empty" + ); + } + + @Test + public void noReconcileErrorsWhenCFFactoryThrows() throws Exception { + reset(cloudfoundry.factory); + when(cloudfoundry.factory.getClient(any(), any())).thenThrow(new IOException("Can't create a client!")); + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " buildpack: bad-buildpack\n" + + " services:\n" + + " - bad-service\n" + + " bogus: bad" //a token error to make sure reconciler is actually running! + ); + editor.assertProblems("bogus|Unknown property"); + } + + @Test + public void noReconcileErrorsWhenClientThrows() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + when(cfClient.getBuildpacks()).thenThrow(new IOException("Can't get buildpacks")); + when(cfClient.getServices()).thenThrow(new IOException("Can't get services")); + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " buildpack: bad-buildpack\n" + + " services:\n" + + " - bad-service\n" + + " bogus: bad" //a token error to make sure reconciler is actually running! + ); + editor.assertProblems("bogus|Unknown property"); + } + + @Test + public void reconcileCFService() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFServiceInstance service = Mockito.mock(CFServiceInstance.class); + when(service.getName()).thenReturn("myservice"); + when(cfClient.getServices()).thenReturn(ImmutableList.of(service)); + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " services:\n" + + " - myservice\n" + + ); + // Should have no problems + editor.assertProblems(/*none*/); + } + + @Test + public void reconcileShowsWarningOnUnknownService() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFServiceInstance service = Mockito.mock(CFServiceInstance.class); + when(service.getName()).thenReturn("myservice"); + when(cfClient.getServices()).thenReturn(ImmutableList.of(service)); + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " services:\n" + + " - bad-service\n" + + ); + editor.assertProblems("bad-service|There is no service instance called"); + + Diagnostic problem = editor.assertProblem("bad-service"); + + assertEquals(DiagnosticSeverity.Warning, problem.getSeverity()); + } + + @Test + public void reconcileShowsWarningOnNoService() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + when(cfClient.getServices()).thenReturn(ImmutableList.of()); + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " services:\n" + + " - bad-service\n"); + editor.assertProblems("bad-service|There is no service instance called"); + + Diagnostic problem = editor.assertProblem("bad-service"); + + assertEquals(DiagnosticSeverity.Warning, problem.getSeverity()); + } + + @Test + public void servicesContentAssistShowErrorMessageWhenNotLoggedIn() throws Exception { + reset(cloudfoundry.paramsProvider); + + when(cloudfoundry.paramsProvider.getParams()).thenThrow(new NoTargetsException("No Cloudfoundry Targets: Please login")); + + String textBefore = + "applications:\n" + + "- name: foo\n" + + " services:\n" + + " - <*>"; + Editor editor = harness.newEditor( + textBefore + ); + + //Applying the single completion should do nothing in the editor: + editor.assertCompletions(textBefore); + + //The message from the exception should appear in the 'doc string': + editor.assertCompletionDetails("No Cloudfoundry Targets", "Error", "Please login"); + + } + + @Test + public void servicesContentAssistShowErrorMessageWhenNotLoggedIn_nonEmptyQueryString() throws Exception { + reset(cloudfoundry.paramsProvider); + + when(cloudfoundry.paramsProvider.getParams()).thenThrow(new NoTargetsException("No Cloudfoundry Targets: Please login")); + + String textBefore = + "applications:\n" + + "- name: foo\n" + + " services:\n" + + " - something<*>"; + Editor editor = harness.newEditor( + textBefore + ); + + //Applying the single completion should do nothing in the editor: + editor.assertCompletions(textBefore); + + //The message from the exception should appear in the 'doc string': + CompletionItem completion = editor.assertCompletionDetails("No Cloudfoundry Targets", "Error", "Please login"); + //query string should match the 'filter text' otherwise vscode will filter the item and it will be gone! + assertEquals("something", completion.getFilterText()); + } + + @Test + public void serviceContentAssistEmptyServices() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + when(cfClient.getServices()).thenReturn(ImmutableList.of()); + assertDoesNotContainCompletions("services:\n" + " - <*>", "mysql"); + } + + @Test + public void serviceContentAssistDoesNotContainServices() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFServiceInstance service = Mockito.mock(CFServiceInstance.class); + when(service.getName()).thenReturn("mysql"); + when(cfClient.getServices()).thenReturn(ImmutableList.of(service)); + assertDoesNotContainCompletions("services:\n" + " - <*>", "wrongsql"); + } + + @Test + public void serviceContentAssist() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFServiceInstance service = Mockito.mock(CFServiceInstance.class); + when(service.getName()).thenReturn("mysql"); + when(cfClient.getServices()).thenReturn(ImmutableList.of(service)); + + assertContainsCompletions("services:\n" + " - <*>", "mysql"); + } + + @Test + public void buildpackContentAssist() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFBuildpack buildPack = Mockito.mock(CFBuildpack.class); + when(buildPack.getName()).thenReturn("java_buildpack"); + when(cfClient.getBuildpacks()).thenReturn(ImmutableList.of(buildPack)); + + assertContainsCompletions("buildpack: <*>", "buildpack: java_buildpack<*>"); + } + + @Test + public void buildpackContentAssistDoesNotContainCompletion() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFBuildpack buildPack = Mockito.mock(CFBuildpack.class); + when(buildPack.getName()).thenReturn("java_buildpack"); + when(cfClient.getBuildpacks()).thenReturn(ImmutableList.of(buildPack)); + assertDoesNotContainCompletions("buildpack: <*>", "buildpack: wrong_buildpack<*>"); + } + + @Test + public void domainContentAssist() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFDomain domain = Mockito.mock(CFDomain.class); + when(domain.getName()).thenReturn("cfapps.io"); + when(cfClient.getDomains()).thenReturn(ImmutableList.of(domain)); + + assertContainsCompletions("domain: <*>", "domain: cfapps.io<*>"); + } + + @Test + public void domainContentAssistDoesNotContainCompletion() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFDomain domain = Mockito.mock(CFDomain.class); + when(domain.getName()).thenReturn("cfapps.io"); + when(cfClient.getDomains()).thenReturn(ImmutableList.of(domain)); + assertDoesNotContainCompletions("domain: <*>", "domain: wrong.cfapps.io<*>"); + } + + @Test + public void domainsContentAssist() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFDomain domain = Mockito.mock(CFDomain.class); + when(domain.getName()).thenReturn("cfapps.io"); + when(cfClient.getDomains()).thenReturn(ImmutableList.of(domain)); + + assertContainsCompletions("domains:\n" + " - <*>", "cfapps.io"); + } + + @Test + public void domainsContentAssistWrongDomain() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFDomain domain = Mockito.mock(CFDomain.class); + when(domain.getName()).thenReturn("cfapps.io"); + when(cfClient.getDomains()).thenReturn(ImmutableList.of(domain)); + assertDoesNotContainCompletions("domains:\n" + " - <*>", "wrong.cfapps.io"); + } + + ////////////////////////////////////////////////////////////////////////////// + + private void assertCompletions(String textBefore, String... textAfter) throws Exception { + Editor editor = harness.newEditor(textBefore); + editor.assertCompletions(textAfter); + } + + private void assertDoesNotContainCompletions(String textBefore, String... notToBeFound) throws Exception { + Editor editor = harness.newEditor(textBefore); + editor.assertDoesNotContainCompletions(notToBeFound); + } + + private void assertContainsCompletions(String textBefore, String... textAfter) throws Exception { + Editor editor = harness.newEditor(textBefore); + editor.assertContainsCompletions(textAfter); + } + + @Test + public void reconcileRouteFormat() throws Exception { + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: http://springsource.org\n"); + editor.assertProblems("http://springsource.org|is not a valid 'Route'"); + Diagnostic problem = editor.assertProblem("http://springsource.org"); + assertEquals(DiagnosticSeverity.Error, problem.getSeverity()); + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: spring source.org\n"); + editor.assertProblems("spring source.org|is not a valid 'Route'"); + problem = editor.assertProblem("spring source.org"); + assertEquals(DiagnosticSeverity.Error, problem.getSeverity()); + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: springsource.org:kuku\n"); + editor.assertProblems("springsource.org:kuku|is not a valid 'Route'"); + problem = editor.assertProblem("springsource.org:kuku"); + assertEquals(DiagnosticSeverity.Error, problem.getSeverity()); + + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: springsource.org/kuku?p=23\n"); + editor.assertProblems("springsource.org/kuku?p=23|is not a valid 'Route'"); + problem = editor.assertProblem("springsource.org/kuku?p=23"); + assertEquals(DiagnosticSeverity.Error, problem.getSeverity()); + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: springsource.org:645788\n"); + editor.assertProblems("springsource.org:645788|is not a valid 'Route'"); + problem = editor.assertProblem("springsource.org:645788"); + assertEquals(DiagnosticSeverity.Error, problem.getSeverity()); + } + + @Test + public void reconcileRoute_Advanced() throws Exception { + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: springsource.org:8765/path\n"); + editor.assertProblems("springsource.org:8765/path|Unable to determine type of route"); + Diagnostic problem = editor.assertProblem("springsource.org:8765/path"); + assertEquals(DiagnosticSeverity.Error, problem.getSeverity()); + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: host.springsource.org:66000\n"); + editor.assertProblems("66000|Invalid port"); + problem = editor.assertProblem("66000"); + assertEquals(DiagnosticSeverity.Error, problem.getSeverity()); + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: host.springsource.org\n"); + editor.assertProblems("springsource.org|Unknown domain"); + problem = editor.assertProblem("springsource.org"); + assertEquals(DiagnosticSeverity.Warning, problem.getSeverity()); + } + + @Test + public void reconcileRouteValidDomain() throws Exception { + ClientRequests cfClient = cloudfoundry.client; + CFDomain domain = Mockito.mock(CFDomain.class); + when(domain.getName()).thenReturn("springsource.org"); + when(cfClient.getDomains()).thenReturn(ImmutableList.of(domain)); + Editor editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " routes:\n" + + " - route: host.springsource.org\n"); + editor.assertProblems(); + } + +} diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java new file mode 100644 index 000000000..192939887 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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.manifest.yaml; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +import org.eclipse.lsp4j.InitializeResult; +import org.eclipse.lsp4j.TextDocumentSyncKind; +import org.junit.Test; +import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; + +public class ManifestYamlLanguageServerTest { + + public static File getTestResource(String name) throws URISyntaxException { + return Paths.get(ManifestYamlLanguageServerTest.class.getResource(name).toURI()).toFile(); + } + + @Test + public void createAndInitializeServerWithWorkspace() throws Exception { + LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); + File workspaceRoot = getTestResource("/workspace/"); + assertExpectedInitResult(harness.intialize(workspaceRoot)); + } + + @Test + public void createAndInitializeServerWithoutWorkspace() throws Exception { + File workspaceRoot = null; + LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); + assertExpectedInitResult(harness.intialize(workspaceRoot)); + } + +// @Test public void completions() throws Exception { +// LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); +// +// File workspaceRoot = getTestResource("/workspace/"); +// assertExpectedInitResult(harness.intialize(workspaceRoot)); +// +// TextDocumentInfo doc = harness.openDocument(getTestResource("/workspace/testfile.yml")); +// +// CompletionList completions = harness.getCompletions(doc, doc.positionOf("foo")); +// assertThat(completions.isIncomplete()).isFalse(); +// assertThat(completions.getItems()) +// .extracting(CompletionItem::getLabel) +// .containsExactly("TypeScript", "JavaScript"); +// +// List resolved = harness.resolveCompletions(completions); +// assertThat(resolved) +// .extracting(CompletionItem::getLabel) +// .containsExactly("TypeScript", "JavaScript"); +// +// assertThat(resolved) +// .extracting(CompletionItem::getDetail) +// .containsExactly("TypeScript details", "JavaScript details"); +// +// assertThat(resolved) +// .extracting(CompletionItem::getDocumentation) +// .containsExactly("TypeScript docs", "JavaScript docs"); +// } + + private void assertExpectedInitResult(InitializeResult initResult) { + assertThat(initResult.getCapabilities().getCompletionProvider().getResolveProvider()).isFalse(); + assertThat(initResult.getCapabilities().getTextDocumentSync().getLeft()).isEqualTo(TextDocumentSyncKind.Incremental); + } + +} diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java new file mode 100644 index 000000000..1c98a65bd --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2015, 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.manifest.yaml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.junit.Test; +import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.util.StringUtil; +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.YTypeFactory.AbstractType; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YSeqType; +import org.springframework.ide.vscode.manifest.yaml.ManifestYmlSchema; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; + +/** + * @author Kris De Volder + */ +public class ManifestYmlSchemaTest { + + private static final String[] NESTED_PROP_NAMES = { +// "applications", + "buildpack", + "command", + "disk_quota", + "domain", + "domains", + "env", + "health-check-type", + "host", + "hosts", +// "inherit", + "instances", + "memory", + "name", + "no-hostname", + "no-route", + "path", + "random-route", + "routes", + "services", + "stack", + "timeout" + }; + + private static final String[] TOPLEVEL_PROP_NAMES = { + "applications", + "buildpack", + "command", + "disk_quota", + "domain", + "domains", + "env", + "health-check-type", +// "host", +// "hosts", + "inherit", + "instances", + "memory", +// "name", + "no-hostname", + "no-route", + "path", + "random-route", + "routes", + "services", + "stack", + "timeout" + }; + + ManifestYmlSchema schema = new ManifestYmlSchema(EMPTY_PROVIDERS); + + @Test + public void toplevelProperties() throws Exception { + assertPropNames(schema.getTopLevelType().getProperties(), TOPLEVEL_PROP_NAMES); + assertPropNames(schema.getTopLevelType().getPropertiesMap(), TOPLEVEL_PROP_NAMES); + } + + @Test + public void nestedProperties() throws Exception { + assertPropNames(getNestedProps(), NESTED_PROP_NAMES); + } + + @Test + public void toplevelPropertiesHaveDescriptions() { + for (YTypedProperty p : schema.getTopLevelType().getProperties()) { + if (!p.getName().equals("applications")) { + assertHasRealDescription(p); + } + } + } + + @Test + public void nestedPropertiesHaveDescriptions() { + for (YTypedProperty p : getNestedProps()) { + assertHasRealDescription(p); + } + } + + ////////////////////////////////////////////////////////////////////////////// + + private void assertHasRealDescription(YTypedProperty p) { + { + String noDescriptionText = Renderables.NO_DESCRIPTION.toHtml(); + String actual = p.getDescription().toHtml(); + String msg = "Description missing for '"+p.getName()+"'"; + assertTrue(msg, StringUtil.hasText(actual)); + assertFalse(msg, noDescriptionText.equals(actual)); + } + { + String noDescriptionText = Renderables.NO_DESCRIPTION.toMarkdown(); + String actual = p.getDescription().toMarkdown(); + String msg = "Description missing for '"+p.getName()+"'"; + assertTrue(msg, StringUtil.hasText(actual)); + assertFalse(msg, noDescriptionText.equals(actual)); + } + } + + private List getNestedProps() { + YSeqType applications = (YSeqType) schema.getTopLevelType().getPropertiesMap().get("applications").getType(); + AbstractType application = (AbstractType) applications.getDomainType(); + return application.getProperties(); + } + + private void assertPropNames(List properties, String... expectedNames) { + assertEquals(ImmutableSet.copyOf(expectedNames), getNames(properties)); + } + + private void assertPropNames(Map propertiesMap, String[] toplevelPropNames) { + assertEquals(ImmutableSet.copyOf(toplevelPropNames), ImmutableSet.copyOf(propertiesMap.keySet())); + } + + private ImmutableSet getNames(Iterable properties) { + Builder builder = ImmutableSet.builder(); + for (YTypedProperty p : properties) { + builder.add(p.getName()); + } + return builder.build(); + } + + private static final ManifestYmlHintProviders EMPTY_PROVIDERS = new ManifestYmlHintProviders() { + + @Override + public Callable> getServicesProvider() { + return null; + } + + @Override + public Callable> getDomainsProvider() { + return null; + } + + @Override + public Callable> getBuildpackProviders() { + return null; + } + }; +} diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/MockCloudfoundry.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/MockCloudfoundry.java new file mode 100644 index 000000000..cc8f42ff8 --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/MockCloudfoundry.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.manifest.yaml; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.mockito.Mockito; +import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; + +import com.google.common.collect.ImmutableList; + +public class MockCloudfoundry { + + public final CFClientParams DEFAULT_PARAMS = new CFClientParams("test.io", "testuser", + CFCredentials.fromRefreshToken("refreshtoken"), false); + + public final CloudFoundryClientFactory factory = mock(CloudFoundryClientFactory.class); + public final ClientRequests client = mock(ClientRequests.class); + public final ClientParamsProvider paramsProvider = mock(ClientParamsProvider.class); + + public MockCloudfoundry() { + try { + //program some default behavior into mocks... most tests will use this. + //other tests should 'reset' the mocks and reprogram them as needed. + when(factory.getClient(any(), any())).thenReturn(client); + when(paramsProvider.getParams()).thenReturn(ImmutableList.of(DEFAULT_PARAMS)); + } catch (Exception e) { + throw ExceptionUtil.unchecked(e); + } + } + + /** + * Reset the mocks. Use this if the default's programmed into the mocks don't suite your test case. + *

+ * Note: you may also choose to call {@link Mockito}.mock directly if you do not want to + * reset all of the mocks. + */ + public void reset() throws Exception { + Mockito.reset(factory, client, paramsProvider); + } + +} diff --git a/headless-services/manifest-yaml-language-server/src/test/resources/workspace/manifest.yml b/headless-services/manifest-yaml-language-server/src/test/resources/workspace/manifest.yml new file mode 100644 index 000000000..7160866ac --- /dev/null +++ b/headless-services/manifest-yaml-language-server/src/test/resources/workspace/manifest.yml @@ -0,0 +1,5 @@ +#Comment +applications: +- name: foo + buildpack: something + \ No newline at end of file diff --git a/vscode-extensions/vscode-manifest-yaml/test/examples/manifest.yml b/headless-services/manifest-yaml-language-server/test/examples/manifest.yml similarity index 100% rename from vscode-extensions/vscode-manifest-yaml/test/examples/manifest.yml rename to headless-services/manifest-yaml-language-server/test/examples/manifest.yml diff --git a/headless-services/pom.xml b/headless-services/pom.xml index 8426d8981..13ec4d29a 100644 --- a/headless-services/pom.xml +++ b/headless-services/pom.xml @@ -11,6 +11,7 @@ commons + manifest-yaml-language-server concourse-language-server boot-properties-language-server boot-java-language-server diff --git a/vscode-extensions/.mvn/wrapper/maven-wrapper.jar b/vscode-extensions/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index c6feb8bb6..000000000 Binary files a/vscode-extensions/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/vscode-extensions/.mvn/wrapper/maven-wrapper.properties b/vscode-extensions/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 6637cedb2..000000000 --- a/vscode-extensions/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1 +0,0 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip \ No newline at end of file diff --git a/vscode-extensions/.project b/vscode-extensions/.project deleted file mode 100644 index 60d5d9946..000000000 --- a/vscode-extensions/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - aggregator - - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - - diff --git a/vscode-extensions/.settings/org.eclipse.m2e.core.prefs b/vscode-extensions/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1c..000000000 --- a/vscode-extensions/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/vscode-extensions/mvnw b/vscode-extensions/mvnw deleted file mode 100755 index 6ecc150ae..000000000 --- a/vscode-extensions/mvnw +++ /dev/null @@ -1,236 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # - # Look for the Apple JDKs first to preserve the existing behaviour, and then look - # for the new JDKs provided by Oracle. - # - if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then - # - # Apple JDKs - # - export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then - # - # Apple JDKs - # - export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then - # - # Oracle JDKs - # - export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then - # - # Apple JDKs - # - export JAVA_HOME=`/usr/libexec/java_home` - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Migwn, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - local basedir=$(pwd) - local wdir=$(pwd) - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - wdir=$(cd "$wdir/.."; pwd) - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@ -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/vscode-extensions/mvnw.cmd b/vscode-extensions/mvnw.cmd deleted file mode 100644 index 8bb827541..000000000 --- a/vscode-extensions/mvnw.cmd +++ /dev/null @@ -1,146 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %* - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - -set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in %* -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/vscode-extensions/pom.xml b/vscode-extensions/pom.xml deleted file mode 100644 index 278a67812..000000000 --- a/vscode-extensions/pom.xml +++ /dev/null @@ -1,16 +0,0 @@ - - 4.0.0 - - org.springframework.ide.vscode - aggregator - pom - 0.0.1-SNAPSHOT - aggregator - - - ../headless-services/commons - vscode-manifest-yaml - - diff --git a/vscode-extensions/vscode-manifest-yaml/.classpath b/vscode-extensions/vscode-manifest-yaml/.classpath deleted file mode 100644 index 0f930ed4f..000000000 --- a/vscode-extensions/vscode-manifest-yaml/.classpath +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vscode-extensions/vscode-manifest-yaml/.project b/vscode-extensions/vscode-manifest-yaml/.project deleted file mode 100644 index 02726e866..000000000 --- a/vscode-extensions/vscode-manifest-yaml/.project +++ /dev/null @@ -1,29 +0,0 @@ - - - vscode-manifest-yaml - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - org.springframework.ide.eclipse.core.springbuilder - - - - - - org.springframework.ide.eclipse.core.springnature - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.core.resources.prefs b/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 29abf9995..000000000 --- a/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,6 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/main/resources=UTF-8 -encoding//src/test/java=UTF-8 -encoding//src/test/resources=UTF-8 -encoding/=UTF-8 diff --git a/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.jdt.core.prefs b/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 714351aec..000000000 --- a/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.m2e.core.prefs b/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1c..000000000 --- a/vscode-extensions/vscode-manifest-yaml/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/vscode-extensions/vscode-manifest-yaml/.vscodeignore b/vscode-extensions/vscode-manifest-yaml/.vscodeignore index a47cdb5ef..8e9d83450 100644 --- a/vscode-extensions/vscode-manifest-yaml/.vscodeignore +++ b/vscode-extensions/vscode-manifest-yaml/.vscodeignore @@ -24,8 +24,6 @@ developer-notes.md # Compiler output out/test/** -target/** -!target/vscode-manifest-yaml-*.jar # Extensions .gitignore diff --git a/vscode-extensions/vscode-manifest-yaml/lib/Main.ts b/vscode-extensions/vscode-manifest-yaml/lib/Main.ts index ceeec688f..17d0d3240 100644 --- a/vscode-extensions/vscode-manifest-yaml/lib/Main.ts +++ b/vscode-extensions/vscode-manifest-yaml/lib/Main.ts @@ -31,7 +31,7 @@ export function activate(context: VSCode.ExtensionContext) { DEBUG : false, CONNECT_TO_LS: false, extensionId: 'vscode-manifest-yaml', - fatJarFile: 'target/vscode-manifest-yaml-0.0.3-SNAPSHOT.jar', + fatJarFile: 'jars/language-server.jar', jvmHeap: '64m', clientOptions: { // HACK!!! documentSelector only takes string|string[] where string is language id, but DocumentFilter object is passed instead diff --git a/vscode-extensions/vscode-manifest-yaml/scripts/preinstall.sh b/vscode-extensions/vscode-manifest-yaml/scripts/preinstall.sh index c1f651ae3..7eef27d15 100755 --- a/vscode-extensions/vscode-manifest-yaml/scripts/preinstall.sh +++ b/vscode-extensions/vscode-manifest-yaml/scripts/preinstall.sh @@ -1,5 +1,8 @@ #!/bin/bash set -e + +workdir=`pwd` + # Download yaml TextMate grammar curl https://raw.githubusercontent.com/textmate/yaml.tmbundle/master/Syntaxes/YAML.tmLanguage > yaml-support/yaml.tmLanguage @@ -8,5 +11,9 @@ curl https://raw.githubusercontent.com/textmate/yaml.tmbundle/master/Syntaxes/YA npm install ../commons-vscode # Use maven to build fat jar of the language server -../mvnw -U -f ../pom.xml -pl vscode-manifest-yaml -am clean install +cd ../../headless-services/manifest-yaml-language-server +./build.sh + +mkdir -p ${workdir}/jars +cp target/*.jar ${workdir}/jars/language-server.jar diff --git a/vscode-extensions/vscode-manifest-yaml/test/.gitignore b/vscode-extensions/vscode-manifest-yaml/test/.gitignore deleted file mode 100644 index a6c7c2852..000000000 --- a/vscode-extensions/vscode-manifest-yaml/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.js diff --git a/vscode-extensions/vscode-manifest-yaml/test/LintSpec.ts b/vscode-extensions/vscode-manifest-yaml/test/LintSpec.ts deleted file mode 100644 index e28292577..000000000 --- a/vscode-extensions/vscode-manifest-yaml/test/LintSpec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as assert from 'assert'; - -// TODO -// describe('lint', () => { -// it('should report a syntax error', done => { -// EMPTY_JAVAC.then(javac => { -// let path = 'test/examples/SyntaxError.java'; - -// return javac.lint({ path }).then(result => { -// let ms = messages(result.messages); - -// assert(ms.length > 0, `${ms} is empty`); - -// done(); -// }); -// }); -// }); - -// it('should report a type error', done => { -// EMPTY_JAVAC.then(javac => { -// let path = 'test/examples/TypeError.java'; - -// return javac.lint({ path }).then(result => { -// let ms = messages(result.messages); - -// assert(ms.length > 0, `${ms} is empty`); - -// done(); -// }); -// }); -// }); -// }); \ No newline at end of file diff --git a/vscode-extensions/vscode-manifest-yaml/test/index.ts b/vscode-extensions/vscode-manifest-yaml/test/index.ts deleted file mode 100644 index e3cebd0d1..000000000 --- a/vscode-extensions/vscode-manifest-yaml/test/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -// -// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING -// -// This file is providing the test runner to use when running extension tests. -// By default the test runner in use is Mocha based. -// -// You can provide your own test runner if you want to override it by exporting -// a function run(testRoot: string, clb: (error:Error) => void) that the extension -// host can call to run the tests. The test runner is expected to use console.log -// to report the results back to the caller. When the tests are finished, return -// a possible error to the callback or null if none. - -var testRunner = require('vscode/lib/testrunner'); - -// You can directly control Mocha options by uncommenting the following lines -// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info -testRunner.configure({ - ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) - useColors: true // colored output from test results -}); - -module.exports = testRunner; \ No newline at end of file diff --git a/vscode-extensions/vscode-manifest-yaml/trigger-rc-build.sh b/vscode-extensions/vscode-manifest-yaml/trigger-rc-build.sh deleted file mode 100755 index ad0e88548..000000000 --- a/vscode-extensions/vscode-manifest-yaml/trigger-rc-build.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -set -e - -if [ -z "$1" ]; then - echo "Usage: ./trigger-rc-build.sh ${RC_TAG}" - echo "Where RC_TAG is one of RC1, RC2, etc." - exit 1 -fi - -rc_tag=$1 -workdir=`pwd` -extension_id=$(basename "$workdir") -version=`jq -r .version package.json` -tag=${extension_id}-${version}-${rc_tag} - -echo "Tagging head as tag=$tag" -git tag $tag -echo "Pushing tag..." -git push origin $tag