Add dynamic CF buildpacks and services completion values

This commit is contained in:
nsingh
2017-01-07 11:59:17 -08:00
parent 2586dc419f
commit 46eb0466e2
15 changed files with 358 additions and 45 deletions

View File

@@ -11,13 +11,6 @@
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<cloudfoundry.client.version>2.1.0.RELEASE</cloudfoundry.client.version>
<client.reactor.version>3.0.4.RELEASE</client.reactor.version>
<reactor.netty>0.6.0.RELEASE</reactor.netty>
</properties>
<dependencies>
<dependency>
@@ -28,22 +21,22 @@
<dependency>
<groupId>org.cloudfoundry</groupId>
<artifactId>cloudfoundry-client-reactor</artifactId>
<version>${cloudfoundry.client.version}</version>
<version>${cloudfoundry-client-version}</version>
</dependency>
<dependency>
<groupId>org.cloudfoundry</groupId>
<artifactId>cloudfoundry-operations</artifactId>
<version>${cloudfoundry.client.version}</version>
<version>${cloudfoundry-client-version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${client.reactor.version}</version>
<version>${reactor-version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
<version>${reactor.netty}</version>
<version>${reactor-netty}</version>
</dependency>
</dependencies>
</project>

View File

@@ -148,4 +148,12 @@ public class CFClientParams {
return true;
}
@Override
public String toString() {
return "CFClientParams [apiUrl=" + apiUrl + ", username=" + username + ", credentials=" + credentials
+ ", skipSslValidation=" + skipSslValidation + ", orgName=" + orgName + ", spaceName=" + spaceName
+ "]";
}
}

View File

@@ -0,0 +1,19 @@
/*******************************************************************************
* 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.commons.cloudfoundry.client.v2;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams;
public interface CloudFoundryClientFactory {
ClientRequests getClient(CFClientParams params) throws Exception;
}

View File

@@ -12,9 +12,9 @@ package org.springframework.ide.vscode.commons.cloudfoundry.client.v2;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams;
public class DefaultCloudFoundryClientFactoryV2 {
public class DefaultCloudFoundryClientFactoryV2 implements CloudFoundryClientFactory {
public static final DefaultCloudFoundryClientFactoryV2 INSTANCE = new DefaultCloudFoundryClientFactoryV2();
public static final CloudFoundryClientFactory INSTANCE = new DefaultCloudFoundryClientFactoryV2();
/**
* Use 'INSTANCE' constant instead. This class is a singleton.
@@ -23,7 +23,11 @@ public class DefaultCloudFoundryClientFactoryV2 {
private CloudFoundryClientCache cache = new CloudFoundryClientCache();
public ClientRequests getClient(CFClientParams params) {
/* (non-Javadoc)
* @see org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientFactory#getClient(org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams)
*/
@Override
public ClientRequests getClient(CFClientParams params) throws Exception {
return new DefaultClientRequestsV2(cache, params);
}
}

View File

@@ -38,11 +38,12 @@ public class ReactorUtils {
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(45); // reflects default timeout of Mono.block in reactor 2.x.
public static boolean DUMP_STACK_ON_TIMEOUT = false;
/**
* Convert a {@link CancelationToken} into a Mono that raises
* an {@link OperationCanceledException} when the token is canceled.
*/
public static <T> Mono<T> toMono(CancelationToken cancelToken) {
// TODO: uncommented when cancellation is handled in vscode
// /**
// * Convert a {@link CancelationToken} into a Mono that raises
// * an {@link OperationCanceledException} when the token is canceled.
// */
// public static <T> Mono<T> toMono(CancelationToken cancelToken) {
// return Mono.delay(Duration.ofSeconds(1))
// .then((ping) ->
// cancelToken.isCanceled()
@@ -50,9 +51,7 @@ public class ReactorUtils {
// : Mono.empty()
// )
// .repeatWhenEmpty((x) -> x);
return Mono.empty();
}
// }
/**
* Similar to Mono.get but logs a more traceable version of the exception to Eclipse's error
@@ -79,9 +78,12 @@ public class ReactorUtils {
*/
public static <T> T get(Duration timeout, CancelationToken cancelationToken, Mono<T> mono) throws Exception {
try {
return Mono.first(mono,
toMono(cancelationToken))
.otherwise(errorFilter(cancelationToken))
return mono
// TODO: uncomment when cancellation properly supported in vscode
// Mono
// .first(mono,
// toMono(cancelationToken))
// .otherwise(errorFilter(cancelationToken))
.block(timeout);
} catch (Exception e) {
dumpStacks();

View File

@@ -10,33 +10,32 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.cloudfoundry.client;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParamsFactory;
import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTarget;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTargets;
import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientFactory;
import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2;
public class CFClientTest {
@Ignore @Test public void testGetBuildpacksFromCliParams() throws Exception {
CFClientParams params = CFClientParamsFactory.INSTANCE.getParams().get(0);
assertNotNull(params);
@Ignore @Test public void testGetBuildpacksFromCliParamsTarget() throws Exception {
ClientRequests requests = DefaultCloudFoundryClientFactoryV2.INSTANCE.getClient(params);
CFClientParamsFactory paramsFactory = CFClientParamsFactory.INSTANCE;
CloudFoundryClientFactory clientFactory = DefaultCloudFoundryClientFactoryV2.INSTANCE;
assertNotNull(requests);
CFClientTargets targets = new CFClientTargets(paramsFactory, clientFactory);
CFClientTarget target = targets.getTargets().get(0);
List<CFBuildpack> buildPacks = requests.getBuildpacks();
List<CFBuildpack> buildPacks = target.getBuildpacks();
assertTrue(!buildPacks.isEmpty());
}
}

View File

@@ -238,6 +238,22 @@ public class Editor {
assertEquals(expect.toString(), actual.toString());
}
public void assertContainsCompletions(String... expectTextAfter) throws Exception {
StringBuilder actual = new StringBuilder();
for (CompletionItem completion : getCompletions()) {
Editor editor = this.clone();
editor.apply(completion);
actual.append(editor.getText());
actual.append("\n-------------------\n");
}
String actualText = actual.toString();
for (String after : expectTextAfter) {
assertContains(after, actualText);
}
}
public void apply(CompletionItem completion) throws Exception {
TextEdit edit = completion.getTextEdit();
String docText = document.getText();

View File

@@ -62,7 +62,10 @@
<jackson-2-version>2.5.0</jackson-2-version>
<jersey-2-version>2.10</jersey-2-version>
<lsp4j-version>0.1.0-SNAPSHOT</lsp4j-version>
<reactor-version>3.0.2.RELEASE</reactor-version>
<!-- NOTE: Reactor version must match version used by the CF client -->
<reactor-version>3.0.4.RELEASE</reactor-version>
<reactor-netty>0.6.0.RELEASE</reactor-netty>
<cloudfoundry-client-version>2.1.0.RELEASE</cloudfoundry-client-version>
</properties>
<build>

View File

@@ -42,6 +42,12 @@
<artifactId>commons-yaml</artifactId>
<version>${project.version}</version>
</dependency>
<!-- CF -->
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>commons-cf</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Test harness -->
<dependency>
<groupId>org.springframework.ide.vscode</groupId>

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* 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 java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Provider;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTarget;
import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
public class ManifestYamlCFBuildpacksProvider implements Provider<Collection<YValueHint>> {
private final List<CFClientTarget> targets;
private static final Logger logger = Logger.getLogger(ManifestYamlCFBuildpacksProvider.class.getName());
public ManifestYamlCFBuildpacksProvider(List<CFClientTarget> targets) {
this.targets = targets;
}
@Override
public Collection<YValueHint> get() {
List<YValueHint> hints = new ArrayList<>();
if (targets != null) {
for (CFClientTarget cfClientTarget : targets) {
List<CFBuildpack> buildpacks;
try {
buildpacks = cfClientTarget.getBuildpacks();
if (buildpacks != null) {
for (CFBuildpack buildpack : buildpacks) {
String name = buildpack.getName();
String label = getBuildpackLabel(cfClientTarget, buildpack);
YValueHint hint = new BasicYValueHint(name, label);
if (!hints.contains(hint)) {
hints.add(hint);
}
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
}
return hints;
}
protected String getBuildpackLabel(CFClientTarget target, CFBuildpack buildpack) {
return buildpack.getName() + " (" + target.getName() + ")";
}
}

View File

@@ -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 java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Provider;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTarget;
import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
public class ManifestYamlCFServicesProvider implements Provider<Collection<YValueHint>> {
private final List<CFClientTarget> targets;
private static final Logger logger = Logger.getLogger(ManifestYamlCFServicesProvider.class.getName());
public ManifestYamlCFServicesProvider(List<CFClientTarget> targets) {
this.targets = targets;
}
@Override
public Collection<YValueHint> get() {
List<YValueHint> hints = new ArrayList<>();
if (targets != null) {
for (CFClientTarget cfClientTarget : targets) {
try {
List<CFServiceInstance> services = cfClientTarget.getClientRequests().getServices();
if (services != null) {
for (CFServiceInstance service : services) {
String name = service.getName();
String label = getServiceLabel(cfClientTarget, service);
YValueHint hint = new BasicYValueHint(name, label);
if (!hints.contains(hint)) {
hints.add(hint);
}
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
}
return hints;
}
private String getServiceLabel(CFClientTarget cfClientTarget, CFServiceInstance service) {
return service.getName() + " - " + service.getPlan() + " (" + cfClientTarget.getParams().getOrgName() + " - "
+ cfClientTarget.getParams().getSpaceName() + ")";
}
}

View File

@@ -1,12 +1,20 @@
package org.springframework.ide.vscode.manifest.yaml;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Provider;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParamsFactory;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTarget;
import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTargets;
import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientFactory;
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;
@@ -32,16 +40,25 @@ import com.google.common.collect.ImmutableList;
public class ManifestYamlLanguageServer extends SimpleLanguageServer {
private static final Provider<Collection<YValueHint>> NO_BUILDPACKS = () -> ImmutableList.of();
private static final Provider<Collection<YValueHint>> NO_PROVIDER = () -> ImmutableList.of();
private static Logger logger = Logger.getLogger(ManifestYamlLanguageServer.class.getName());
private Yaml yaml = new Yaml();
private YamlSchema schema = new ManifestYmlSchema(NO_BUILDPACKS);
private YamlSchema schema;
private CFClientTargets cfClientTargets;
public ManifestYamlLanguageServer() {
SimpleTextDocumentService documents = getTextDocumentService();
YamlASTProvider parser = new YamlParser(yaml);
CFClientTargets cfTargets = getCFTargets();
Provider<Collection<YValueHint>> buildPacksProvider = getBuildpacksProvider(cfTargets);
Provider<Collection<YValueHint>> servicesProvider = getServicesProvider(cfTargets);
schema = new ManifestYmlSchema(buildPacksProvider, servicesProvider);
YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT;
YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema);
@@ -72,7 +89,48 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
documents.onCompletionResolve(completionEngine::resolveCompletion);
documents.onHover(hoverEngine ::getHover);
}
private CFClientTargets getCFTargets() {
if (cfClientTargets == null) {
CFClientParamsFactory paramsFactory = CFClientParamsFactory.INSTANCE;
CloudFoundryClientFactory clientFactory = DefaultCloudFoundryClientFactoryV2.INSTANCE;
cfClientTargets = new CFClientTargets(paramsFactory, clientFactory);
}
return cfClientTargets;
}
private Provider<Collection<YValueHint>> getBuildpacksProvider(CFClientTargets targets) {
try {
if (targets != null) {
List<CFClientTarget> cfTargets = targets.getTargets();
if (cfTargets != null && !cfTargets.isEmpty()) {;
return new ManifestYamlCFBuildpacksProvider(cfTargets);
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
return NO_PROVIDER;
}
private Provider<Collection<YValueHint>> getServicesProvider(CFClientTargets targets) {
try {
if (targets != null) {
List<CFClientTarget> cfTargets = targets.getTargets();
if (cfTargets != null && !cfTargets.isEmpty()) {
return new ManifestYamlCFServicesProvider(cfTargets);
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
return null;
}
@Override
protected ServerCapabilities getServerCapabilities() {

View File

@@ -19,6 +19,7 @@ import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
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;
@@ -41,7 +42,7 @@ public class ManifestYmlSchema implements YamlSchema {
"name", "host", "hosts"
);
public ManifestYmlSchema(Provider<Collection<YValueHint>> buildpackProvider) {
public ManifestYmlSchema(Provider<Collection<YValueHint>> buildpackProvider, Provider<Collection<YValueHint>> servicesProvider) {
this.buildpackProvider = buildpackProvider;
YTypeFactory f = new YTypeFactory();
TYPE_UTIL = f.TYPE_UTIL;
@@ -51,15 +52,20 @@ public class ManifestYmlSchema implements YamlSchema {
YBeanType application = f.ybean("Application");
YAtomicType t_path = f.yatomic("Path");
YAtomicType t_buildpack = f.yatomic("Buildpack");
YAtomicType t_buildpack = f.yatomic("Buildpack");
t_buildpack.addHintProvider(this.buildpackProvider);
YType t_service_string = f.yatomic("String");
if (servicesProvider != null && t_service_string instanceof AbstractType) {
((AbstractType)t_service_string).addHintProvider(servicesProvider);
}
YType t_services = f.yseq(t_service_string);
YAtomicType t_boolean = f.yenum("boolean", "true", "false");
YType t_string = f.yatomic("String");
YType t_strings = f.yseq(t_string);
YAtomicType t_memory = f.yatomic("Memory");
t_memory.addHints("256M", "512M", "1024M");
t_memory.parseWith(ManifestYmlValueParsers.MEMORY);
@@ -94,7 +100,7 @@ public class ManifestYmlSchema implements YamlSchema {
f.yprop("no-route", t_boolean),
f.yprop("path", t_path),
f.yprop("random-route", t_boolean),
f.yprop("services", t_strings),
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)

View File

@@ -0,0 +1,63 @@
/*******************************************************************************
* 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 org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
public class ManifestYamlEditorCFTest {
LanguageServerHarness harness;
@Before
public void setup() throws Exception {
harness = new LanguageServerHarness(ManifestYamlLanguageServer::new);
harness.intialize(null);
}
/*
* Optional test that is here only to be run in certain conditions. Tests if
* buildpack completion values are actually fetched from PWS if the test env
* also has a CLI that is alread connected to PWS. Enable only if underlying
* conditions for CF connection from vscode are present. For example, CLI is
* installed.
*/
@Ignore
@Test
public void optionalDynamicBuildpacksPWSUsingCliParams() throws Exception {
// No special test harness setup to use CLI. It is a "default" CF client params
// provider in the CF vscode framework.
// Just have to make sure the test env has a CLI that is connected to
// PWS
assertContainsCompletions("buildpack: <*>", "buildpack: java_buildpack<*>");
}
@Ignore
@Test
public void optionalDynamicServicesPWSUsingCliParams() throws Exception {
// No special test harness setup to use CLI. It is a "default" CF client params
// provider in the CF vscode framework.
// Just have to make sure the test env has a CLI that is connected to
// PWS
assertContainsCompletions( "services:\n"+
" - <*>", "sql");
}
//////////////////////////////////////////////////////////////////////////////
private void assertContainsCompletions(String textBefore, String... textAfter) throws Exception {
Editor editor = harness.newEditor(textBefore);
editor.assertContainsCompletions(textAfter);
}
}

View File

@@ -82,7 +82,7 @@ public class ManifestYmlSchemaTest {
"timeout"
};
ManifestYmlSchema schema = new ManifestYmlSchema(null);
ManifestYmlSchema schema = new ManifestYmlSchema(null, null);
@Test
public void toplevelProperties() throws Exception {