Commit 95f7e3ca authored by Dmytro Nosan's avatar Dmytro Nosan Committed by Stephane Nicoll

Add support for environment variables

See gh-12800
parent 5dd4a7e9
......@@ -2771,6 +2771,14 @@ See {spring-boot-maven-plugin-site}/examples/run-debug.html[this example] for mo
details.
[[howto-set-env-maven-run]]
=== Run Spring Boot Application with Environment variables Started with Maven
To set up the environment variables to a Spring Boot application that was started with Maven, you
can use the `environmentVariables` property of the {spring-boot-maven-plugin-site}[maven plugin].
See {spring-boot-maven-plugin-site}/examples/run-with-env.html[this example] for more
details.
[[howto-build-an-executable-archive-with-ant]]
=== Build an Executable Archive from Ant without Using `spring-boot-antlib`
......
......@@ -20,6 +20,8 @@ import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* Utility used to run a process.
......@@ -28,6 +30,7 @@ import java.util.Collection;
* @author Dave Syer
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Dmytro Nosan
* @since 1.1.0
*/
public class RunProcess {
......@@ -63,14 +66,19 @@ public class RunProcess {
}
public int run(boolean waitForProcess, String... args) throws IOException {
return run(waitForProcess, Arrays.asList(args));
return run(waitForProcess, Arrays.asList(args), Collections.emptyMap());
}
protected int run(boolean waitForProcess, Collection<String> args)
public int run(boolean waitForProcess, String[] args, Map<String, String> environmentVariables) throws IOException {
return run(waitForProcess, Arrays.asList(args), environmentVariables);
}
protected int run(boolean waitForProcess, Collection<String> args, Map<String, String> environmentVariables)
throws IOException {
ProcessBuilder builder = new ProcessBuilder(this.command);
builder.directory(this.workingDirectory);
builder.command().addAll(args);
builder.environment().putAll(environmentVariables);
builder.redirectErrorStream(true);
builder.inheritIO();
try {
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>run-envargs</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<environmentVariables>
<ENV1>5000</ENV1>
<ENV2>Some Text</ENV2>
<ENV3/>
<ENV4></ENV4>
</environmentVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
/*
* Copyright 2012-2018 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.test;
public class SampleApplication {
public static void main(String[] args) {
assertEnvValue("ENV1", "5000");
assertEnvValue("ENV2", "Some Text");
assertEnvValue("ENV3", "");
assertEnvValue("ENV4", "");
System.out.println("I haz been run");
}
static void assertEnvValue(String envKey, String expectedValue) {
String actual = System.getenv(envKey);
if (!expectedValue.equals(actual)) {
throw new IllegalStateException("env property [" + envKey + "] mismatch (got [" + actual + "], expected [" + expectedValue + "]");
}
}
}
def file = new File(basedir, "build.log")
return file.text.contains("I haz been run")
......@@ -24,6 +24,7 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -48,6 +49,7 @@ import org.springframework.boot.loader.tools.MainClassFinder;
* @author Stephane Nicoll
* @author David Liu
* @author Daniel Young
* @author Dmytro Nosan
* @see RunMojo
* @see StartMojo
*/
......@@ -115,6 +117,15 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
@Parameter
private Map<String, String> systemPropertyVariables;
/**
* List of Environment variables that should be associated with the forked process used to run the
* application.
* <p>NOTE: the use of Environment variables means that processes will be started by forking a
* new JVM.
*/
@Parameter(property = "spring-boot.run.environmentVariables")
private Map<String, String> environmentVariables;
/**
* Arguments that should be passed to the application. On command line use commas to
* separate multiple arguments.
......@@ -203,7 +214,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
* @see #logDisabledFork()
*/
protected boolean enableForkByDefault() {
return hasAgent() || hasJvmArgs() || hasWorkingDirectorySet();
return hasAgent() || hasJvmArgs() || hasEnvVariables() || hasWorkingDirectorySet();
}
private boolean hasAgent() {
......@@ -216,6 +227,11 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
&& !this.systemPropertyVariables.isEmpty());
}
private boolean hasEnvVariables() {
return (this.environmentVariables != null && !this.environmentVariables.isEmpty());
}
private boolean hasWorkingDirectorySet() {
return this.workingDirectory != null;
}
......@@ -257,22 +273,27 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
private void doRunWithForkedJvm(String startClassName)
throws MojoExecutionException, MojoFailureException {
List<String> args = new ArrayList<>();
Map<String, String> envVariables = new LinkedHashMap<>();
addAgents(args);
addJvmArgs(args);
addClasspath(args);
args.add(startClassName);
addArgs(args);
runWithForkedJvm(this.workingDirectory, args);
addEnvironmentVariables(envVariables);
runWithForkedJvm(this.workingDirectory, args, envVariables);
}
/**
* Run with a forked VM, using the specified command line arguments.
* @param workingDirectory the working directory of the forked JVM
* @param args the arguments (JVM arguments and application arguments)
* @param environmentVariables the environment variables;
* @throws MojoExecutionException in case of MOJO execution errors
* @throws MojoFailureException in case of MOJO failures
*/
protected abstract void runWithForkedJvm(File workingDirectory, List<String> args)
protected abstract void runWithForkedJvm(File workingDirectory, List<String> args,
Map<String, String> environmentVariables)
throws MojoExecutionException, MojoFailureException;
/**
......@@ -295,12 +316,29 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
return runArguments;
}
/**
* Resolve the environment variables to use.
*
* @return a {@link EnvVariables} defining the environment variables
*/
protected EnvVariables resolveEnvVariables() {
return new EnvVariables(this.environmentVariables);
}
private void addArgs(List<String> args) {
RunArguments applicationArguments = resolveApplicationArguments();
Collections.addAll(args, applicationArguments.asArray());
logArguments("Application argument(s): ", this.arguments);
}
private void addEnvironmentVariables(Map<String, String> environmentVariables) {
EnvVariables envVariables = resolveEnvVariables();
environmentVariables.putAll(envVariables.asMap());
logArguments("Environment variable(s): ", envVariables.asArray());
}
/**
* Resolve the JVM arguments to use.
* @return a {@link RunArguments} defining the JVM arguments
......
/*
* Copyright 2012-2018 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.boot.maven;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Utility class for working with Env variables.
*
* @author Dmytro Nosan
*/
class EnvVariables {
private static final String SPACE = "=";
private static final String NO_VALUE = "";
private final Map<String, String> args = new LinkedHashMap<>();
EnvVariables(Map<String, String> args) {
this.args.putAll(getArgs(args));
}
Map<String, String> asMap() {
return Collections.unmodifiableMap(this.args);
}
String[] asArray() {
List<String> args = new ArrayList<>(this.args.size());
for (Map.Entry<String, String> arg : this.args.entrySet()) {
args.add(arg.getKey() + SPACE + arg.getValue());
}
return args.toArray(new String[args.size()]);
}
private Map<String, String> getArgs(Map<String, String> args) {
if (args == null || args.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> result = new LinkedHashMap<>();
for (Map.Entry<String, String> e : args.entrySet()) {
if (hasText(e.getKey())) {
result.put(e.getKey(), getValue(e.getValue()));
}
}
return result;
}
private String getValue(String value) {
if (hasText(value)) {
return value;
}
return NO_VALUE;
}
private boolean hasText(String source) {
return source != null && !source.trim().isEmpty();
}
}
......@@ -20,6 +20,7 @@ import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.Map;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Execute;
......@@ -34,6 +35,7 @@ import org.springframework.boot.loader.tools.RunProcess;
* Run an executable archive application.
*
* @author Phillip Webb
* @author Dmytro Nosan
* @author Stephane Nicoll
* @author Andy Wilkinson
*/
......@@ -64,14 +66,14 @@ public class RunMojo extends AbstractRunMojo {
}
@Override
protected void runWithForkedJvm(File workingDirectory, List<String> args)
protected void runWithForkedJvm(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
throws MojoExecutionException {
try {
RunProcess runProcess = new RunProcess(workingDirectory,
new JavaExecutable().toString());
Runtime.getRuntime()
.addShutdownHook(new Thread(new RunProcessKiller(runProcess)));
int exitCode = runProcess.run(true, args.toArray(new String[0]));
int exitCode = runProcess.run(true, args.toArray(new String[0]), environmentVariables);
if (exitCode == 0 || exitCode == EXIT_CODE_SIGINT) {
return;
}
......
......@@ -23,6 +23,7 @@ import java.net.ConnectException;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.management.MBeanServerConnection;
......@@ -46,6 +47,7 @@ import org.springframework.boot.loader.tools.RunProcess;
* stopped after.
*
* @author Stephane Nicoll
* @author Dmytro Nosan
* @since 1.3.0
* @see StopMojo
*/
......@@ -88,9 +90,9 @@ public class StartMojo extends AbstractRunMojo {
private final Object lock = new Object();
@Override
protected void runWithForkedJvm(File workingDirectory, List<String> args)
protected void runWithForkedJvm(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
throws MojoExecutionException, MojoFailureException {
RunProcess runProcess = runProcess(workingDirectory, args);
RunProcess runProcess = runProcess(workingDirectory, args, environmentVariables);
try {
waitForSpringApplication();
}
......@@ -100,12 +102,12 @@ public class StartMojo extends AbstractRunMojo {
}
}
private RunProcess runProcess(File workingDirectory, List<String> args)
private RunProcess runProcess(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
throws MojoExecutionException {
try {
RunProcess runProcess = new RunProcess(workingDirectory,
new JavaExecutable().toString());
runProcess.run(false, args.toArray(new String[0]));
runProcess.run(false, args.toArray(new String[0]), environmentVariables);
return runProcess;
}
catch (Exception ex) {
......
-----
Specify environment variables
-----
Dmytro Nosan
-----
2018-04-08
-----
The environmnet variables to use for a particular application can be specified using the <<<environmentVariables>>>
argument. The following configuration enables the 'ENV1', 'ENV2', 'ENV3', 'ENV4' env variables:
---
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<configuration>
<environmentVariables>
<ENV1>5000</ENV1>
<ENV2>Some Text</ENV2>
<ENV3/>
<ENV4></ENV4>
</environmentVariables>
</configuration>
...
</plugin>
...
</plugins>
...
</build>
...
</project>
---
Note that since you specified some Environment variables, the process is forked automatically.
......@@ -60,6 +60,8 @@ Spring Boot Maven Plugin
* {{{./examples/run-profiles.html}Specify active profiles}}
* {{{./examples/run-with-env.html}Specify Environment variables}}
* {{{./examples/build-info.html}Generate build information}}
* {{{./examples/custom-layout.html}Custom layout}}
......
......@@ -135,14 +135,15 @@ mvn spring-boot:run
By default the application is executed directly from the Maven JVM. If you need to run
in a forked process you can use the 'fork' option. Forking will also occur if the
'jvmArguments', 'systemPropertyVariables' or 'agent' options are specified, or if
devtools is present.
'jvmArguments', 'systemPropertyVariables', 'environmentVariables' or 'agent' options
are specified, or if devtools is present.
If you need to specify some JVM arguments (i.e. for debugging purposes), you can use
the <<<jvmArguments>>> parameter, see {{{./examples/run-debug.html}Debug the application}}
for more details. There is also explicit support
{{{./examples/run-system-properties.html}for system properties}}. As a convenience, the
profiles to enable are handled by a specific property (<<<profiles>>>), see
{{{./examples/run-system-properties.html}for system properties}} and
{{{./examples/run-with-env.html}environment variables}}. As a convenience, the profiles
to enable are handled by a specific property (<<<profiles>>>), see
{{{./examples/run-profiles.html}Specify active profiles}}.
Spring Boot 1.3 has introduced <<<devtools>>>, a module to improve the development-time
......
......@@ -15,6 +15,7 @@
<item name="Random port for integration tests" href="examples/it-random-port.html"/>
<item name="Skip integration tests" href="examples/it-skip.html"/>
<item name="Specify active profiles" href="examples/run-profiles.html"/>
<item name="Specify environment variables" href="examples/run-with-env.html"/>
<item name="Generate build information" href="examples/build-info.html"/>
<item name="Custom layout" href="examples/custom-layout.html"/>
</menu>
......
/*
* Copyright 2012-2018 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.boot.maven;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link EnvVariables}.
*
* @author Dmytro Nosan
*/
public class EnvVariablesTests {
@Test
public void asNull() {
Map<String, String> args = new EnvVariables(null).asMap();
assertThat(args).hasSize(0);
}
@Test
public void asArray() {
assertThat(new EnvVariables(getTestArgs()).asArray())
.contains("key=My Value")
.contains("key1= tt ")
.contains("key2=")
.contains("key3=");
}
@Test
public void asMap() {
assertThat(new EnvVariables(getTestArgs()).asMap())
.containsEntry("key", "My Value")
.containsEntry("key1", " tt ")
.containsEntry("key2", "")
.containsEntry("key3", "");
}
private Map<String, String> getTestArgs() {
Map<String, String> args = new LinkedHashMap<>();
args.put("key", "My Value");
//should not be trimmed
args.put("key1", " tt ");
args.put("key2", " ");
args.put("key3", null);
return args;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment