diff --git a/spring-cloud-launcher/pom.xml b/spring-cloud-launcher/pom.xml index e0ec992..858c1a8 100644 --- a/spring-cloud-launcher/pom.xml +++ b/spring-cloud-launcher/pom.xml @@ -18,10 +18,10 @@ + spring-cloud-launcher-deployer spring-cloud-launcher-cli spring-cloud-launcher-configserver spring-cloud-launcher-dataflow - spring-cloud-launcher-deployer spring-cloud-launcher-eureka spring-cloud-launcher-h2 spring-cloud-launcher-hystrixdashboard diff --git a/spring-cloud-launcher/spring-cloud-launcher-cli/src/main/java/org/springframework/cloud/launcher/cli/LauncherCommand.java b/spring-cloud-launcher/spring-cloud-launcher-cli/src/main/java/org/springframework/cloud/launcher/cli/LauncherCommand.java index 461ab81..e10c9a0 100644 --- a/spring-cloud-launcher/spring-cloud-launcher-cli/src/main/java/org/springframework/cloud/launcher/cli/LauncherCommand.java +++ b/spring-cloud-launcher/spring-cloud-launcher-cli/src/main/java/org/springframework/cloud/launcher/cli/LauncherCommand.java @@ -58,13 +58,16 @@ public class LauncherCommand extends OptionParsingCommand { static { EXAMPLES.add(new HelpExample("Launch Eureka", "spring cloud eureka")); - EXAMPLES.add(new HelpExample("Launch Config Server and Eureka", "spring cloud configserver eureka")); + EXAMPLES.add(new HelpExample("Launch Config Server and Eureka", + "spring cloud configserver eureka")); EXAMPLES.add(new HelpExample("List deployable apps", "spring cloud --list")); - EXAMPLES.add(new HelpExample("Launch Config Server with git repo", "spring cloud --git-uri=http://example.com/proj.git configserver")); + EXAMPLES.add(new HelpExample("Launch Config Server with git repo", + "spring cloud --git-uri=http://example.com/proj.git configserver")); } public LauncherCommand() { - super("cloud", "Start Spring Cloud services, like Eureka, Config Server, etc.", new LauncherOptionHandler()); + super("cloud", "Start Spring Cloud services, like Eureka, Config Server, etc.", + new LauncherOptionHandler()); } @Override @@ -79,10 +82,14 @@ public class LauncherCommand extends OptionParsingCommand { @Override protected void options() { - // if the classloader is loaded here, we could load a collection of interfaces that - // can create options and then populate the args[] that is sent to the DeployerThread - this.debugOption = option(Arrays.asList("debug", "d"), "Debug logging for the deployer"); - this.listOption = option(Arrays.asList("list", "l"), "List the deployables (don't launch anything)"); + // if the classloader is loaded here, we could load a collection of interfaces + // that + // can create options and then populate the args[] that is sent to the + // DeployerThread + this.debugOption = option(Arrays.asList("debug", "d"), + "Debug logging for the deployer"); + this.listOption = option(Arrays.asList("list", "l"), + "List the deployables (don't launch anything)"); } @Override @@ -94,8 +101,10 @@ public class LauncherCommand extends OptionParsingCommand { String name = "org.springframework.cloud.launcher.deployer.DeployerThread"; Class threadClass = classLoader.loadClass(name); - Constructor constructor = threadClass.getConstructor(ClassLoader.class, String[].class); - Thread thread = (Thread) constructor.newInstance(classLoader, getArgs(options)); + Constructor constructor = threadClass.getConstructor(ClassLoader.class, + String[].class); + Thread thread = (Thread) constructor.newInstance(classLoader, + getArgs(options)); thread.start(); thread.join(); } @@ -112,12 +121,14 @@ public class LauncherCommand extends OptionParsingCommand { int sourceArgCount = 0; for (Object option : options.nonOptionArguments()) { if (option instanceof String) { - String filename = (String) option; - if ("--".equals(filename)) { - break; - } sourceArgCount++; - apps.add(option.toString()); + if (option.toString().startsWith("--")) { + // jopts makes all args after "--" non-options + args.add(option.toString()); + } + else { + apps.add(option.toString()); + } } } if (options.has(this.debugOption)) { @@ -128,22 +139,27 @@ public class LauncherCommand extends OptionParsingCommand { } else { if (!apps.isEmpty()) { - args.add("--launcher.deploy=" + StringUtils.collectionToCommaDelimitedString(apps)); + args.add("--launcher.deploy=" + + StringUtils.collectionToCommaDelimitedString(apps)); } } - args.addAll(options.nonOptionArguments().subList(sourceArgCount, options.nonOptionArguments().size())); + args.addAll(options.nonOptionArguments().subList(sourceArgCount, + options.nonOptionArguments().size())); return args.toArray(new String[args.size()]); } - private URLClassLoader populateClassloader(OptionSet options) throws MalformedURLException { + private URLClassLoader populateClassloader(OptionSet options) + throws MalformedURLException { DependencyResolutionContext resolutionContext = new DependencyResolutionContext(); - GroovyClassLoader loader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), + GroovyClassLoader loader = new GroovyClassLoader( + Thread.currentThread().getContextClassLoader(), new CompilerConfiguration()); List repositoryConfiguration = RepositoryConfigurationFactory .createDefaultRepositoryConfiguration(); - repositoryConfiguration.add(0, new RepositoryConfiguration("local", new File("repository").toURI(), true)); + repositoryConfiguration.add(0, new RepositoryConfiguration("local", + new File("repository").toURI(), true)); String[] classpaths = { "." }; for (String classpath : classpaths) { @@ -154,8 +170,8 @@ public class LauncherCommand extends OptionParsingCommand { System.setProperty("groovy.grape.report.downloads", "true"); } - AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(loader, repositoryConfiguration, - resolutionContext); + AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(loader, + repositoryConfiguration, resolutionContext); HashMap dependency = new HashMap<>(); dependency.put("group", "org.springframework.cloud.launcher"); @@ -171,7 +187,8 @@ public class LauncherCommand extends OptionParsingCommand { private String getVersion() { Package pkg = LauncherCommand.class.getPackage(); - return (pkg != null ? pkg.getImplementationVersion() : DEFAULT_VERSION); + String version = (pkg != null ? pkg.getImplementationVersion() : DEFAULT_VERSION); + return version != null ? version : DEFAULT_VERSION; } } diff --git a/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/java/org/springframework/cloud/launcher/cli/LauncherCommandTests.java b/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/java/org/springframework/cloud/launcher/cli/LauncherCommandTests.java index b6ea09e..3afaddf 100644 --- a/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/java/org/springframework/cloud/launcher/cli/LauncherCommandTests.java +++ b/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/java/org/springframework/cloud/launcher/cli/LauncherCommandTests.java @@ -16,7 +16,10 @@ package org.springframework.cloud.launcher.cli; -import org.junit.Ignore; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; + +import org.junit.Rule; import org.junit.Test; /** @@ -24,10 +27,18 @@ import org.junit.Test; */ public class LauncherCommandTests { + @Rule + public OutputCapture output = new OutputCapture(); + @Test - @Ignore - public void testLoadDeployer() throws Exception { - //new LauncherCommand().populateClassloader(); - new LauncherCommand().run(); + public void testCreateClassLoaderAndListDeployables() throws Exception { + new LauncherCommand().run("--list"); + assertThat(output.toString(), containsString("configserver")); + } + + @Test + public void testNonOptionArgsPassedDown() throws Exception { + new LauncherCommand().run("--list", "--", "--spring.profiles.active=test"); + assertThat(output.toString(), containsString("foo")); } } diff --git a/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/java/org/springframework/cloud/launcher/cli/OutputCapture.java b/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/java/org/springframework/cloud/launcher/cli/OutputCapture.java new file mode 100644 index 0000000..fb83384 --- /dev/null +++ b/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/java/org/springframework/cloud/launcher/cli/OutputCapture.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed 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. + */ + +package org.springframework.cloud.launcher.cli; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * Capture output from System.out and System.err. + * + * @author Phillip Webb + */ +public class OutputCapture implements TestRule { + + private CaptureOutputStream captureOut; + + private CaptureOutputStream captureErr; + + private ByteArrayOutputStream copy; + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + captureOutput(); + try { + base.evaluate(); + } + finally { + releaseOutput(); + } + } + }; + } + + protected void captureOutput() { + this.copy = new ByteArrayOutputStream(); + this.captureOut = new CaptureOutputStream(System.out, this.copy); + this.captureErr = new CaptureOutputStream(System.err, this.copy); + System.setOut(new PrintStream(this.captureOut)); + System.setErr(new PrintStream(this.captureErr)); + } + + protected void releaseOutput() { + System.setOut(this.captureOut.getOriginal()); + System.setErr(this.captureErr.getOriginal()); + this.copy = null; + } + + public void flush() { + try { + this.captureOut.flush(); + this.captureErr.flush(); + } + catch (IOException ex) { + // ignore + } + } + + @Override + public String toString() { + flush(); + return this.copy.toString(); + } + + private static class CaptureOutputStream extends OutputStream { + + private final PrintStream original; + + private final OutputStream copy; + + CaptureOutputStream(PrintStream original, OutputStream copy) { + this.original = original; + this.copy = copy; + } + + @Override + public void write(int b) throws IOException { + this.copy.write(b); + this.original.write(b); + this.original.flush(); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + this.copy.write(b, off, len); + this.original.write(b, off, len); + } + + public PrintStream getOriginal() { + return this.original; + } + + @Override + public void flush() throws IOException { + this.copy.flush(); + this.original.flush(); + } + } + +} diff --git a/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/resources/cloud-test.yml b/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/resources/cloud-test.yml new file mode 100644 index 0000000..f2911d3 --- /dev/null +++ b/spring-cloud-launcher/spring-cloud-launcher-cli/src/test/resources/cloud-test.yml @@ -0,0 +1,9 @@ +spring: + cloud: + launcher: + deployables: + - name: foo + coordinates: com.example:foo:0.0.1-SNAPSHOT + port: 8000 + waitUntilStarted: true + order: -200 diff --git a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerProperties.java b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerProperties.java index b0cc736..1d0e8e6 100644 --- a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerProperties.java +++ b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerProperties.java @@ -77,8 +77,8 @@ public class DeployerProperties { public String toString() { final StringBuffer sb = new StringBuffer("DeployerProperties{"); sb.append("deployables=").append(this.deployables); - sb.append("deploy=").append(this.deploy); - sb.append("statusSleepMillis=").append(this.statusSleepMillis); + sb.append(", deploy=").append(this.deploy); + sb.append(", statusSleepMillis=").append(this.statusSleepMillis); sb.append('}'); return sb.toString(); } diff --git a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerThread.java b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerThread.java index 68667d9..c74e2cb 100644 --- a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerThread.java +++ b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerThread.java @@ -29,8 +29,8 @@ import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.Banner.Mode; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.boot.logging.LogLevel; @@ -47,7 +47,6 @@ import org.springframework.core.OrderComparator; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; -import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; @@ -93,19 +92,7 @@ public class DeployerThread extends Thread { } private void list() { - StandardEnvironment environment = new StandardEnvironment(); - loadCloudProperties(environment); - DeployerProperties properties = new DeployerProperties(); - PropertiesConfigurationFactory factory = new PropertiesConfigurationFactory<>( - properties); - factory.setTargetName("spring.cloud.launcher"); - factory.setPropertySources(environment.getPropertySources()); - try { - factory.afterPropertiesSet(); - properties = factory.getObject(); - } - catch (Exception e) { - } + DeployerProperties properties = loadCloudProperties(); if (!properties.getDeployables().isEmpty()) { Collection names = new ArrayList<>(); for (Deployable deployable : properties.getDeployables()) { @@ -115,11 +102,20 @@ public class DeployerThread extends Thread { } } - private void loadCloudProperties(StandardEnvironment environment) { - String path = "/cloud.yml"; - PropertySource source = extractPropertySource(path); - if (source != null) { - environment.getPropertySources().addLast(source); + private DeployerProperties loadCloudProperties() { + + final ConfigurableApplicationContext context = new SpringApplicationBuilder( + PropertyPlaceholderAutoConfiguration.class, DeployerConfiguration.class) + .bannerMode(Mode.OFF).logStartupInfo(false).web(false) + .properties("spring.config.name=cloud", "logging.level.ROOT=OFF", + "spring.cloud.launcher.list=true", + "launcher.version=" + getVersion()) + .run(this.args); + try { + return context.getBean(DeployerProperties.class); + } + finally { + context.close(); } } @@ -180,8 +176,10 @@ public class DeployerThread extends Thread { final ConfigurableApplicationContext context = new SpringApplicationBuilder( PropertyPlaceholderAutoConfiguration.class, DeployerConfiguration.class) - .web(false).properties("spring.config.name=cloud", - "banner.location=launcher-banner.txt", "launcher.version="+getVersion()) + .web(false) + .properties("spring.config.name=cloud", + "banner.location=launcher-banner.txt", + "launcher.version=" + getVersion()) .run(this.args); final AppDeployer deployer = context.getBean(AppDeployer.class);