diff --git a/headless-services/bosh-language-server/pom.xml b/headless-services/bosh-language-server/pom.xml index 2f00f5c1f..cbd6d6d60 100644 --- a/headless-services/bosh-language-server/pom.xml +++ b/headless-services/bosh-language-server/pom.xml @@ -45,6 +45,18 @@ ${dependencies.version} test + + org.mockito + mockito-all + ${mockito-version} + test + + + org.springframework.ide.vscode + language-server-test-harness + ${dependencies.version} + test + diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java index 4312f0f25..e1ecd7b2c 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.Map; import java.util.UUID; +import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigProvider; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; @@ -77,9 +78,11 @@ public class BoshDeploymentManifestSchema implements YamlSchema { private YType t_release_name_ref; private YType t_var_name_def; private final ASTTypeCache astTypes; + private CloudConfigProvider cloudConfigProvider; - public BoshDeploymentManifestSchema(ASTTypeCache astTypes) { + public BoshDeploymentManifestSchema(ASTTypeCache astTypes, CloudConfigProvider cloudConfigProvider) { this.astTypes = astTypes; + this.cloudConfigProvider = cloudConfigProvider; TYPE_UTIL = f.TYPE_UTIL; V2_TOPLEVEL_TYPE = createV2Schema(); diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java index 5951962bd..6441898cf 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh; +import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigProvider; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.LazyCompletionResolver; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; @@ -40,12 +41,12 @@ public class BoshLanguageServer extends SimpleLanguageServer { private final LazyCompletionResolver completionResolver = new LazyCompletionResolver(); //Set to null to disable lazy resolving private final VscodeCompletionEngineAdapter completionEngine; - public BoshLanguageServer() { + public BoshLanguageServer(CloudConfigProvider cloudConfigProvider) { super("vscode-bosh"); YamlASTProvider parser = new YamlParser(yaml); SimpleTextDocumentService documents = getTextDocumentService(); ASTTypeCache astTypeCache = new ASTTypeCache(); - BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(astTypeCache); + BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(astTypeCache, cloudConfigProvider); YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT; YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema); diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java index ecdbf2c3c..91b0c6119 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java @@ -8,16 +8,15 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ - package org.springframework.ide.vscode.bosh; import java.io.IOException; +import org.springframework.ide.vscode.bosh.cloudconfig.BoshCommandCloudConfigProvider; import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; -import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions; public class Main { public static void main(String[] args) throws IOException, InterruptedException { - LaunguageServerApp.start(() -> new BoshLanguageServer()); + LaunguageServerApp.start(() -> new BoshLanguageServer(new BoshCommandCloudConfigProvider())); } } diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java new file mode 100644 index 000000000..68cfac007 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * 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.cloudconfig; + +import java.io.File; +import java.time.Duration; +import java.util.Collection; +import java.util.stream.Stream; + +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.CollectorUtil; +import org.springframework.ide.vscode.commons.util.ExternalCommand; +import org.springframework.ide.vscode.commons.util.ExternalProcess; +import org.springframework.ide.vscode.commons.util.StringUtil; +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.NodeUtil; +import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; +import org.springframework.ide.vscode.commons.yaml.ast.YamlParser; +import org.springframework.ide.vscode.commons.yaml.path.YamlPath; +import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal; +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.representer.Representer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Concrete implementation of {@link CloudConfigProvider} that runs `bosh cloud-config` command + * with bosh cli and parses the output. + * + * @author Kris De Volder + */ +public class BoshCommandCloudConfigProvider implements CloudConfigProvider { + + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + final YamlParser yamlParser; + + public BoshCommandCloudConfigProvider() { + Representer representer = new Representer(); + representer.getPropertyUtils().setSkipMissingProperties(true); + yamlParser = new YamlParser(new Yaml()); + } + + /** + * For deserializing the output from bosh cloud-config command. + */ + public static class CloudConfigResponse { + private String[] blocks; + + @JsonProperty("Blocks") + public String[] getBlocks() { + return blocks; + } + + public void setBlocks(String[] blocks) { + this.blocks = blocks; + } + } + + YamlTraversal VM_TYPE_NAMES = YamlPath.EMPTY + .thenAnyChild() + .thenValAt("vm_types") + .thenAnyChild() + .thenValAt("name"); + + @Override + public CloudConfigModel getCloudConfig(DynamicSchemaContext dc) throws Exception { + String out = executeBoshCloudConfigCommand(); + CloudConfigResponse response = mapper.readValue(out, CloudConfigResponse.class); + String[] blocks = response.getBlocks(); + Assert.isLegal(blocks!=null); + Assert.isLegal(blocks.length==1); + TextDocument doc = new TextDocument(null, LanguageId.BOSH_CLOUD_CONFIG); + doc.setText(blocks[0]); + YamlFileAST ast = yamlParser.getAST(doc); + return new CloudConfigModel() { + @Override + public Collection getVMTypes() { + return VM_TYPE_NAMES.traverseAmbiguously(ast) + .flatMap(nameNode -> { + String name = NodeUtil.asScalar(nameNode); + return StringUtil.hasText(name) + ? Stream.of(name) + : Stream.empty(); + }) + .collect(CollectorUtil.toMultiset()); + } + }; + } + + protected String executeBoshCloudConfigCommand() throws Exception { + ExternalCommand command = new ExternalCommand("bosh", "cloud-config", "--json"); + ExternalProcess process = new ExternalProcess(new File(".").getAbsoluteFile(), command, true, Duration.ofSeconds(30)); + System.out.println("executeBoshCloudConfigCommand: "+process); + String out = process.getOut(); + return out; + } + +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigModel.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigModel.java new file mode 100644 index 000000000..ec3cae7a9 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigModel.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * 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.cloudconfig; + +import java.util.Collection; + +/** + * Represents CloudConfig information as might be retrieved from bosh director. + */ +public interface CloudConfigModel { + Collection getVMTypes(); +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigProvider.java new file mode 100644 index 000000000..5d1a3c306 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigProvider.java @@ -0,0 +1,12 @@ +package org.springframework.ide.vscode.bosh.cloudconfig; + +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; + +/** + * Responsible for somehow obtaining {@link CloudConfigModel} relative to a {@link DynamicSchemaContext} + */ +public interface CloudConfigProvider { + + CloudConfigModel getCloudConfig(DynamicSchemaContext dc) throws Exception; + +} diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java new file mode 100644 index 000000000..26d4728d3 --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * 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 static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.ide.vscode.bosh.cloudconfig.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigModel; +import org.springframework.ide.vscode.commons.util.IOUtil; +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; + +import com.google.common.collect.ImmutableMultiset; + +public class BoshCommandCloudConfigProviderTest { + + private static final String MOCK_DATA_RSRC = "/cmd-out/cloud-config.json"; + + public static final BoshCommandCloudConfigProvider mockProvider = new BoshCommandCloudConfigProvider() { + /** + * Override with a 'fake' which just returns some mock data. That way we can unit-test + * without requiring a real bosh setup. + */ + @Override + protected String executeBoshCloudConfigCommand() throws Exception { + return IOUtil.toString(BoshCommandCloudConfigProviderTest.class.getResourceAsStream(MOCK_DATA_RSRC)); + }; + }; + +// 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(); + + @Test public void getVMTypes() throws Exception { + BoshCommandCloudConfigProvider provider = mockProvider; + DynamicSchemaContext dc = Mockito.mock(DynamicSchemaContext.class); + CloudConfigModel cloudConfig = provider.getCloudConfig(dc); + + assertEquals(ImmutableMultiset.of("default", "large"), cloudConfig.getVMTypes()); + } + +} diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java similarity index 99% rename from headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java rename to headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java index 9a0d813bf..2d18dc3ae 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.concourse; +package org.springframework.ide.vscode.bosh; import static org.springframework.ide.vscode.languageserver.testharness.Editor.PLAIN_COMPLETION; @@ -26,7 +26,7 @@ public class BoshEditorTest { @Before public void setup() throws Exception { harness = new LanguageServerHarness(() -> { - return new BoshLanguageServer() + return new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider) .setMaxCompletions(100); }, LanguageId.BOSH_DEPLOYMENT diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshLanguageServerTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java similarity index 90% rename from headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshLanguageServerTest.java rename to headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java index 3b542dd30..83f89d4f6 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshLanguageServerTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java @@ -9,7 +9,7 @@ * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.concourse; +package org.springframework.ide.vscode.bosh; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +31,9 @@ public class BoshLanguageServerTest { @Test public void createAndInitializeServerWithWorkspace() throws Exception { - LanguageServerHarness harness = new LanguageServerHarness(() -> new BoshLanguageServer()); + LanguageServerHarness harness = new LanguageServerHarness(() -> + new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider) + ); File workspaceRoot = getTestResource("/workspace/"); assertExpectedInitResult(harness.intialize(workspaceRoot)); } @@ -39,7 +41,9 @@ public class BoshLanguageServerTest { @Test public void createAndInitializeServerWithoutWorkspace() throws Exception { File workspaceRoot = null; - LanguageServerHarness harness = new LanguageServerHarness(() -> new BoshLanguageServer()); + LanguageServerHarness harness = new LanguageServerHarness(() -> + new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider) + ); assertExpectedInitResult(harness.intialize(workspaceRoot)); } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshValueParserTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshValueParserTest.java similarity index 98% rename from headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshValueParserTest.java rename to headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshValueParserTest.java index 3c814eae9..c54f6ea2a 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshValueParserTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshValueParserTest.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.concourse; +package org.springframework.ide.vscode.bosh; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/headless-services/bosh-language-server/src/test/resources/cmd-out/cloud-config.json b/headless-services/bosh-language-server/src/test/resources/cmd-out/cloud-config.json new file mode 100644 index 000000000..360653a13 --- /dev/null +++ b/headless-services/bosh-language-server/src/test/resources/cmd-out/cloud-config.json @@ -0,0 +1,10 @@ +{ + "Tables": null, + "Blocks": [ + "azs:\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z1\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z2\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z3\ncompilation:\n az: z1\n network: default\n reuse_compilation_vms: true\n vm_type: default\n workers: 5\ndisk_types:\n- disk_size: 3000\n name: default\n- disk_size: 50000\n name: large\nnetworks:\n- name: default\n subnets:\n - azs:\n - z1\n - z2\n - z3\n cloud_properties:\n name: VLAN 40 - AF\n dns:\n - 10.192.2.10\n - 8.8.8.8\n gateway: 10.194.4.1\n range: 10.194.4.0/23\n reserved:\n - 10.194.4.1-10.194.4.34\n - 10.194.4.40-10.194.5.255\n type: manual\nvm_types:\n- cloud_properties:\n cpu: 2\n disk: 3240\n ram: 1024\n name: default\n- cloud_properties:\n cpu: 2\n disk: 30240\n ram: 4096\n name: large\n" + ], + "Lines": [ + "Using environment '10.194.4.35' as client 'admin'", + "Succeeded" + ] +} \ No newline at end of file diff --git a/headless-services/commons/commons-cf/pom.xml b/headless-services/commons/commons-cf/pom.xml index ddfa1ebbf..587de437c 100644 --- a/headless-services/commons/commons-cf/pom.xml +++ b/headless-services/commons/commons-cf/pom.xml @@ -32,7 +32,7 @@ org.mockito mockito-all - 1.10.19 + ${mockito-version} test diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java index d1d12b5f6..54018b13a 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeoutException; import org.springframework.ide.vscode.commons.util.ExternalCommand; import org.springframework.ide.vscode.commons.util.ExternalProcess; @@ -28,7 +29,7 @@ public class MavenBuilder { private List properties; - public void execute() throws IOException, InterruptedException { + public void execute() throws IOException, InterruptedException, TimeoutException { Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win") ? projectPath.resolve("mvnw.cmd") : projectPath.resolve("mvnw"); mvnwPath.toFile().setExecutable(true); diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalCommand.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalCommand.java index 39895018d..8999daccd 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalCommand.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalCommand.java @@ -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.concurrent.TimeoutException; /** * Encapsulates information about an 'external' command that can be run through the OS. @@ -68,7 +69,7 @@ public class ExternalCommand { * result of commands are logged to the console and if the command returns non * 0 exit value an exception is thrown. */ - public void exec(File workdir) throws IOException, InterruptedException { + public void exec(File workdir) throws IOException, InterruptedException, TimeoutException { System.out.println(">>> exec: "+this); ExternalProcess process = new ExternalProcess(workdir, this); System.out.println(process); diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalProcess.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalProcess.java index a5ee4d077..b1d5a0122 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalProcess.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalProcess.java @@ -16,6 +16,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** @@ -113,13 +116,14 @@ public class ExternalProcess { * Creates an external process and waits for it to terminate. The output and error streams * will be read and forwarded to System.out and System.err */ - public ExternalProcess(File workingDir, ExternalCommand cmd) throws IOException, InterruptedException { - this(workingDir, cmd, false); + public ExternalProcess(File workingDir, ExternalCommand cmd) throws IOException, InterruptedException, TimeoutException { + this(workingDir, cmd, false, null); } private void init(File workingDir, ExternalCommand cmd, - OutputStream outStream, OutputStream errStream) throws IOException, - InterruptedException { + OutputStream outStream, OutputStream errStream, + Duration timeout) throws IOException, + InterruptedException, TimeoutException { this.cmd = cmd; ProcessBuilder processBuilder = new ProcessBuilder(cmd.getProgramAndArgs()); processBuilder.directory(workingDir); @@ -127,15 +131,33 @@ public class ExternalProcess { process = processBuilder.start(); err = new StreamGobler(process.getErrorStream(), errStream); out = new StreamGobler(process.getInputStream(), outStream); - exitValue = process.waitFor(); - } - - public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException, InterruptedException { - if (captureStreams) { - init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream()); + if (timeout==null) { + exitValue = process.waitFor(); } else { - init(workingDir, cmd, System.out, System.err); + if (process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS)) { + exitValue = process.exitValue(); + } else { + process.destroy(); + exitValue = 999; //Set some non-0 value as that is what some callers might use to determine 'failure' occurred. + throw new TimeoutException("Command timed out: "+this); + } } + if (exitValue!=0) { + throw new IOException("Command execution failed:\n"+this); + } + } + + public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams, Duration timeout) throws IOException, InterruptedException, TimeoutException { + if (captureStreams) { + init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream(), timeout); + } else { + init(workingDir, cmd, System.out, System.err, timeout); + } + + } + + public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException, InterruptedException, TimeoutException { + this(workingDir, cmd, captureStreams, null); } public String getOut() throws InterruptedException { @@ -156,13 +178,13 @@ public class ExternalProcess { result.append("exitValue = "+exitValue+"\n"); String strOut = getOut(); if (strOut!=null) { - result.append("\n------- System.out -------\n"); + result.append("------- System.out -------\n"); result.append(strOut); } String strErr = getErr(); if (strErr!=null) { - result.append("\n------- System.err -------\n"); - result.append(getOut()); + result.append("------- System.err -------\n"); + result.append(strErr); } result.append("<<<< ExternalProcess"); return result.toString(); diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/LanguageId.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/LanguageId.java index 575a2ff1e..980fae8e3 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/LanguageId.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/LanguageId.java @@ -21,10 +21,12 @@ public class LanguageId { public static final LanguageId PLAINTEXT = of("plaintext"); public static final LanguageId CONCOURSE_TASK = of("concourse-task-yaml"); public static final LanguageId CONCOURSE_PIPELINE = of("concourse-pipeline-yaml"); - public static final LanguageId BOSH_DEPLOYMENT = of("bosh-deployment-manifest"); public static final LanguageId CF_MANIFEST = of("manifest-yaml"); public static final LanguageId JAVA = of("java"); public static final LanguageId YAML = of("yaml"); + + public static final LanguageId BOSH_DEPLOYMENT = of("bosh-deployment-manifest"); + public static final LanguageId BOSH_CLOUD_CONFIG = of("bosh-cloud-config"); private final String id; diff --git a/headless-services/commons/pom.xml b/headless-services/commons/pom.xml index b17f01c7e..9e311511e 100644 --- a/headless-services/commons/pom.xml +++ b/headless-services/commons/pom.xml @@ -77,6 +77,7 @@ 3.5.2 1.7.22 19.0 + 1.10.19 2.5.0 2.10 0.2.1 diff --git a/headless-services/manifest-yaml-language-server/pom.xml b/headless-services/manifest-yaml-language-server/pom.xml index 8f796ff9c..276cb6c62 100644 --- a/headless-services/manifest-yaml-language-server/pom.xml +++ b/headless-services/manifest-yaml-language-server/pom.xml @@ -57,7 +57,7 @@ org.mockito mockito-all - 1.10.19 + ${mockito-version} test