Wire up BoshCliConfig so it gets used.

This commit is contained in:
Kris De Volder
2017-07-26 13:39:07 -07:00
parent deb6539054
commit 00972506e5
15 changed files with 189 additions and 38 deletions

View File

@@ -0,0 +1,45 @@
/*******************************************************************************
* 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.bosh;
import java.time.Duration;
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
/**
* Provides access to configuration options that allow user to
* change the way bosh CLI commands are executed by the
* bosh language server.
*
* @author Kris De Volder
*/
public class BoshCliConfig {
/**
* The settings object. This is obtained from 'didChangeConfiguration' events.
*/
private Settings settings = new Settings(null);
public String getCommand() {
return (String) settings.getProperty("cli.command");
}
public String getTarget() {
return (String) settings.getProperty("cli.target");
}
public Duration getTimeout() {
Integer seconds = (Integer) settings.getProperty("cli.timeout");
return seconds == null ? Duration.ofSeconds(3) : Duration.ofSeconds(seconds);
}
public void handleConfigurationChange(Settings newConfig) {
this.settings = newConfig;
}
}

View File

@@ -18,8 +18,11 @@ import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProv
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.Settings;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
@@ -38,7 +41,7 @@ public class BoshLanguageServer extends SimpleLanguageServer {
private final VscodeCompletionEngineAdapter completionEngine;
public BoshLanguageServer(DynamicModelProvider<CloudConfigModel> cloudConfigProvider, DynamicModelProvider<StemcellsModel> stemcellsProvider) {
public BoshLanguageServer(BoshCliConfig cliConfig, DynamicModelProvider<CloudConfigModel> cloudConfigProvider, DynamicModelProvider<StemcellsModel> stemcellsProvider) {
super("vscode-bosh");
YamlAstCache asts = new YamlAstCache();
SimpleTextDocumentService documents = getTextDocumentService();
@@ -64,6 +67,11 @@ public class BoshLanguageServer extends SimpleLanguageServer {
documents.onCompletionResolve(completionEngine::resolveCompletion);
documents.onHover(hoverEngine ::getHover);
documents.onDefinition(new BoshDefintionFinder(this, schema, asts, astTypeCache));
SimpleWorkspaceService workspace = getWorkspaceService();
workspace.onDidChangeConfiguraton((Settings settings) -> {
cliConfig.handleConfigurationChange(settings);
});
}
private void validateOnDocumentChange(IReconcileEngine engine, TextDocument doc) {

View File

@@ -18,9 +18,11 @@ import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
BoshCliConfig cliConfig = new BoshCliConfig();
LaunguageServerApp.start(() -> new BoshLanguageServer(
new BoshCommandCloudConfigProvider(),
new BoshCommandStemcellsProvider()
cliConfig,
new BoshCommandCloudConfigProvider(cliConfig),
new BoshCommandStemcellsProvider(cliConfig)
));
}
}

View File

@@ -11,8 +11,10 @@
package org.springframework.ide.vscode.bosh.models;
import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.springframework.ide.vscode.bosh.BoshCliConfig;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
import org.springframework.ide.vscode.commons.util.ExternalProcess;
@@ -37,9 +39,10 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
private final YamlParser yamlParser;
protected final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
protected Duration CMD_TIMEOUT = Duration.ofSeconds(3);
private final BoshCliConfig config;
protected BoshCommandBasedModelProvider() {
protected BoshCommandBasedModelProvider(BoshCliConfig config) {
this.config = config;
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
yamlParser = new YamlParser(new Yaml());
@@ -69,6 +72,20 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
return blocks[0];
}
protected final ExternalCommand getCommand() {
List<String> command = new ArrayList<>();
command.add(config.getCommand());
String target = config.getTarget();
if (target!=null) {
command.add("-e");
command.add(target);
}
for (String s : getBoshCommand()) {
command.add(s);
}
return new ExternalCommand(command.toArray(new String[command.size()]));
}
protected JsonNode getJsonTree() throws Exception {
String out = executeCommand(getCommand());
return mapper.readTree(out);
@@ -77,7 +94,7 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
protected String executeCommand(ExternalCommand command) throws Exception {
Log.info("executing cmd: "+command);
try {
ExternalProcess process = new ExternalProcess(getWorkingDir(), command, true, CMD_TIMEOUT);
ExternalProcess process = new ExternalProcess(getWorkingDir(), command, true, config.getTimeout());
Log.info("executing cmd SUCCESS: "+process);
String out = process.getOut();
return out;
@@ -91,7 +108,7 @@ public abstract class BoshCommandBasedModelProvider<T> implements DynamicModelPr
return new File(".").getAbsoluteFile();
}
protected abstract ExternalCommand getCommand();
protected abstract String[] getBoshCommand();
protected YamlFileAST parseYaml(String block) throws Exception {
TextDocument doc = new TextDocument(null, LanguageId.BOSH_CLOUD_CONFIG);

View File

@@ -14,6 +14,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.stream.Stream;
import org.springframework.ide.vscode.bosh.BoshCliConfig;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
import org.springframework.ide.vscode.commons.util.StringUtil;
@@ -31,7 +32,8 @@ import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
*/
public class BoshCommandCloudConfigProvider extends BoshCommandBasedModelProvider<CloudConfigModel> {
public BoshCommandCloudConfigProvider() {
public BoshCommandCloudConfigProvider(BoshCliConfig config) {
super(config);
}
private static final YamlTraversal VM_TYPE_NAMES = YamlPath.EMPTY
@@ -103,18 +105,9 @@ public class BoshCommandCloudConfigProvider extends BoshCommandBasedModelProvide
};
}
/**
* Configure how long we wait for the command to fetch cloud config before
* raising timeout exception. (The command may block for long amounts of time
* of the director is unreachable on the network).
*/
public void setCommandTimeout(Duration duration) {
this.CMD_TIMEOUT = duration;
}
@Override
protected ExternalCommand getCommand() {
return new ExternalCommand("bosh", "cloud-config", "--json");
protected String[] getBoshCommand() {
return new String[] {"cloud-config", "--json"};
}
}

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.bosh.models;
import java.util.Collection;
import java.util.stream.Stream;
import org.springframework.ide.vscode.bosh.BoshCliConfig;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
import org.springframework.ide.vscode.commons.util.StringUtil;
@@ -37,6 +38,10 @@ public class BoshCommandStemcellsProvider extends BoshCommandBasedModelProvider<
private static final YamlTraversal STEMCELL_VERSIONS = STEMCELLS
.thenValAt("version");
public BoshCommandStemcellsProvider(BoshCliConfig config) {
super(config);
}
@Override
public StemcellsModel getModel(DynamicSchemaContext dc) throws Exception {
JSONCursor cursor = new JSONCursor(getJsonTree());
@@ -92,8 +97,8 @@ public class BoshCommandStemcellsProvider extends BoshCommandBasedModelProvider<
}
@Override
protected ExternalCommand getCommand() {
return new ExternalCommand("bosh", "stemcells", "--json");
protected String[] getBoshCommand() {
return new String[] { "stemcells", "--json" };
}
}

View File

@@ -48,12 +48,13 @@ public class BoshEditorTest {
LanguageServerHarness harness;
private MockCloudConfigProvider cloudConfigProvider = new MockCloudConfigProvider();
private BoshCliConfig cliConfig = new BoshCliConfig();
private MockCloudConfigProvider cloudConfigProvider = new MockCloudConfigProvider(cliConfig);
private DynamicModelProvider<StemcellsModel> stemcellsProvider = Mockito.mock(DynamicModelProvider.class);
@Before public void setup() throws Exception {
harness = new LanguageServerHarness(() -> {
return new BoshLanguageServer(cloudConfigProvider, (dc) -> stemcellsProvider.getModel(dc))
return new BoshLanguageServer(cliConfig, cloudConfigProvider, (dc) -> stemcellsProvider.getModel(dc))
.setMaxCompletions(100);
},
LanguageId.BOSH_DEPLOYMENT
@@ -1100,7 +1101,7 @@ public class BoshEditorTest {
}
private DynamicModelProvider<StemcellsModel> provideStemcellsFrom(StemcellData... stemcellData) {
return new BoshCommandStemcellsProvider() {
return new BoshCommandStemcellsProvider(cliConfig) {
@Override
protected String executeCommand(ExternalCommand command) throws Exception {
String rows = mapper.writeValueAsString(stemcellData);

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.bosh;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import java.io.File;
import java.net.URISyntaxException;
@@ -20,7 +21,7 @@ import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider;
import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider;import org.springframework.ide.vscode.bosh.models.CloudConfigModel;
import org.springframework.ide.vscode.bosh.models.DynamicModelProvider;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
@@ -30,10 +31,12 @@ public class BoshLanguageServerTest {
return Paths.get(BoshLanguageServerTest.class.getResource(name).toURI()).toFile();
}
private BoshCliConfig cliConfig = new BoshCliConfig();
@Test
public void createAndInitializeServerWithWorkspace() throws Exception {
LanguageServerHarness harness = new LanguageServerHarness(() ->
new BoshLanguageServer(new MockCloudConfigProvider(), Mockito.mock(DynamicModelProvider.class))
new BoshLanguageServer(cliConfig, new MockCloudConfigProvider(cliConfig), mock(DynamicModelProvider.class))
);
File workspaceRoot = getTestResource("/workspace/");
assertExpectedInitResult(harness.intialize(workspaceRoot));
@@ -43,7 +46,7 @@ public class BoshLanguageServerTest {
public void createAndInitializeServerWithoutWorkspace() throws Exception {
File workspaceRoot = null;
LanguageServerHarness harness = new LanguageServerHarness(() ->
new BoshLanguageServer(new MockCloudConfigProvider(), Mockito.mock(DynamicModelProvider.class))
new BoshLanguageServer(cliConfig, new MockCloudConfigProvider(cliConfig), Mockito.mock(DynamicModelProvider.class))
);
assertExpectedInitResult(harness.intialize(workspaceRoot));
}

View File

@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.bosh.mocks;
import java.util.concurrent.Callable;
import org.springframework.ide.vscode.bosh.BoshCliConfig;
import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider;
import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProviderTest;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
@@ -29,6 +30,10 @@ public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvide
private Callable<String> reader = null;
public MockCloudConfigProvider(BoshCliConfig config) {
super(config);
}
/**
* Override with a 'fake' which just returns some mock data. That way we can unit-test
* without requiring a real bosh setup.

View File

@@ -14,6 +14,7 @@ import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.ide.vscode.bosh.BoshCliConfig;
import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
@@ -21,7 +22,8 @@ import com.google.common.collect.ImmutableMultiset;
public class BoshCommandCloudConfigProviderTest {
public final MockCloudConfigProvider provider = new MockCloudConfigProvider();
private BoshCliConfig cliConfig = new BoshCliConfig();
public final MockCloudConfigProvider provider = new MockCloudConfigProvider(cliConfig);
// For local testing only... in CI builds we don't have the means to use a real bosh director and cli.
// private BoshCommandCloudConfigProvider realProvider = new BoshCommandCloudConfigProvider();

View File

@@ -11,21 +11,30 @@
package org.springframework.ide.vscode.bosh.models;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.ide.vscode.bosh.BoshCliConfig;
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
import org.springframework.ide.vscode.commons.util.IOUtil;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
public class BoshCommandStemcellsProviderTest {
private static final String MOCK_DATA_RSRC = "/cmd-out/stemcells.json";;
public BoshCommandStemcellsProvider provider = Mockito.spy(BoshCommandStemcellsProvider.class);
private static final String MOCK_DATA_RSRC = "/cmd-out/stemcells.json";
private BoshCliConfig cliConfig = new BoshCliConfig();
public BoshCommandStemcellsProvider provider = Mockito.spy(new BoshCommandStemcellsProvider(cliConfig));
@Before
public void settup() throws Exception {
@@ -62,5 +71,33 @@ public class BoshCommandStemcellsProviderTest {
provider.getModel(mock(DynamicSchemaContext.class)).getVersions());
}
@Test public void obeysCliConfigCommand() throws Exception {
Map<String, String> settings = ImmutableMap.of(
"cli.command", "alternate-command"
);
cliConfig.handleConfigurationChange(new Settings(settings));
assertEquals(ImmutableList.of(
new StemcellData("bosh-vsphere-esxi-centos-7-go_agent", "3421.11", "centos-7"),
new StemcellData("bosh-vsphere-esxi-ubuntu-trusty-go_agent", "3421.11", "ubuntu-trusty")
),
provider.getModel(mock(DynamicSchemaContext.class)).getStemcells()
);
verify(provider).executeCommand(eq(new ExternalCommand("alternate-command", "stemcells", "--json")));
}
@Test public void obeysCliConfigTarget() throws Exception {
Map<String, String> settings = ImmutableMap.of(
"cli.command", "alternate-command",
"cli.target", "explicit-target"
);
cliConfig.handleConfigurationChange(new Settings(settings));
assertEquals(ImmutableList.of(
new StemcellData("bosh-vsphere-esxi-centos-7-go_agent", "3421.11", "centos-7"),
new StemcellData("bosh-vsphere-esxi-ubuntu-trusty-go_agent", "3421.11", "ubuntu-trusty")
),
provider.getModel(mock(DynamicSchemaContext.class)).getStemcells()
);
verify(provider).executeCommand(eq(new ExternalCommand("alternate-command", "-e", "explicit-target", "stemcells", "--json")));
}
}

View File

@@ -8,7 +8,6 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.util;
import java.util.Map;
@@ -18,9 +17,9 @@ import java.util.Map;
* retrieve properties from the settings object.
*/
public class Settings {
private Object settings;
public Settings(Object settings) {
this.settings = settings;
}
@@ -32,7 +31,7 @@ public class Settings {
}
return null;
}
public Object getProperty(String... names) {
return getProperty(settings, names, 0);
}
@@ -49,6 +48,8 @@ public class Settings {
}
}
@Override
public String toString() {
return settings.toString();
}
}

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeoutException;
/**
@@ -76,4 +77,27 @@ public class ExternalCommand {
// org.junit.Assert.assertEquals(0, process.getExitValue());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(command);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ExternalCommand other = (ExternalCommand) obj;
if (!Arrays.equals(command, other.command))
return false;
return true;
}
}

View File

@@ -36,7 +36,10 @@ export function activate(context: VSCode.ExtensionContext) {
fatJarFile: 'jars/language-server.jar',
jvmHeap: "48m",
clientOptions: {
documentSelector: [ BOSH_DEPLOYMENT_LANGUAGE_ID ]
documentSelector: [ BOSH_DEPLOYMENT_LANGUAGE_ID ],
synchronize: {
configurationSection: "bosh"
}
}
};
let clientPromise = commons.activate(options, context);

View File

@@ -58,6 +58,11 @@
"type": ["string", "null"],
"default": null,
"description": "Specifies the director to target when executing bosh cli commands. I.e. this value is passed to the CLI via `-e` parameter."
},
"bosh.cli.timeout": {
"type": "integer",
"default": 3,
"description": "Number of seconds before CLI commands are terminated with a timeout"
}
}
}