mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Remove support for embedded launch scripts
Closes gh-47666
This commit is contained in:
-32
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version-spring-boot}'
|
||||
}
|
||||
|
||||
tasks.named("bootJar") {
|
||||
mainClass = 'com.example.ExampleApplication'
|
||||
}
|
||||
|
||||
// tag::custom-launch-script[]
|
||||
tasks.named("bootJar") {
|
||||
launchScript {
|
||||
script = file('src/custom.script')
|
||||
}
|
||||
}
|
||||
// end::custom-launch-script[]
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
||||
|
||||
plugins {
|
||||
java
|
||||
id("org.springframework.boot") version "{version-spring-boot}"
|
||||
}
|
||||
|
||||
tasks.named<BootJar>("bootJar") {
|
||||
mainClass.set("com.example.ExampleApplication")
|
||||
}
|
||||
|
||||
// tag::custom-launch-script[]
|
||||
tasks.named<BootJar>("bootJar") {
|
||||
launchScript {
|
||||
script = file("src/custom.script")
|
||||
}
|
||||
}
|
||||
// end::custom-launch-script[]
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version-spring-boot}'
|
||||
}
|
||||
|
||||
tasks.named("bootJar") {
|
||||
mainClass = 'com.example.ExampleApplication'
|
||||
}
|
||||
|
||||
// tag::include-launch-script[]
|
||||
tasks.named("bootJar") {
|
||||
launchScript()
|
||||
}
|
||||
// end::include-launch-script[]
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
||||
|
||||
plugins {
|
||||
java
|
||||
id("org.springframework.boot") version "{version-spring-boot}"
|
||||
}
|
||||
|
||||
tasks.named<BootJar>("bootJar") {
|
||||
mainClass.set("com.example.ExampleApplication")
|
||||
}
|
||||
|
||||
// tag::include-launch-script[]
|
||||
tasks.named<BootJar>("bootJar") {
|
||||
launchScript()
|
||||
}
|
||||
// end::include-launch-script[]
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version-spring-boot}'
|
||||
}
|
||||
|
||||
tasks.named("bootJar") {
|
||||
mainClass = 'com.example.ExampleApplication'
|
||||
}
|
||||
|
||||
// tag::launch-script-properties[]
|
||||
tasks.named("bootJar") {
|
||||
launchScript {
|
||||
properties 'logFilename': 'example-app.log'
|
||||
}
|
||||
}
|
||||
// end::launch-script-properties[]
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
||||
|
||||
plugins {
|
||||
java
|
||||
id("org.springframework.boot") version "{version-spring-boot}"
|
||||
}
|
||||
|
||||
tasks.named<BootJar>("bootJar") {
|
||||
mainClass.set("com.example.ExampleApplication")
|
||||
}
|
||||
|
||||
// tag::launch-script-properties[]
|
||||
tasks.named<BootJar>("bootJar") {
|
||||
launchScript {
|
||||
properties(mapOf("logFilename" to "example-app.log"))
|
||||
}
|
||||
}
|
||||
// end::launch-script-properties[]
|
||||
-69
@@ -246,75 +246,6 @@ The closure is passed a `FileTreeElement` and should return a `boolean` indicati
|
||||
|
||||
|
||||
|
||||
[[packaging-executable.configuring.launch-script]]
|
||||
=== Making an Archive Fully Executable
|
||||
|
||||
Spring Boot provides support for fully executable archives.
|
||||
An archive is made fully executable by prepending a shell script that knows how to launch the application.
|
||||
On Unix-like platforms, this launch script allows the archive to be run directly like any other executable or to be installed as a service.
|
||||
|
||||
NOTE: Currently, some tools do not accept this format so you may not always be able to use this technique.
|
||||
For example, `jar -xf` may silently fail to extract a jar or war that has been made fully-executable.
|
||||
It is recommended that you only enable this option if you intend to execute it directly, rather than running it with `java -jar` or deploying it to a servlet container.
|
||||
|
||||
To use this feature, the inclusion of the launch script must be enabled:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Groovy::
|
||||
+
|
||||
[source,groovy,indent=0,subs="verbatim,attributes"]
|
||||
----
|
||||
include::example$packaging/boot-jar-include-launch-script.gradle[tags=include-launch-script]
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,attributes"]
|
||||
----
|
||||
include::example$packaging/boot-jar-include-launch-script.gradle.kts[tags=include-launch-script]
|
||||
----
|
||||
======
|
||||
|
||||
This will add Spring Boot's default launch script to the archive.
|
||||
The default launch script includes several properties with sensible default values.
|
||||
The values can be customized using the `properties` property:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Groovy::
|
||||
+
|
||||
[source,groovy,indent=0,subs="verbatim,attributes"]
|
||||
----
|
||||
include::example$packaging/boot-jar-launch-script-properties.gradle[tags=launch-script-properties]
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,attributes"]
|
||||
----
|
||||
include::example$packaging/boot-jar-launch-script-properties.gradle.kts[tags=launch-script-properties]
|
||||
----
|
||||
======
|
||||
|
||||
If the default launch script does not meet your needs, the `script` property can be used to provide a custom launch script:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Groovy::
|
||||
+
|
||||
[source,groovy,indent=0,subs="verbatim,attributes"]
|
||||
----
|
||||
include::example$packaging/boot-jar-custom-launch-script.gradle[tags=custom-launch-script]
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,attributes"]
|
||||
----
|
||||
include::example$packaging/boot-jar-custom-launch-script.gradle.kts[tags=custom-launch-script]
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
[[packaging-executable.configuring.properties-launcher]]
|
||||
=== Using the PropertiesLauncher
|
||||
|
||||
|
||||
-24
@@ -18,7 +18,6 @@ package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
@@ -30,7 +29,6 @@ import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -65,28 +63,6 @@ public interface BootArchive extends Task {
|
||||
*/
|
||||
void requiresUnpack(Spec<FileTreeElement> spec);
|
||||
|
||||
/**
|
||||
* Returns the {@link LaunchScriptConfiguration} that will control the script that is
|
||||
* prepended to the archive.
|
||||
* @return the launch script configuration, or {@code null} if the launch script has
|
||||
* not been configured.
|
||||
*/
|
||||
@Nested
|
||||
@Optional
|
||||
@Nullable LaunchScriptConfiguration getLaunchScript();
|
||||
|
||||
/**
|
||||
* Configures the archive to have a prepended launch script.
|
||||
*/
|
||||
void launchScript();
|
||||
|
||||
/**
|
||||
* Configures the archive to have a prepended launch script, customizing its
|
||||
* configuration using the given {@code action}.
|
||||
* @param action the action to apply
|
||||
*/
|
||||
void launchScript(Action<LaunchScriptConfiguration> action);
|
||||
|
||||
/**
|
||||
* Returns the classpath that will be included in the archive.
|
||||
* @return the classpath
|
||||
|
||||
+2
-13
@@ -75,8 +75,6 @@ class BootArchiveSupport {
|
||||
|
||||
private final Function<FileCopyDetails, ZipCompression> compressionResolver;
|
||||
|
||||
private @Nullable LaunchScriptConfiguration launchScript;
|
||||
|
||||
BootArchiveSupport(String loaderMainClass, Spec<FileCopyDetails> librarySpec,
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver) {
|
||||
this.loaderMainClass = loaderMainClass;
|
||||
@@ -129,13 +127,12 @@ class BootArchiveSupport {
|
||||
boolean includeDefaultLoader = isUsingDefaultLoader(jar);
|
||||
Spec<FileTreeElement> requiresUnpack = this.requiresUnpack.getAsSpec();
|
||||
Spec<FileTreeElement> exclusions = this.exclusions.getAsExcludeSpec();
|
||||
LaunchScriptConfiguration launchScript = this.launchScript;
|
||||
Spec<FileCopyDetails> librarySpec = this.librarySpec;
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver = this.compressionResolver;
|
||||
String encoding = jar.getMetadataCharset();
|
||||
CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirPermissions,
|
||||
filePermissions, includeDefaultLoader, jarmodeToolsLocation, requiresUnpack, exclusions, launchScript,
|
||||
librarySpec, compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver);
|
||||
filePermissions, includeDefaultLoader, jarmodeToolsLocation, requiresUnpack, exclusions, librarySpec,
|
||||
compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver);
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -175,14 +172,6 @@ class BootArchiveSupport {
|
||||
return DEFAULT_LAUNCHER_CLASSES.contains(jar.getManifest().getAttributes().get("Main-Class"));
|
||||
}
|
||||
|
||||
@Nullable LaunchScriptConfiguration getLaunchScript() {
|
||||
return this.launchScript;
|
||||
}
|
||||
|
||||
void setLaunchScript(LaunchScriptConfiguration launchScript) {
|
||||
this.launchScript = launchScript;
|
||||
}
|
||||
|
||||
void requiresUnpack(String... patterns) {
|
||||
this.requiresUnpack.include(patterns);
|
||||
}
|
||||
|
||||
-24
@@ -166,21 +166,6 @@ public abstract class BootJar extends Jar implements BootArchive {
|
||||
this.support.requiresUnpack(spec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LaunchScriptConfiguration getLaunchScript() {
|
||||
return this.support.getLaunchScript();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchScript() {
|
||||
enableLaunchScriptIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchScript(Action<LaunchScriptConfiguration> action) {
|
||||
action.execute(enableLaunchScriptIfNecessary());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the spec that describes the layers in a layered jar.
|
||||
* @return the spec for the layers
|
||||
@@ -273,15 +258,6 @@ public abstract class BootJar extends Jar implements BootArchive {
|
||||
return path.startsWith(LIB_DIRECTORY);
|
||||
}
|
||||
|
||||
private LaunchScriptConfiguration enableLaunchScriptIfNecessary() {
|
||||
LaunchScriptConfiguration launchScript = this.support.getLaunchScript();
|
||||
if (launchScript == null) {
|
||||
launchScript = new LaunchScriptConfiguration(this);
|
||||
this.support.setLaunchScript(launchScript);
|
||||
}
|
||||
return launchScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
|
||||
-24
@@ -140,21 +140,6 @@ public abstract class BootWar extends War implements BootArchive {
|
||||
this.support.requiresUnpack(spec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LaunchScriptConfiguration getLaunchScript() {
|
||||
return this.support.getLaunchScript();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchScript() {
|
||||
enableLaunchScriptIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchScript(Action<LaunchScriptConfiguration> action) {
|
||||
action.execute(enableLaunchScriptIfNecessary());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provided classpath, the contents of which will be included in the
|
||||
* {@code WEB-INF/lib-provided} directory of the war.
|
||||
@@ -241,15 +226,6 @@ public abstract class BootWar extends War implements BootArchive {
|
||||
return path.startsWith(LIB_DIRECTORY) || path.startsWith(LIB_PROVIDED_DIRECTORY);
|
||||
}
|
||||
|
||||
private LaunchScriptConfiguration enableLaunchScriptIfNecessary() {
|
||||
LaunchScriptConfiguration launchScript = this.support.getLaunchScript();
|
||||
if (launchScript == null) {
|
||||
launchScript = new LaunchScriptConfiguration(this);
|
||||
this.support.setLaunchScript(launchScript);
|
||||
}
|
||||
return launchScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
|
||||
+1
-22
@@ -54,7 +54,6 @@ import org.gradle.util.GradleVersion;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.boot.gradle.tasks.bundling.ResolvedDependencies.DependencyDescriptor;
|
||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||
import org.springframework.boot.loader.tools.FileUtils;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
@@ -102,8 +101,6 @@ class BootZipCopyAction implements CopyAction {
|
||||
|
||||
private final Spec<FileTreeElement> exclusions;
|
||||
|
||||
private final @Nullable LaunchScriptConfiguration launchScript;
|
||||
|
||||
private final Spec<FileCopyDetails> librarySpec;
|
||||
|
||||
private final Function<FileCopyDetails, ZipCompression> compressionResolver;
|
||||
@@ -118,8 +115,7 @@ class BootZipCopyAction implements CopyAction {
|
||||
|
||||
BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, @Nullable Integer dirMode,
|
||||
@Nullable Integer fileMode, boolean includeDefaultLoader, @Nullable String jarmodeToolsLocation,
|
||||
Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
|
||||
@Nullable LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec,
|
||||
Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions, Spec<FileCopyDetails> librarySpec,
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver, @Nullable String encoding,
|
||||
ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile,
|
||||
@Nullable LayerResolver layerResolver) {
|
||||
@@ -132,7 +128,6 @@ class BootZipCopyAction implements CopyAction {
|
||||
this.jarmodeToolsLocation = jarmodeToolsLocation;
|
||||
this.requiresUnpack = requiresUnpack;
|
||||
this.exclusions = exclusions;
|
||||
this.launchScript = launchScript;
|
||||
this.librarySpec = librarySpec;
|
||||
this.compressionResolver = compressionResolver;
|
||||
this.encoding = encoding;
|
||||
@@ -164,7 +159,6 @@ class BootZipCopyAction implements CopyAction {
|
||||
|
||||
private void writeArchive(CopyActionProcessingStream copyActions, OutputStream output) throws IOException {
|
||||
ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(output);
|
||||
writeLaunchScriptIfNecessary(zipOutput);
|
||||
try {
|
||||
setEncodingIfNecessary(zipOutput);
|
||||
Processor processor = new Processor(zipOutput);
|
||||
@@ -176,21 +170,6 @@ class BootZipCopyAction implements CopyAction {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLaunchScriptIfNecessary(ZipArchiveOutputStream outputStream) {
|
||||
if (this.launchScript == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
File file = this.launchScript.getScript();
|
||||
Map<String, String> properties = this.launchScript.getProperties();
|
||||
outputStream.writePreamble(new DefaultLaunchScript(file, properties).toByteArray());
|
||||
this.output.setExecutable(true);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new GradleException("Failed to write launch script to " + this.output, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEncodingIfNecessary(ZipArchiveOutputStream zipOutputStream) {
|
||||
if (this.encoding != null) {
|
||||
zipOutputStream.setEncoding(this.encoding);
|
||||
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.gradle.tasks.bundling;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates the configuration of the launch script for an executable jar or war.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class LaunchScriptConfiguration implements Serializable {
|
||||
|
||||
private static final Pattern WHITE_SPACE_PATTERN = Pattern.compile("\\s+");
|
||||
|
||||
private static final Pattern LINE_FEED_PATTERN = Pattern.compile("\n");
|
||||
|
||||
// We don't care about the order, but Gradle's configuration cache currently does.
|
||||
// https://github.com/gradle/gradle/pull/17863
|
||||
private final Map<String, String> properties = new TreeMap<>();
|
||||
|
||||
private @Nullable File script;
|
||||
|
||||
public LaunchScriptConfiguration() {
|
||||
}
|
||||
|
||||
LaunchScriptConfiguration(AbstractArchiveTask archiveTask) {
|
||||
Project project = archiveTask.getProject();
|
||||
String baseName = archiveTask.getArchiveBaseName().get();
|
||||
putIfMissing(this.properties, "initInfoProvides", baseName);
|
||||
putIfMissing(this.properties, "initInfoShortDescription", removeLineBreaks(project.getDescription()), baseName);
|
||||
putIfMissing(this.properties, "initInfoDescription", augmentLineBreaks(project.getDescription()), baseName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties that are applied to the launch script when it's being
|
||||
* including in the executable archive.
|
||||
* @return the properties
|
||||
*/
|
||||
@Input
|
||||
public Map<String, String> getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the properties that are applied to the launch script when it's being including
|
||||
* in the executable archive.
|
||||
* @param properties the properties
|
||||
*/
|
||||
public void properties(Map<String, String> properties) {
|
||||
this.properties.putAll(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the script {@link File} that will be included in the executable archive.
|
||||
* When {@code null}, the default launch script will be used.
|
||||
* @return the script file
|
||||
*/
|
||||
@Optional
|
||||
@InputFile
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public @Nullable File getScript() {
|
||||
return this.script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the script {@link File} that will be included in the executable archive. When
|
||||
* {@code null}, the default launch script will be used.
|
||||
* @param script the script file
|
||||
*/
|
||||
public void setScript(@Nullable File script) {
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
@Contract("!null -> !null")
|
||||
private @Nullable String removeLineBreaks(@Nullable String string) {
|
||||
return (string != null) ? WHITE_SPACE_PATTERN.matcher(string).replaceAll(" ") : null;
|
||||
}
|
||||
|
||||
@Contract("!null -> !null")
|
||||
private @Nullable String augmentLineBreaks(@Nullable String string) {
|
||||
return (string != null) ? LINE_FEED_PATTERN.matcher(string).replaceAll("\n# ") : null;
|
||||
}
|
||||
|
||||
private void putIfMissing(Map<String, String> properties, String key, @Nullable String... valueCandidates) {
|
||||
if (!properties.containsKey(key)) {
|
||||
for (String candidate : valueCandidates) {
|
||||
if (StringUtils.hasLength(candidate)) {
|
||||
properties.put(key, candidate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-33
@@ -18,8 +18,6 @@ package org.springframework.boot.gradle.docs;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.jar.JarEntry;
|
||||
@@ -34,7 +32,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.gradle.junit.GradleMultiDslExtension;
|
||||
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -128,36 +125,6 @@ class PackagingDocumentationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void bootJarIncludeLaunchScript() throws IOException {
|
||||
this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-include-launch-script").build("bootJar");
|
||||
File file = new File(this.gradleBuild.getProjectDir(),
|
||||
"build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar");
|
||||
assertThat(file).isFile();
|
||||
assertThat(FileCopyUtils.copyToString(new FileReader(file))).startsWith("#!/bin/bash");
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void bootJarLaunchScriptProperties() throws IOException {
|
||||
this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-launch-script-properties").build("bootJar");
|
||||
File file = new File(this.gradleBuild.getProjectDir(),
|
||||
"build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar");
|
||||
assertThat(file).isFile();
|
||||
assertThat(FileCopyUtils.copyToString(new FileReader(file))).contains("example-app.log");
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void bootJarCustomLaunchScript() throws IOException {
|
||||
File customScriptFile = new File(this.gradleBuild.getProjectDir(), "src/custom.script");
|
||||
customScriptFile.getParentFile().mkdirs();
|
||||
FileCopyUtils.copy("custom", new FileWriter(customScriptFile));
|
||||
this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-custom-launch-script").build("bootJar");
|
||||
File file = new File(this.gradleBuild.getProjectDir(),
|
||||
"build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar");
|
||||
assertThat(file).isFile();
|
||||
assertThat(FileCopyUtils.copyToString(new FileReader(file))).startsWith("custom");
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void bootWarPropertiesLauncher() throws IOException {
|
||||
this.gradleBuild.script(Examples.DIR + "packaging/boot-war-properties-launcher").build("bootWar");
|
||||
|
||||
-50
@@ -123,56 +123,6 @@ abstract class AbstractBootArchiveIntegrationTests {
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void upToDateWhenBuiltTwiceWithLaunchScriptIncluded() {
|
||||
BuildTask task = this.gradleBuild.build("-PincludeLaunchScript=true", this.taskName).task(":" + this.taskName);
|
||||
assertThat(task).isNotNull();
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
task = this.gradleBuild.build("-PincludeLaunchScript=true", this.taskName).task(":" + this.taskName);
|
||||
assertThat(task).isNotNull();
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded() {
|
||||
BuildTask task = this.gradleBuild.scriptProperty("launchScript", "")
|
||||
.build(this.taskName)
|
||||
.task(":" + this.taskName);
|
||||
assertThat(task).isNotNull();
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
task = this.gradleBuild.scriptProperty("launchScript", "launchScript()")
|
||||
.build(this.taskName)
|
||||
.task(":" + this.taskName);
|
||||
assertThat(task).isNotNull();
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded() {
|
||||
BuildTask task = this.gradleBuild.scriptProperty("launchScript", "launchScript()")
|
||||
.build(this.taskName)
|
||||
.task(":" + this.taskName);
|
||||
assertThat(task).isNotNull();
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
task = this.gradleBuild.scriptProperty("launchScript", "").build(this.taskName).task(":" + this.taskName);
|
||||
assertThat(task).isNotNull();
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void notUpToDateWhenLaunchScriptPropertyChanges() {
|
||||
BuildTask task = this.gradleBuild.scriptProperty("launchScriptProperty", "alpha")
|
||||
.build(this.taskName)
|
||||
.task(":" + this.taskName);
|
||||
assertThat(task).isNotNull();
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
task = this.gradleBuild.scriptProperty("launchScriptProperty", "bravo")
|
||||
.build(this.taskName)
|
||||
.task(":" + this.taskName);
|
||||
assertThat(task).isNotNull();
|
||||
assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void applicationPluginMainClassNameIsUsed() throws IOException {
|
||||
BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName);
|
||||
|
||||
-58
@@ -23,18 +23,12 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
@@ -66,7 +60,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.gradle.junit.GradleProjectBuilder;
|
||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
@@ -310,57 +303,6 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchScriptCanBePrepended() throws IOException {
|
||||
this.task.getMainClass().set("com.example.Main");
|
||||
this.task.launchScript();
|
||||
executeTask();
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("initInfoProvides", this.task.getArchiveBaseName().get());
|
||||
properties.put("initInfoShortDescription", this.project.getDescription());
|
||||
properties.put("initInfoDescription", this.project.getDescription());
|
||||
File archiveFile = this.task.getArchiveFile().get().getAsFile();
|
||||
assertThat(Files.readAllBytes(archiveFile.toPath()))
|
||||
.startsWith(new DefaultLaunchScript(null, properties).toByteArray());
|
||||
try (ZipFile zipFile = ZipFile.builder().setFile(archiveFile).get()) {
|
||||
assertThat(zipFile.getEntries().hasMoreElements()).isTrue();
|
||||
}
|
||||
try {
|
||||
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(archiveFile.toPath());
|
||||
assertThat(permissions).contains(PosixFilePermission.OWNER_EXECUTE);
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
// Windows, presumably. Continue
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void customLaunchScriptCanBePrepended() throws IOException {
|
||||
this.task.getMainClass().set("com.example.Main");
|
||||
File customScript = new File(this.temp, "custom.script");
|
||||
Files.writeString(customScript.toPath(), "custom script", StandardOpenOption.CREATE);
|
||||
this.task.launchScript((configuration) -> configuration.setScript(customScript));
|
||||
executeTask();
|
||||
Path path = this.task.getArchiveFile().get().getAsFile().toPath();
|
||||
assertThat(Files.readString(path, StandardCharsets.ISO_8859_1)).startsWith("custom script");
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchScriptInitInfoPropertiesCanBeCustomized() throws IOException {
|
||||
this.task.getMainClass().set("com.example.Main");
|
||||
this.task.launchScript((configuration) -> {
|
||||
configuration.getProperties().put("initInfoProvides", "provides");
|
||||
configuration.getProperties().put("initInfoShortDescription", "short description");
|
||||
configuration.getProperties().put("initInfoDescription", "description");
|
||||
});
|
||||
executeTask();
|
||||
Path path = this.task.getArchiveFile().get().getAsFile().toPath();
|
||||
String content = Files.readString(path, StandardCharsets.ISO_8859_1);
|
||||
assertThat(content).containsSequence("Provides: provides");
|
||||
assertThat(content).containsSequence("Short-Description: short description");
|
||||
assertThat(content).containsSequence("Description: description");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMainClassInTheManifestIsHonored() throws IOException {
|
||||
this.task.getMainClass().set("com.example.Main");
|
||||
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.gradle.tasks.bundling;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link LaunchScriptConfiguration}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class LaunchScriptConfigurationTests {
|
||||
|
||||
private final AbstractArchiveTask task = mock(AbstractArchiveTask.class);
|
||||
|
||||
private final Project project = mock(Project.class);
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
given(this.task.getProject()).willReturn(this.project);
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoProvidesUsesArchiveBaseNameByDefault() {
|
||||
Property<String> baseName = stringProperty("base-name");
|
||||
given(this.task.getArchiveBaseName()).willReturn(baseName);
|
||||
assertThat(new LaunchScriptConfiguration(this.task).getProperties()).containsEntry("initInfoProvides",
|
||||
"base-name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoShortDescriptionUsesDescriptionByDefault() {
|
||||
given(this.project.getDescription()).willReturn("Project description");
|
||||
Property<String> baseName = stringProperty("base-name");
|
||||
given(this.task.getArchiveBaseName()).willReturn(baseName);
|
||||
assertThat(new LaunchScriptConfiguration(this.task).getProperties()).containsEntry("initInfoShortDescription",
|
||||
"Project description");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoShortDescriptionUsesArchiveBaseNameWhenDescriptionIsNull() {
|
||||
Property<String> baseName = stringProperty("base-name");
|
||||
given(this.task.getArchiveBaseName()).willReturn(baseName);
|
||||
assertThat(new LaunchScriptConfiguration(this.task).getProperties()).containsEntry("initInfoShortDescription",
|
||||
"base-name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoShortDescriptionUsesSingleLineVersionOfMultiLineProjectDescription() {
|
||||
given(this.project.getDescription()).willReturn("Project\ndescription");
|
||||
Property<String> baseName = stringProperty("base-name");
|
||||
given(this.task.getArchiveBaseName()).willReturn(baseName);
|
||||
assertThat(new LaunchScriptConfiguration(this.task).getProperties()).containsEntry("initInfoShortDescription",
|
||||
"Project description");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoDescriptionUsesArchiveBaseNameWhenDescriptionIsNull() {
|
||||
Property<String> baseName = stringProperty("base-name");
|
||||
given(this.task.getArchiveBaseName()).willReturn(baseName);
|
||||
assertThat(new LaunchScriptConfiguration(this.task).getProperties()).containsEntry("initInfoDescription",
|
||||
"base-name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoDescriptionUsesProjectDescriptionByDefault() {
|
||||
given(this.project.getDescription()).willReturn("Project description");
|
||||
Property<String> baseName = stringProperty("base-name");
|
||||
given(this.task.getArchiveBaseName()).willReturn(baseName);
|
||||
assertThat(new LaunchScriptConfiguration(this.task).getProperties()).containsEntry("initInfoDescription",
|
||||
"Project description");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoDescriptionUsesCorrectlyFormattedMultiLineProjectDescription() {
|
||||
given(this.project.getDescription()).willReturn("The\nproject\ndescription");
|
||||
Property<String> baseName = stringProperty("base-name");
|
||||
given(this.task.getArchiveBaseName()).willReturn(baseName);
|
||||
assertThat(new LaunchScriptConfiguration(this.task).getProperties()).containsEntry("initInfoDescription",
|
||||
"The\n# project\n# description");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Property<String> stringProperty(String value) {
|
||||
Property<String> property = mock(Property.class);
|
||||
given(property.get()).willReturn(value);
|
||||
return property;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -41,7 +41,7 @@ import tools.jackson.databind.JacksonModule;
|
||||
|
||||
import org.springframework.asm.ClassVisitor;
|
||||
import org.springframework.boot.buildpack.platform.build.BuildRequest;
|
||||
import org.springframework.boot.loader.tools.LaunchScript;
|
||||
import org.springframework.boot.loader.tools.Layers;
|
||||
import org.springframework.boot.testsupport.BuildOutput;
|
||||
import org.springframework.boot.testsupport.gradle.testkit.Dsl;
|
||||
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
|
||||
@@ -80,7 +80,7 @@ public class PluginClasspathGradleBuild extends GradleBuild {
|
||||
classpath.add(new File("bin/main"));
|
||||
classpath.add(new File("build/classes/java/main"));
|
||||
classpath.add(new File("build/resources/main"));
|
||||
classpath.add(new File(pathOfJarContaining(LaunchScript.class)));
|
||||
classpath.add(new File(pathOfJarContaining(Layers.class)));
|
||||
classpath.add(new File(pathOfJarContaining(ClassVisitor.class)));
|
||||
classpath.add(new File(pathOfJarContaining(DependencyManagementPlugin.class)));
|
||||
if (this.kotlin) {
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
bootJar {
|
||||
mainClass = 'com.example.Application'
|
||||
launchScript {
|
||||
properties 'prop' : '{launchScriptProperty}'
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
bootJar {
|
||||
mainClass = 'com.example.Application'
|
||||
{launchScript}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
bootJar {
|
||||
mainClass = 'com.example.Application'
|
||||
{launchScript}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
bootJar {
|
||||
mainClass = 'com.example.Application'
|
||||
launchScript()
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'war'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
launchScript {
|
||||
properties 'prop' : '{launchScriptProperty}'
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'war'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
{launchScript}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'war'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
{launchScript}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'war'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
launchScript()
|
||||
}
|
||||
-20
@@ -160,15 +160,6 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void whenACustomLaunchScriptIsConfiguredItAppearsInTheRepackagedJar(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("jar-custom-launcher").goals("install").execute((project) -> {
|
||||
File repackaged = new File(project, "target/jar-0.0.1.BUILD-SNAPSHOT.jar");
|
||||
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/");
|
||||
assertThat(launchScript(repackaged)).contains("Hello world");
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void whenAnEntryIsExcludedItDoesNotAppearInTheRepackagedJar(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("jar-exclude-entry").goals("install").execute((project) -> {
|
||||
@@ -267,17 +258,6 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void whenAJarIsExecutableItBeginsWithTheDefaultLaunchScript(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("jar-executable").execute((project) -> {
|
||||
File repackaged = new File(project, "target/jar-executable-0.0.1.BUILD-SNAPSHOT.jar");
|
||||
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/");
|
||||
assertThat(launchScript(repackaged)).contains("Spring Boot Startup Script")
|
||||
.contains("MyFullyExecutableJarName")
|
||||
.contains("MyFullyExecutableJarDesc");
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void whenAJarIsBuiltWithLibrariesWithConflictingNamesTheyAreMadeUniqueUsingTheirGroupIds(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("jar-lib-name-conflict").execute((project) -> {
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.boot.maven.it</groupId>
|
||||
<artifactId>jar</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>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<embeddedLaunchScript>${basedir}/src/launcher/custom.script</embeddedLaunchScript>
|
||||
<embeddedLaunchScriptProperties>
|
||||
<name>world</name>
|
||||
</embeddedLaunchScriptProperties>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Hello {{name}}"
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.boot.maven.it</groupId>
|
||||
<artifactId>jar-executable</artifactId>
|
||||
<name>MyFullyExecutableJarName</name>
|
||||
<description>MyFullyExecutableJarDesc</description>
|
||||
<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>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>true</executable>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>@maven-jar-plugin.version@</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>some.random.Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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) {
|
||||
}
|
||||
|
||||
}
|
||||
+1
-65
@@ -20,7 +20,6 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -36,13 +35,10 @@ import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||
import org.apache.maven.project.MavenProjectHelper;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||
import org.springframework.boot.loader.tools.LaunchScript;
|
||||
import org.springframework.boot.loader.tools.LayoutFactory;
|
||||
import org.springframework.boot.loader.tools.Libraries;
|
||||
import org.springframework.boot.loader.tools.Repackager;
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Repackage existing JAR and WAR archives so that they can be executed from the command
|
||||
@@ -122,35 +118,6 @@ public class RepackageMojo extends AbstractPackagerMojo {
|
||||
@Parameter
|
||||
private @Nullable List<Dependency> requiresUnpack;
|
||||
|
||||
/**
|
||||
* Make a fully executable jar for *nix machines by prepending a launch script to the
|
||||
* jar.
|
||||
* <p>
|
||||
* Currently, some tools do not accept this format so you may not always be able to
|
||||
* use this technique. For example, {@code jar -xf} may silently fail to extract a jar
|
||||
* or war that has been made fully-executable. It is recommended that you only enable
|
||||
* this option if you intend to execute it directly, rather than running it with
|
||||
* {@code java -jar} or deploying it to a servlet container.
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Parameter(defaultValue = "false")
|
||||
private boolean executable;
|
||||
|
||||
/**
|
||||
* The embedded launch script to prepend to the front of the jar if it is fully
|
||||
* executable. If not specified the 'Spring Boot' default script will be used.
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Parameter
|
||||
private @Nullable File embeddedLaunchScript;
|
||||
|
||||
/**
|
||||
* Properties that should be expanded in the embedded launch script.
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Parameter
|
||||
private @Nullable Properties embeddedLaunchScriptProperties;
|
||||
|
||||
/**
|
||||
* Timestamp for reproducible output archive entries, either formatted as ISO 8601
|
||||
* (<code>yyyy-MM-dd'T'HH:mm:ssXXX</code>) or an {@code int} representing seconds
|
||||
@@ -227,8 +194,7 @@ public class RepackageMojo extends AbstractPackagerMojo {
|
||||
Repackager repackager = getRepackager(source.getFile());
|
||||
Libraries libraries = getLibraries(this.requiresUnpack);
|
||||
try {
|
||||
LaunchScript launchScript = getLaunchScript();
|
||||
repackager.repackage(target, libraries, launchScript, parseOutputTimestamp());
|
||||
repackager.repackage(target, libraries, parseOutputTimestamp());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MojoExecutionException(ex.getMessage(), ex);
|
||||
@@ -249,41 +215,11 @@ public class RepackageMojo extends AbstractPackagerMojo {
|
||||
return getConfiguredPackager(() -> new Repackager(source));
|
||||
}
|
||||
|
||||
private @Nullable LaunchScript getLaunchScript() throws IOException {
|
||||
if (this.executable || this.embeddedLaunchScript != null) {
|
||||
return new DefaultLaunchScript(this.embeddedLaunchScript, buildLaunchScriptProperties());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Properties buildLaunchScriptProperties() {
|
||||
Properties properties = new Properties();
|
||||
if (this.embeddedLaunchScriptProperties != null) {
|
||||
properties.putAll(this.embeddedLaunchScriptProperties);
|
||||
}
|
||||
putIfMissing(properties, "initInfoProvides", this.project.getArtifactId());
|
||||
putIfMissing(properties, "initInfoShortDescription", this.project.getName(), this.project.getArtifactId());
|
||||
putIfMissing(properties, "initInfoDescription", removeLineBreaks(this.project.getDescription()),
|
||||
this.project.getName(), this.project.getArtifactId());
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Contract("!null -> !null")
|
||||
private @Nullable String removeLineBreaks(@Nullable String description) {
|
||||
return (description != null) ? WHITE_SPACE_PATTERN.matcher(description).replaceAll(" ") : null;
|
||||
}
|
||||
|
||||
private void putIfMissing(Properties properties, String key, String... valueCandidates) {
|
||||
if (!properties.containsKey(key)) {
|
||||
for (String candidate : valueCandidates) {
|
||||
if (StringUtils.hasLength(candidate)) {
|
||||
properties.put(key, candidate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateArtifact(Artifact source, File target, File original) {
|
||||
if (this.attach) {
|
||||
attachArtifact(source, target, original);
|
||||
|
||||
@@ -335,7 +335,6 @@
|
||||
* xref:gradle-plugin:packaging-oci-image.adoc#build-image[gradle-plugin#build-image]
|
||||
* xref:gradle-plugin:packaging.adoc#packaging-executable.and-plain-archives[gradle-plugin#packaging-executable.and-plain-archives]
|
||||
* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.including-development-only-dependencies[gradle-plugin#packaging-executable.configuring.including-development-only-dependencies]
|
||||
* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.launch-script[gradle-plugin#packaging-executable.configuring.launch-script]
|
||||
* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.layered-archives.configuration[gradle-plugin#packaging-executable.configuring.layered-archives.configuration]
|
||||
* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.layered-archives[gradle-plugin#packaging-executable.configuring.layered-archives]
|
||||
* xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.main-class[gradle-plugin#packaging-executable.configuring.main-class]
|
||||
@@ -502,23 +501,6 @@
|
||||
* xref:how-to:deployment/index.adoc#howto.deployment[#deployment]
|
||||
* xref:how-to:deployment/index.adoc[#deployment]
|
||||
* xref:how-to:deployment/index.adoc[deployment]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running.conf-file[#deployment.installing.init-d.script-customization.when-running.conf-file]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running.conf-file[#deployment.installing.nix-services.script-customization.when-running.conf-file]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running[#deployment-script-customization-when-it-runs]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running[#deployment.installing.init-d.script-customization.when-running]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running[#deployment.installing.nix-services.script-customization.when-running]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-written[#deployment-script-customization-when-it-written]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-written[#deployment.installing.init-d.script-customization.when-written]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-written[#deployment.installing.nix-services.script-customization.when-written]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization[#deployment-script-customization]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization[#deployment.installing.init-d.script-customization]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization[#deployment.installing.nix-services.script-customization]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.securing[#deployment-initd-service-securing]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.securing[#deployment.installing.init-d.securing]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d.securing[#deployment.installing.nix-services.init-d.securing]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d[#deployment-initd-service]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d[#deployment.installing.init-d]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.init-d[#deployment.installing.nix-services.init-d]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.system-d[#deployment-systemd-service]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.system-d[#deployment.installing.nix-services.system-d]
|
||||
* xref:how-to:deployment/installing.adoc#howto.deployment.installing.system-d[#deployment.installing.system-d]
|
||||
@@ -879,13 +861,10 @@
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.optional-parameters[maven-plugin#packaging.repackage-goal.optional-parameters]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.attach[maven-plugin#packaging.repackage-goal.parameter-details.attach]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.classifier[maven-plugin#packaging.repackage-goal.parameter-details.classifier]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.embedded-launch-script-properties[maven-plugin#packaging.repackage-goal.parameter-details.embedded-launch-script-properties]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.embedded-launch-script[maven-plugin#packaging.repackage-goal.parameter-details.embedded-launch-script]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.exclude-devtools[maven-plugin#packaging.repackage-goal.parameter-details.exclude-devtools]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.exclude-docker-compose[maven-plugin#packaging.repackage-goal.parameter-details.exclude-docker-compose]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.exclude-group-ids[maven-plugin#packaging.repackage-goal.parameter-details.exclude-group-ids]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.excludes[maven-plugin#packaging.repackage-goal.parameter-details.excludes]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.executable[maven-plugin#packaging.repackage-goal.parameter-details.executable]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.include-system-scope[maven-plugin#packaging.repackage-goal.parameter-details.include-system-scope]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.include-tools[maven-plugin#packaging.repackage-goal.parameter-details.include-tools]
|
||||
* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.includes[maven-plugin#packaging.repackage-goal.parameter-details.includes]
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
= Deploying Spring Boot Applications
|
||||
|
||||
Spring Boot's flexible packaging options provide a great deal of choice when it comes to deploying your application.
|
||||
You can deploy Spring Boot applications to a variety of cloud platforms, to virtual/real machines, or make them fully executable for Unix systems.
|
||||
You can deploy Spring Boot applications to a variety of cloud platforms and to virtual or real machines.
|
||||
|
||||
This section covers some of the more common deployment scenarios.
|
||||
|
||||
|
||||
+1
-330
@@ -1,7 +1,7 @@
|
||||
[[howto.deployment.installing]]
|
||||
= Installing Spring Boot Applications
|
||||
|
||||
In addition to running Spring Boot applications by using `java -jar` directly, it is also possible to run them as `systemd`, `init.d` or Windows services.
|
||||
In addition to running Spring Boot applications by using `java -jar` directly, it is also possible to run them as services.
|
||||
|
||||
|
||||
|
||||
@@ -51,335 +51,6 @@ Run `man systemctl` for more details.
|
||||
|
||||
|
||||
|
||||
[[howto.deployment.installing.init-d]]
|
||||
== Installation as an init.d Service (System V)
|
||||
|
||||
To use your application as `init.d` service, configure its build to produce a xref:deployment/installing.adoc[fully executable jar].
|
||||
|
||||
CAUTION: Fully executable jars work by embedding an extra script at the front of the file.
|
||||
Currently, some tools do not accept this format, so you may not always be able to use this technique.
|
||||
For example, `jar -xf` may silently fail to extract a jar or war that has been made fully executable.
|
||||
It is recommended that you make your jar or war fully executable only if you intend to execute it directly, rather than running it with `java -jar` or deploying it to a servlet container.
|
||||
|
||||
CAUTION: A zip64-format jar file cannot be made fully executable.
|
||||
Attempting to do so will result in a jar file that is reported as corrupt when executed directly or with `java -jar`.
|
||||
A standard-format jar file that contains one or more zip64-format nested jars can be fully executable.
|
||||
|
||||
To create a '`fully executable`' jar with Maven, use the following plugin configuration:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<executable>true</executable>
|
||||
</configuration>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
The following example shows the equivalent Gradle configuration:
|
||||
|
||||
[source,gradle]
|
||||
----
|
||||
tasks.named('bootJar') {
|
||||
launchScript()
|
||||
}
|
||||
----
|
||||
|
||||
It can then be symlinked to `init.d` to support the standard `start`, `stop`, `restart`, and `status` commands.
|
||||
|
||||
The default launch script that is added to a fully executable jar supports most Linux distributions and is tested on CentOS and Ubuntu.
|
||||
Other platforms, such as OS X and FreeBSD, require the use of a custom script.
|
||||
The default scripts supports the following features:
|
||||
|
||||
* Starts the services as the user that owns the jar file
|
||||
* Tracks the application's PID by using `/var/run/<appname>/<appname>.pid`
|
||||
* Writes console logs to `/var/log/<appname>.log`
|
||||
|
||||
Assuming that you have a Spring Boot application installed in `/var/myapp`, to install a Spring Boot application as an `init.d` service, create a symlink, as follows:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
$ sudo ln -s /var/myapp/myapp.jar /etc/init.d/myapp
|
||||
----
|
||||
|
||||
Once installed, you can start and stop the service in the usual way.
|
||||
For example, on a Debian-based system, you could start it with the following command:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
$ service myapp start
|
||||
----
|
||||
|
||||
TIP: If your application fails to start, check the log file written to `/var/log/<appname>.log` for errors.
|
||||
|
||||
You can also flag the application to start automatically by using your standard operating system tools.
|
||||
For example, on Debian, you could use the following command:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
$ update-rc.d myapp defaults <priority>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[howto.deployment.installing.init-d.securing]]
|
||||
=== Securing an init.d Service
|
||||
|
||||
NOTE: The following is a set of guidelines on how to secure a Spring Boot application that runs as an init.d service.
|
||||
It is not intended to be an exhaustive list of everything that should be done to harden an application and the environment in which it runs.
|
||||
|
||||
When executed as root, as is the case when root is being used to start an init.d service, the default executable script runs the application as the user specified in the `RUN_AS_USER` environment variable.
|
||||
When the environment variable is not set, the user who owns the jar file is used instead.
|
||||
You should never run a Spring Boot application as `root`, so `RUN_AS_USER` should never be root and your application's jar file should never be owned by root.
|
||||
Instead, create a specific user to run your application and set the `RUN_AS_USER` environment variable or use `chown` to make it the owner of the jar file, as shown in the following example:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
$ chown bootapp:bootapp your-app.jar
|
||||
----
|
||||
|
||||
In this case, the default executable script runs the application as the `bootapp` user.
|
||||
|
||||
TIP: To reduce the chances of the application's user account being compromised, you should consider preventing it from using a login shell.
|
||||
For example, you can set the account's shell to `/usr/sbin/nologin`.
|
||||
|
||||
You should also take steps to prevent the modification of your application's jar file.
|
||||
Firstly, configure its permissions so that it cannot be written and can only be read or executed by its owner, as shown in the following example:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
$ chmod 500 your-app.jar
|
||||
----
|
||||
|
||||
Second, you should also take steps to limit the damage if your application or the account that is running it is compromised.
|
||||
If an attacker does gain access, they could make the jar file writable and change its contents.
|
||||
One way to protect against this is to make it immutable by using `chattr`, as shown in the following example:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
$ sudo chattr +i your-app.jar
|
||||
----
|
||||
|
||||
This will prevent any user, including root, from modifying the jar.
|
||||
|
||||
If root is used to control the application's service and you xref:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running.conf-file[use a `.conf` file] to customize its startup, the `.conf` file is read and evaluated by the root user.
|
||||
It should be secured accordingly.
|
||||
Use `chmod` so that the file can only be read by the owner and use `chown` to make root the owner, as shown in the following example:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
$ chmod 400 your-app.conf
|
||||
$ sudo chown root:root your-app.conf
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[howto.deployment.installing.init-d.script-customization]]
|
||||
=== Customizing the Startup Script
|
||||
|
||||
The default embedded startup script written by the Maven or Gradle plugin can be customized in a number of ways.
|
||||
For most people, using the default script along with a few customizations is usually enough.
|
||||
If you find you cannot customize something that you need to, use the `embeddedLaunchScript` option to write your own file entirely.
|
||||
|
||||
|
||||
|
||||
[[howto.deployment.installing.init-d.script-customization.when-written]]
|
||||
==== Customizing the Start Script When It Is Written
|
||||
|
||||
It often makes sense to customize elements of the start script as it is written into the jar file.
|
||||
For example, init.d scripts can provide a "`description`".
|
||||
Since you know the description up front (and it need not change), you may as well provide it when the jar is generated.
|
||||
|
||||
To customize written elements, use the `embeddedLaunchScriptProperties` option of the Spring Boot Maven plugin or the xref:gradle-plugin:packaging.adoc#packaging-executable.configuring.launch-script[`properties` property of the Spring Boot Gradle plugin's `launchScript`].
|
||||
|
||||
The following property substitutions are supported with the default script:
|
||||
|
||||
[cols="1,3,3,3"]
|
||||
|===
|
||||
| Name | Description | Gradle default | Maven default
|
||||
|
||||
| `mode`
|
||||
| The script mode.
|
||||
| `auto`
|
||||
| `auto`
|
||||
|
||||
| `initInfoProvides`
|
||||
| The `Provides` section of "`INIT INFO`"
|
||||
| `${task.baseName}`
|
||||
| `${project.artifactId}`
|
||||
|
||||
| `initInfoRequiredStart`
|
||||
| `Required-Start` section of "`INIT INFO`".
|
||||
| `$remote_fs $syslog $network`
|
||||
| `$remote_fs $syslog $network`
|
||||
|
||||
| `initInfoRequiredStop`
|
||||
| `Required-Stop` section of "`INIT INFO`".
|
||||
| `$remote_fs $syslog $network`
|
||||
| `$remote_fs $syslog $network`
|
||||
|
||||
| `initInfoDefaultStart`
|
||||
| `Default-Start` section of "`INIT INFO`".
|
||||
| `2 3 4 5`
|
||||
| `2 3 4 5`
|
||||
|
||||
| `initInfoDefaultStop`
|
||||
| `Default-Stop` section of "`INIT INFO`".
|
||||
| `0 1 6`
|
||||
| `0 1 6`
|
||||
|
||||
| `initInfoShortDescription`
|
||||
| `Short-Description` section of "`INIT INFO`".
|
||||
| Single-line version of `${project.description}` (falling back to `${task.baseName}`)
|
||||
| `${project.name}`
|
||||
|
||||
| `initInfoDescription`
|
||||
| `Description` section of "`INIT INFO`".
|
||||
| `${project.description}` (falling back to `${task.baseName}`)
|
||||
| `${project.description}` (falling back to `${project.name}`)
|
||||
|
||||
| `initInfoChkconfig`
|
||||
| `chkconfig` section of "`INIT INFO`"
|
||||
| `2345 99 01`
|
||||
| `2345 99 01`
|
||||
|
||||
| `confFolder`
|
||||
| The default value for `CONF_FOLDER`
|
||||
| Folder containing the jar
|
||||
| Folder containing the jar
|
||||
|
||||
| `inlinedConfScript`
|
||||
| Reference to a file script that should be inlined in the default launch script.
|
||||
This can be used to set environmental variables such as `JAVA_OPTS` before any external config files are loaded
|
||||
|
|
||||
|
|
||||
|
||||
| `logFolder`
|
||||
| Default value for `LOG_FOLDER`.
|
||||
Only valid for an `init.d` service
|
||||
|
|
||||
|
|
||||
|
||||
| `logFilename`
|
||||
| Default value for `LOG_FILENAME`.
|
||||
Only valid for an `init.d` service
|
||||
|
|
||||
|
|
||||
|
||||
| `pidFolder`
|
||||
| Default value for `PID_FOLDER`.
|
||||
Only valid for an `init.d` service
|
||||
|
|
||||
|
|
||||
|
||||
| `pidFilename`
|
||||
| Default value for the name of the PID file in `PID_FOLDER`.
|
||||
Only valid for an `init.d` service
|
||||
|
|
||||
|
|
||||
|
||||
| `useStartStopDaemon`
|
||||
| Whether the `start-stop-daemon` command, when it is available, should be used to control the process
|
||||
| `true`
|
||||
| `true`
|
||||
|
||||
| `stopWaitTime`
|
||||
| Default value for `STOP_WAIT_TIME` in seconds.
|
||||
Only valid for an `init.d` service
|
||||
| 60
|
||||
| 60
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[howto.deployment.installing.init-d.script-customization.when-running]]
|
||||
==== Customizing a Script When It Runs
|
||||
|
||||
For items of the script that need to be customized _after_ the jar has been written, you can use environment variables or a xref:deployment/installing.adoc#howto.deployment.installing.init-d.script-customization.when-running.conf-file[config file].
|
||||
|
||||
The following environment properties are supported with the default script:
|
||||
|
||||
[cols="1,6"]
|
||||
|===
|
||||
| Variable | Description
|
||||
|
||||
| `MODE`
|
||||
| The "`mode`" of operation.
|
||||
The default depends on the way the jar was built but is usually `auto` (meaning it tries to guess if it is an init script by checking if it is a symlink in a directory called `init.d`).
|
||||
You can explicitly set it to `service` so that the `stop\|start\|status\|restart` commands work or to `run` if you want to run the script in the foreground.
|
||||
|
||||
| `RUN_AS_USER`
|
||||
| The user that will be used to run the application.
|
||||
When not set, the user that owns the jar file will be used.
|
||||
|
||||
| `USE_START_STOP_DAEMON`
|
||||
| Whether the `start-stop-daemon` command, when it is available, should be used to control the process.
|
||||
Defaults to `true`.
|
||||
|
||||
| `PID_FOLDER`
|
||||
| The root name of the pid folder (`/var/run` by default).
|
||||
|
||||
| `LOG_FOLDER`
|
||||
| The name of the folder in which to put log files (`/var/log` by default).
|
||||
|
||||
| `CONF_FOLDER`
|
||||
| The name of the folder from which to read .conf files (same folder as jar-file by default).
|
||||
|
||||
| `LOG_FILENAME`
|
||||
| The name of the log file in the `LOG_FOLDER` (`<appname>.log` by default).
|
||||
|
||||
| `APP_NAME`
|
||||
| The name of the app.
|
||||
If the jar is run from a symlink, the script guesses the app name.
|
||||
If it is not a symlink or you want to explicitly set the app name, this can be useful.
|
||||
|
||||
| `RUN_ARGS`
|
||||
| The arguments to pass to the program (the Spring Boot app).
|
||||
|
||||
| `JAVA_HOME`
|
||||
| The location of the `java` executable is discovered by using the `PATH` by default, but you can set it explicitly if there is an executable file at `$JAVA_HOME/bin/java`.
|
||||
|
||||
| `JAVA_OPTS`
|
||||
| Options that are passed to the JVM when it is launched.
|
||||
|
||||
| `JARFILE`
|
||||
| The explicit location of the jar file, in case the script is being used to launch a jar that it is not actually embedded.
|
||||
|
||||
| `DEBUG`
|
||||
| If not empty, sets the `-x` flag on the shell process, allowing you to see the logic in the script.
|
||||
|
||||
| `STOP_WAIT_TIME`
|
||||
| The time in seconds to wait when stopping the application before forcing a shutdown (`60` by default).
|
||||
|===
|
||||
|
||||
NOTE: The `PID_FOLDER`, `LOG_FOLDER`, and `LOG_FILENAME` variables are only valid for an `init.d` service.
|
||||
For `systemd`, the equivalent customizations are made by using the '`service`' script.
|
||||
See the https://www.freedesktop.org/software/systemd/man/systemd.service.html[service unit configuration man page] for more details.
|
||||
|
||||
|
||||
|
||||
[[howto.deployment.installing.init-d.script-customization.when-running.conf-file]]
|
||||
===== Using a Conf File
|
||||
|
||||
With the exception of `JARFILE` and `APP_NAME`, the settings listed in the preceding section can be configured by using a `.conf` file.
|
||||
The file is expected to be next to the jar file and have the same name but suffixed with `.conf` rather than `.jar`.
|
||||
For example, a jar named `/var/myapp/myapp.jar` uses the configuration file named `/var/myapp/myapp.conf`, as shown in the following example:
|
||||
|
||||
.myapp.conf
|
||||
[source,properties]
|
||||
----
|
||||
JAVA_OPTS=-Xmx1024M
|
||||
LOG_FOLDER=/custom/log/folder
|
||||
----
|
||||
|
||||
TIP: If you do not like having the config file next to the jar file, you can set a `CONF_FOLDER` environment variable to customize the location of the config file.
|
||||
|
||||
To learn about securing this file appropriately, see xref:deployment/installing.adoc#howto.deployment.installing.init-d.securing[the guidelines for securing an init.d service].
|
||||
|
||||
|
||||
|
||||
[[howto.deployment.installing.windows-services]]
|
||||
== Microsoft Windows Services
|
||||
|
||||
|
||||
-3
@@ -5,9 +5,6 @@ While it is possible to convert a Spring Boot uber jar into a Docker image with
|
||||
When you create a jar containing the layers index file, the `spring-boot-jarmode-tools` jar will be added as a dependency to your jar.
|
||||
With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers.
|
||||
|
||||
CAUTION: The `tools` mode can not be used with a xref:how-to:deployment/installing.adoc[fully executable Spring Boot archive] that includes a launch script.
|
||||
Disable launch script configuration when building a jar file that is intended to be used with the `extract` tools mode command.
|
||||
|
||||
Here’s how you can launch your jar with a `tools` jar mode:
|
||||
|
||||
[source,shell]
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
= Spring Boot Launch Script Tests
|
||||
|
||||
This module contains integration tests for the default launch script that is used
|
||||
to make a jar file fully executable on Linux. The tests use Docker to verify the
|
||||
functionality in a variety of Linux distributions.
|
||||
|
||||
|
||||
|
||||
== Setting up Docker
|
||||
|
||||
The setup that's required varies depending on your operating system.
|
||||
|
||||
|
||||
|
||||
=== Docker on OS X
|
||||
|
||||
Install Docker for Mac. See the https://docs.docker.com/docker-for-mac/install/[macOS
|
||||
installation instructions] for details.
|
||||
|
||||
|
||||
|
||||
=== Docker on Linux
|
||||
|
||||
Install Docker as appropriate for your Linux distribution. See the
|
||||
https://docs.docker.com/engine/installation/[Linux installation instructions] for more
|
||||
information.
|
||||
|
||||
Next, add your user to the `docker` group. For example:
|
||||
|
||||
----
|
||||
$ sudo usermod -a -G docker awilkinson
|
||||
----
|
||||
|
||||
You may need to log out and back in again for this change to take effect and for your
|
||||
user to be able to connect to the daemon.
|
||||
|
||||
|
||||
|
||||
== Running the tests
|
||||
|
||||
You're now ready to run the tests. Assuming that you're in the same directory as this
|
||||
README, the tests can be launched as follows:
|
||||
|
||||
----
|
||||
$ gradle intTest
|
||||
----
|
||||
|
||||
The first time the tests are run, Docker will create the container images that are used to
|
||||
run the tests. This can take several minutes, particularly if you have a slow network
|
||||
connection. Subsequent runs will be faster as the images are cached locally. You can run
|
||||
`docker images` to see a list of the cached images. Images created by these tests will be
|
||||
tagged with `spring-boot-it` prefix to easily distinguish them.
|
||||
|
||||
|
||||
|
||||
== Cleaning up
|
||||
|
||||
If you want to reclaim the disk space used by the cached images (at the expense of having
|
||||
to wait for them to be downloaded and rebuilt the next time you run the tests), you can
|
||||
use `docker images` to list the images and `docker rmi <image>` to delete them (look for
|
||||
`spring-boot-it` tag). See `docker rmi --help` for further details.
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot.docker-test"
|
||||
id "de.undercouch.download"
|
||||
}
|
||||
|
||||
description = "Spring Boot Launch Script Integration Tests"
|
||||
|
||||
def jdkVersion = "17.0.11+10"
|
||||
def jdkArch = "aarch64".equalsIgnoreCase(System.getProperty("os.arch")) ? "aarch64" : "amd64"
|
||||
|
||||
configurations {
|
||||
app
|
||||
}
|
||||
|
||||
dependencies {
|
||||
app project(path: ":platform:spring-boot-dependencies", configuration: "mavenRepository")
|
||||
app project(path: ":build-plugin:spring-boot-gradle-plugin", configuration: "mavenRepository")
|
||||
|
||||
dockerTestImplementation(project(":test-support:spring-boot-docker-test-support"))
|
||||
dockerTestImplementation(project(":starter:spring-boot-starter-test"))
|
||||
dockerTestImplementation("org.testcontainers:testcontainers")
|
||||
}
|
||||
|
||||
tasks.register("syncMavenRepository", Sync) {
|
||||
from configurations.app
|
||||
into layout.buildDirectory.dir("docker-test-maven-repository")
|
||||
}
|
||||
|
||||
tasks.register("syncAppSource", org.springframework.boot.build.SyncAppSource) {
|
||||
sourceDirectory = file("spring-boot-launch-script-tests-app")
|
||||
destinationDirectory = file(layout.buildDirectory.dir("spring-boot-launch-script-tests-app"))
|
||||
}
|
||||
|
||||
tasks.register("buildApp", GradleBuild) {
|
||||
dependsOn syncAppSource, syncMavenRepository
|
||||
dir = layout.buildDirectory.dir("spring-boot-launch-script-tests-app")
|
||||
startParameter.buildCacheEnabled = false
|
||||
tasks = ["build"]
|
||||
}
|
||||
|
||||
tasks.register("downloadJdk", Download) {
|
||||
def destFolder = new File(project.gradle.gradleUserHomeDir, "caches/springboot/downloads/jdk/bellsoft")
|
||||
destFolder.mkdirs()
|
||||
src "https://download.bell-sw.com/java/${jdkVersion}/bellsoft-jdk${jdkVersion}-linux-${jdkArch}.tar.gz"
|
||||
dest destFolder
|
||||
tempAndMove true
|
||||
overwrite false
|
||||
retries 3
|
||||
}
|
||||
|
||||
tasks.register("syncJdkDownloads", Sync) {
|
||||
dependsOn downloadJdk
|
||||
from "${project.gradle.gradleUserHomeDir}/caches/springboot/downloads/jdk/bellsoft/"
|
||||
include "bellsoft-jdk${jdkVersion}-linux-${jdkArch}.tar.gz"
|
||||
into layout.buildDirectory.dir("downloads/jdk/bellsoft/")
|
||||
}
|
||||
|
||||
tasks.named("processDockerTestResources").configure {
|
||||
dependsOn syncJdkDownloads
|
||||
}
|
||||
|
||||
tasks.named("dockerTest").configure {
|
||||
dependsOn buildApp
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot"
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = '17'
|
||||
targetCompatibility = '17'
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url = layout.projectDirectory.dir("../docker-test-maven-repository") }
|
||||
mavenCentral()
|
||||
spring.mavenRepositories()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||
implementation("org.apache.tomcat.embed:tomcat-embed-core")
|
||||
}
|
||||
|
||||
bootJar {
|
||||
launchScript()
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
pluginManagement {
|
||||
evaluate(new File("${gradle.parent.rootProject.rootDir}/buildSrc/SpringRepositorySupport.groovy")).apply(this)
|
||||
repositories {
|
||||
maven { url = layout.settingsDirectory.dir("../docker-test-maven-repository") }
|
||||
mavenCentral()
|
||||
spring.mavenRepositories()
|
||||
}
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
if (requested.id.id == "org.springframework.boot") {
|
||||
useModule "org.springframework.boot:spring-boot-gradle-plugin:${requested.version}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.launchscript;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
|
||||
public class LaunchScriptTestApplication {
|
||||
|
||||
public static void main(String[] args) throws LifecycleException {
|
||||
System.out.println("Starting " + LaunchScriptTestApplication.class.getSimpleName() + " (" + findSource() + ")");
|
||||
Tomcat tomcat = new Tomcat();
|
||||
tomcat.getConnector().setPort(getPort(args));
|
||||
Context context = tomcat.addContext(getContextPath(args), null);
|
||||
tomcat.addServlet(context.getPath(), "test", new HttpServlet() {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.getWriter().println("Launched");
|
||||
}
|
||||
|
||||
});
|
||||
context.addServletMappingDecoded("/", "test");
|
||||
tomcat.start();
|
||||
}
|
||||
|
||||
private static URL findSource() {
|
||||
try {
|
||||
ProtectionDomain domain = LaunchScriptTestApplication.class.getProtectionDomain();
|
||||
CodeSource codeSource = (domain != null) ? domain.getCodeSource() : null;
|
||||
return (codeSource != null) ? codeSource.getLocation() : null;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int getPort(String[] args) {
|
||||
String port = getProperty(args, "server.port");
|
||||
return (port != null) ? Integer.parseInt(port) : 8080;
|
||||
}
|
||||
|
||||
private static String getContextPath(String[] args) {
|
||||
String contextPath = getProperty(args, "server.servlet.context-path");
|
||||
return (contextPath != null) ? contextPath : "";
|
||||
}
|
||||
|
||||
private static String getProperty(String[] args, String property) {
|
||||
String value = System.getProperty(property);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
String prefix = "--" + property + "=";
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith(prefix)) {
|
||||
return arg.substring(prefix.length());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.launchscript;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.output.Slf4jLogConsumer;
|
||||
import org.testcontainers.containers.output.ToStringConsumer;
|
||||
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
|
||||
import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import org.springframework.boot.ansi.AnsiColor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
/**
|
||||
* Abstract base class for testing the launch script.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Ali Shahbour
|
||||
* @author Alexey Vinogradov
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
abstract class AbstractLaunchScriptIntegrationTests {
|
||||
|
||||
protected static final char ESC = 27;
|
||||
|
||||
private final String scriptsDir;
|
||||
|
||||
protected AbstractLaunchScriptIntegrationTests(String scriptsDir) {
|
||||
this.scriptsDir = scriptsDir;
|
||||
}
|
||||
|
||||
static List<Object[]> filterParameters(Predicate<File> osFilter) {
|
||||
List<Object[]> parameters = new ArrayList<>();
|
||||
for (File os : new File("src/dockerTest/resources/conf").listFiles()) {
|
||||
if (osFilter.test(os)) {
|
||||
for (File version : os.listFiles()) {
|
||||
parameters.add(new Object[] { os.getName(), version.getName() });
|
||||
}
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
protected Condition<String> coloredString(AnsiColor color, String string) {
|
||||
String colorString = ESC + "[0;" + color + "m" + string + ESC + "[0m";
|
||||
return new Condition<>() {
|
||||
|
||||
@Override
|
||||
public boolean matches(String value) {
|
||||
return containsString(colorString).matches(value);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
protected void doLaunch(String os, String version, String script) throws Exception {
|
||||
assertThat(doTest(os, version, script)).contains("Launched");
|
||||
}
|
||||
|
||||
protected String doTest(String os, String version, String script) throws Exception {
|
||||
ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false);
|
||||
try (LaunchScriptTestContainer container = new LaunchScriptTestContainer(os, version, this.scriptsDir,
|
||||
script)) {
|
||||
container.withLogConsumer(consumer);
|
||||
container.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("docker")));
|
||||
container.start();
|
||||
while (container.isRunning()) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
}
|
||||
return consumer.toUtf8String();
|
||||
}
|
||||
|
||||
private static final class LaunchScriptTestContainer extends GenericContainer<LaunchScriptTestContainer> {
|
||||
|
||||
private LaunchScriptTestContainer(String os, String version, String scriptsDir, String testScript) {
|
||||
super(createImage(os, version));
|
||||
withCopyFileToContainer(MountableFile.forHostPath(findApplication().getAbsolutePath()), "/app.jar");
|
||||
withCopyFileToContainer(
|
||||
MountableFile.forHostPath("src/dockerTest/resources/scripts/" + scriptsDir + "test-functions.sh"),
|
||||
"/test-functions.sh");
|
||||
withCopyFileToContainer(
|
||||
MountableFile.forHostPath("src/dockerTest/resources/scripts/" + scriptsDir + testScript),
|
||||
"/" + testScript);
|
||||
withCommand("/bin/bash", "-c",
|
||||
"chown root:root *.sh && chown root:root *.jar && chmod +x " + testScript + " && ./" + testScript);
|
||||
withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofMinutes(5)));
|
||||
}
|
||||
|
||||
private static ImageFromDockerfile createImage(String os, String version) {
|
||||
ImageFromDockerfile image = new ImageFromDockerfile(
|
||||
"spring-boot-launch-script/" + os.toLowerCase(Locale.ROOT) + "-" + version);
|
||||
image.withFileFromFile("Dockerfile",
|
||||
new File("src/dockerTest/resources/conf/" + os + "/" + version + "/Dockerfile"));
|
||||
for (File file : new File("build/downloads/jdk/bellsoft").listFiles()) {
|
||||
image.withFileFromFile("downloads/" + file.getName(), file);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private static File findApplication() {
|
||||
String name = String.format("build/%1$s/build/libs/%1$s.jar", "spring-boot-launch-script-tests-app");
|
||||
File jar = new File(name);
|
||||
Assert.state(jar.isFile(), () -> "Could not find " + name + ". Have you built it?");
|
||||
return jar;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.launchscript;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests of Spring Boot's launch script when executing the jar directly.
|
||||
*
|
||||
* @author Alexey Vinogradov
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@DisabledIfDockerUnavailable
|
||||
class JarLaunchScriptIntegrationTests extends AbstractLaunchScriptIntegrationTests {
|
||||
|
||||
JarLaunchScriptIntegrationTests() {
|
||||
super("jar/");
|
||||
}
|
||||
|
||||
static List<Object[]> parameters() {
|
||||
return filterParameters((file) -> true);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void basicLaunch(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "basic-launch.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithDebugEnv(String os, String version) throws Exception {
|
||||
final String output = doTest(os, version, "launch-with-debug.sh");
|
||||
assertThat(output).contains("++ pwd");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithDifferentJarFileEnv(String os, String version) throws Exception {
|
||||
final String output = doTest(os, version, "launch-with-jarfile.sh");
|
||||
assertThat(output).contains("app-another.jar");
|
||||
assertThat(output).doesNotContain("spring-boot-launch-script-tests.jar");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithSingleCommandLineArgument(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-single-command-line-argument.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithMultipleCommandLineArguments(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-multiple-command-line-arguments.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithSingleRunArg(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-single-run-arg.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithMultipleRunArgs(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-multiple-run-args.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithSingleJavaOpt(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-single-java-opt.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithMultipleJavaOpts(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-multiple-java-opts.sh");
|
||||
}
|
||||
|
||||
}
|
||||
-291
@@ -1,291 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.launchscript;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.boot.ansi.AnsiColor;
|
||||
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for Spring Boot's launch script on OSs that use SysVinit.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Ali Shahbour
|
||||
* @author Alexey Vinogradov
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
@DisabledIfDockerUnavailable
|
||||
class SysVinitLaunchScriptIntegrationTests extends AbstractLaunchScriptIntegrationTests {
|
||||
|
||||
SysVinitLaunchScriptIntegrationTests() {
|
||||
super("init.d/");
|
||||
}
|
||||
|
||||
static List<Object[]> parameters() {
|
||||
return filterParameters((file) -> !file.getName().contains("RedHat"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void statusWhenStopped(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "status-when-stopped.sh");
|
||||
assertThat(output).contains("Status: 3");
|
||||
assertThat(output).has(coloredString(AnsiColor.RED, "Not running"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void statusWhenStarted(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "status-when-started.sh");
|
||||
assertThat(output).contains("Status: 0");
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Started [" + extractPid(output) + "]"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void statusWhenKilled(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "status-when-killed.sh");
|
||||
assertThat(output).contains("Status: 1");
|
||||
assertThat(output)
|
||||
.has(coloredString(AnsiColor.RED, "Not running (process " + extractPid(output) + " not found)"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void stopWhenStopped(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "stop-when-stopped.sh");
|
||||
assertThat(output).contains("Status: 0");
|
||||
assertThat(output).has(coloredString(AnsiColor.YELLOW, "Not running (pidfile not found)"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void forceStopWhenStopped(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "force-stop-when-stopped.sh");
|
||||
assertThat(output).contains("Status: 0");
|
||||
assertThat(output).has(coloredString(AnsiColor.YELLOW, "Not running (pidfile not found)"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void startWhenStarted(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "start-when-started.sh");
|
||||
assertThat(output).contains("Status: 0");
|
||||
assertThat(output).has(coloredString(AnsiColor.YELLOW, "Already running [" + extractPid(output) + "]"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void restartWhenStopped(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "restart-when-stopped.sh");
|
||||
assertThat(output).contains("Status: 0");
|
||||
assertThat(output).has(coloredString(AnsiColor.YELLOW, "Not running (pidfile not found)"));
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Started [" + extractPid(output) + "]"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void restartWhenStarted(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "restart-when-started.sh");
|
||||
assertThat(output).contains("Status: 0");
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Started [" + extract("PID1", output) + "]"));
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Stopped [" + extract("PID1", output) + "]"));
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Started [" + extract("PID2", output) + "]"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void startWhenStopped(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "start-when-stopped.sh");
|
||||
assertThat(output).contains("Status: 0");
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Started [" + extractPid(output) + "]"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void basicLaunch(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "basic-launch.sh");
|
||||
assertThat(output).doesNotContain("PID_FOLDER");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithMissingLogFolderGeneratesAWarning(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "launch-with-missing-log-folder.sh");
|
||||
assertThat(output)
|
||||
.has(coloredString(AnsiColor.YELLOW, "LOG_FOLDER /does/not/exist does not exist. Falling back to /tmp"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithMissingPidFolderGeneratesAWarning(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "launch-with-missing-pid-folder.sh");
|
||||
assertThat(output)
|
||||
.has(coloredString(AnsiColor.YELLOW, "PID_FOLDER /does/not/exist does not exist. Falling back to /tmp"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithSingleCommandLineArgument(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-single-command-line-argument.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithMultipleCommandLineArguments(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-multiple-command-line-arguments.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithSingleRunArg(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-single-run-arg.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithMultipleRunArgs(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-multiple-run-args.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithSingleJavaOpt(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-single-java-opt.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithDoubleLinkSingleJavaOpt(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-double-link-single-java-opt.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithMultipleJavaOpts(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-multiple-java-opts.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithUseOfStartStopDaemonDisabled(String os, String version) throws Exception {
|
||||
doLaunch(os, version, "launch-with-use-of-start-stop-daemon-disabled.sh");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithRelativePidFolder(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "launch-with-relative-pid-folder.sh");
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Started [" + extractPid(output) + "]"));
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Running [" + extractPid(output) + "]"));
|
||||
assertThat(output).has(coloredString(AnsiColor.GREEN, "Stopped [" + extractPid(output) + "]"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void pidFolderOwnership(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "pid-folder-ownership.sh");
|
||||
assertThat(output).contains("phil root");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void pidFileOwnership(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "pid-file-ownership.sh");
|
||||
assertThat(output).contains("phil root");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void logFileOwnership(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "log-file-ownership.sh");
|
||||
assertThat(output).contains("phil root");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void logFileOwnershipIsChangedWhenCreated(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "log-file-ownership-is-changed-when-created.sh");
|
||||
assertThat(output).contains("andy root");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void logFileOwnershipIsUnchangedWhenExists(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "log-file-ownership-is-unchanged-when-exists.sh");
|
||||
assertThat(output).contains("root root");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithRelativeLogFolder(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "launch-with-relative-log-folder.sh");
|
||||
assertThat(output).contains("Log written");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void launchWithRunAsUser(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "launch-with-run-as-user.sh");
|
||||
assertThat(output).contains("wagner root");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void whenRunAsUserDoesNotExistLaunchFailsWithInvalidArgument(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "launch-with-run-as-invalid-user.sh");
|
||||
assertThat(output).contains("Status: 2");
|
||||
assertThat(output).has(coloredString(AnsiColor.RED, "Cannot run as 'johndoe': no such user"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void whenJarOwnerAndRunAsUserAreBothSpecifiedRunAsUserTakesPrecedence(String os, String version) throws Exception {
|
||||
String output = doTest(os, version, "launch-with-run-as-user-preferred-to-jar-owner.sh");
|
||||
assertThat(output).contains("wagner root");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} {1}")
|
||||
@MethodSource("parameters")
|
||||
void whenLaunchedUsingNonRootUserWithRunAsUserSpecifiedLaunchFailsWithInsufficientPrivilege(String os,
|
||||
String version) throws Exception {
|
||||
String output = doTest(os, version, "launch-with-run-as-user-root-required.sh");
|
||||
assertThat(output).contains("Status: 4");
|
||||
assertThat(output).has(coloredString(AnsiColor.RED, "Cannot run as 'wagner': current user is not root"));
|
||||
}
|
||||
|
||||
private String extractPid(String output) {
|
||||
return extract("PID", output);
|
||||
}
|
||||
|
||||
private String extract(String label, String output) {
|
||||
Pattern pattern = Pattern.compile(".*" + label + ": ([0-9]+).*", Pattern.DOTALL);
|
||||
java.util.regex.Matcher matcher = pattern.matcher(output);
|
||||
if (matcher.matches()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
throw new IllegalArgumentException("Failed to extract " + label + " from output: " + output);
|
||||
}
|
||||
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
FROM redhat/ubi9:9.3-1476 as prepare
|
||||
COPY downloads/* /opt/download/
|
||||
RUN mkdir -p /opt/jdk && \
|
||||
cd /opt/jdk && \
|
||||
tar xzf /opt/download/* --strip-components=1
|
||||
|
||||
FROM redhat/ubi9:9.3-1476
|
||||
COPY --from=prepare /opt/jdk /opt/jdk
|
||||
ENV JAVA_HOME /opt/jdk
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
FROM ubuntu:jammy-20240405 as prepare
|
||||
COPY downloads/* /opt/download/
|
||||
RUN mkdir -p /opt/jdk && \
|
||||
cd /opt/jdk && \
|
||||
tar xzf /opt/download/* --strip-components=1
|
||||
|
||||
FROM ubuntu:jammy-20240405
|
||||
RUN apt-get update && apt-get install -y software-properties-common curl
|
||||
COPY --from=prepare /opt/jdk /opt/jdk
|
||||
ENV JAVA_HOME /opt/jdk
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
FROM ubuntu:noble-20250404 as prepare
|
||||
COPY downloads/* /opt/download/
|
||||
RUN mkdir -p /opt/jdk && \
|
||||
cd /opt/jdk && \
|
||||
tar xzf /opt/download/* --strip-components=1
|
||||
|
||||
FROM ubuntu:noble-20250404
|
||||
RUN apt-get update && apt-get install -y software-properties-common curl
|
||||
COPY --from=prepare /opt/jdk /opt/jdk
|
||||
ENV JAVA_HOME /opt/jdk
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
start_service
|
||||
await_app
|
||||
curl -s http://127.0.0.1:8080/
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
force_stop_service
|
||||
echo "Status: $?"
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_double_link_service
|
||||
echo 'JAVA_OPTS=-Dserver.port=8081' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
await_app http://127.0.0.1:8081/
|
||||
curl -s http://127.0.0.1:8081/
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
echo 'LOG_FOLDER=/does/not/exist' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
await_app
|
||||
curl -s http://127.0.0.1:8080/
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
echo 'PID_FOLDER=/does/not/exist' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
await_app
|
||||
curl -s http://127.0.0.1:8080/
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
start_service --server.port=8081 --server.servlet.context-path=/test
|
||||
await_app http://127.0.0.1:8081/test/
|
||||
curl -s http://127.0.0.1:8081/test/
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
echo 'JAVA_OPTS="-Dserver.port=8081 -Dserver.servlet.context-path=/test"' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
await_app http://127.0.0.1:8081/test/
|
||||
curl -s http://127.0.0.1:8081/test/
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
echo 'RUN_ARGS="--server.port=8081 --server.servlet.context-path=/test"' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
await_app http://127.0.0.1:8081/test/
|
||||
curl -s http://127.0.0.1:8081/test/
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
mkdir ./pid
|
||||
install_service
|
||||
echo 'LOG_FOLDER=log' > /test-service/spring-boot-app.conf
|
||||
mkdir -p /test-service/log
|
||||
start_service
|
||||
await_app
|
||||
[[ -s /test-service/log/spring-boot-app.log ]] && echo "Log written"
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
mkdir /test-service/pid
|
||||
echo 'PID_FOLDER=pid' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
echo "PID: $(cat /test-service/pid/spring-boot-app/spring-boot-app.pid)"
|
||||
await_app
|
||||
curl -s http://127.0.0.1:8080/
|
||||
status_service
|
||||
stop_service
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
|
||||
echo 'RUN_AS_USER=johndoe' > /test-service/spring-boot-app.conf
|
||||
|
||||
start_service
|
||||
echo "Status: $?"
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
|
||||
useradd wagner
|
||||
echo 'RUN_AS_USER=wagner' > /test-service/spring-boot-app.conf
|
||||
|
||||
useradd phil
|
||||
chown phil /test-service/spring-boot-app.jar
|
||||
|
||||
start_service
|
||||
await_app
|
||||
|
||||
ls -la /var/log/spring-boot-app.log
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
|
||||
useradd wagner
|
||||
echo 'RUN_AS_USER=wagner' > /test-service/spring-boot-app.conf
|
||||
echo "JAVA_HOME='$JAVA_HOME'" >> /test-service/spring-boot-app.conf
|
||||
|
||||
su - wagner -c "$(which service) spring-boot-app start"
|
||||
echo "Status: $?"
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
|
||||
useradd wagner
|
||||
echo 'RUN_AS_USER=wagner' > /test-service/spring-boot-app.conf
|
||||
|
||||
start_service
|
||||
await_app
|
||||
|
||||
ls -la /var/log/spring-boot-app.log
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
start_service --server.port=8081
|
||||
await_app http://127.0.0.1:8081/
|
||||
curl -s http://127.0.0.1:8081/
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
echo 'JAVA_OPTS=-Dserver.port=8081' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
await_app http://127.0.0.1:8081/
|
||||
curl -s http://127.0.0.1:8081/
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
echo 'RUN_ARGS=--server.port=8081' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
await_app http://127.0.0.1:8081/
|
||||
curl -s http://127.0.0.1:8081/
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
chmod -x $(type -p start-stop-daemon)
|
||||
install_service
|
||||
echo 'USE_START_STOP_DAEMON=false' > /test-service/spring-boot-app.conf
|
||||
start_service
|
||||
await_app
|
||||
curl -s http://127.0.0.1:8080/
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
echo 'LOG_FOLDER=log' > /test-service/spring-boot-app.conf
|
||||
mkdir -p /test-service/log
|
||||
useradd andy
|
||||
chown andy /test-service/spring-boot-app.jar
|
||||
start_service
|
||||
await_app
|
||||
ls -al /test-service/log/spring-boot-app.log
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
echo 'LOG_FOLDER=log' > /test-service/spring-boot-app.conf
|
||||
mkdir -p /test-service/log
|
||||
touch /test-service/log/spring-boot-app.log
|
||||
chmod a+w /test-service/log/spring-boot-app.log
|
||||
useradd andy
|
||||
chown andy /test-service/spring-boot-app.jar
|
||||
start_service
|
||||
await_app
|
||||
ls -al /test-service/log/spring-boot-app.log
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
|
||||
chmod o+w /var/log
|
||||
|
||||
useradd phil
|
||||
mkdir /phil-files
|
||||
chown phil /phil-files
|
||||
|
||||
useradd andy
|
||||
chown andy /test-service/spring-boot-app.jar
|
||||
|
||||
start_service
|
||||
stop_service
|
||||
|
||||
su - andy -c "ln -s -f /phil-files /var/log/spring-boot-app.log"
|
||||
|
||||
start_service
|
||||
|
||||
ls -ld /phil-files
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
|
||||
useradd phil
|
||||
mkdir /phil-files
|
||||
chown phil /phil-files
|
||||
|
||||
useradd andy
|
||||
chown andy /test-service/spring-boot-app.jar
|
||||
|
||||
start_service
|
||||
stop_service
|
||||
|
||||
su - andy -c "ln -s /phil-files /var/run/spring-boot-app/spring-boot-app.pid"
|
||||
|
||||
start_service
|
||||
|
||||
ls -ld /phil-files
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
|
||||
chmod o+w /var/run
|
||||
|
||||
useradd phil
|
||||
mkdir /phil-files
|
||||
chown phil /phil-files
|
||||
|
||||
useradd andy
|
||||
chown andy /test-service/spring-boot-app.jar
|
||||
|
||||
su - andy -c "ln -s -f /phil-files /var/run/spring-boot-app"
|
||||
|
||||
start_service
|
||||
|
||||
ls -ld /phil-files
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
start_service
|
||||
echo "PID1: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"
|
||||
restart_service
|
||||
echo "Status: $?"
|
||||
echo "PID2: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
restart_service
|
||||
echo "Status: $?"
|
||||
echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
start_service
|
||||
echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"
|
||||
start_service
|
||||
echo "Status: $?"
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
start_service
|
||||
echo "Status: $?"
|
||||
echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
start_service
|
||||
pid=$(cat /var/run/spring-boot-app/spring-boot-app.pid)
|
||||
echo "PID: $pid"
|
||||
kill -9 $pid
|
||||
status_service
|
||||
echo "Status: $?"
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
start_service
|
||||
status_service
|
||||
echo "Status: $?"
|
||||
echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
status_service
|
||||
echo "Status: $?"
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
install_service
|
||||
stop_service
|
||||
echo "Status: $?"
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
await_app() {
|
||||
if [ -z $1 ]
|
||||
then
|
||||
url=http://127.0.0.1:8080
|
||||
else
|
||||
url=$1
|
||||
fi
|
||||
end=$(date +%s)
|
||||
let "end+=30"
|
||||
until curl -s $url > /dev/null
|
||||
do
|
||||
now=$(date +%s)
|
||||
if [[ $now -ge $end ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
install_service() {
|
||||
mkdir /test-service
|
||||
mv /app.jar /test-service/spring-boot-app.jar
|
||||
chmod +x /test-service/spring-boot-app.jar
|
||||
ln -s /test-service/spring-boot-app.jar /etc/init.d/spring-boot-app
|
||||
}
|
||||
|
||||
install_double_link_service() {
|
||||
mkdir /test-service
|
||||
mv /app.jar /test-service/
|
||||
chmod +x /test-service/app.jar
|
||||
ln -s /test-service/app.jar /test-service/spring-boot-app.jar
|
||||
ln -s /test-service/spring-boot-app.jar /etc/init.d/spring-boot-app
|
||||
}
|
||||
|
||||
start_service() {
|
||||
service spring-boot-app start $@
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
service spring-boot-app restart
|
||||
}
|
||||
|
||||
status_service() {
|
||||
service spring-boot-app status
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
service spring-boot-app stop
|
||||
}
|
||||
|
||||
force_stop_service() {
|
||||
service spring-boot-app force-stop
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
launch_jar
|
||||
await_app
|
||||
curl -s http://127.0.0.1:8080/
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
export DEBUG=true
|
||||
source ./test-functions.sh
|
||||
launch_jar
|
||||
await_app
|
||||
curl -s http://127.0.0.1:8080/
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
cp app.jar app-another.jar
|
||||
export JARFILE=app-another.jar
|
||||
launch_jar
|
||||
await_app
|
||||
curl -s http://127.0.0.1:8080/
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
launch_jar --server.port=8081 --server.servlet.context-path=/test
|
||||
await_app http://127.0.0.1:8081/test/
|
||||
curl -s http://127.0.0.1:8081/test/
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
echo 'JAVA_OPTS="-Dserver.port=8081 -Dserver.servlet.context-path=/test"' > app.conf
|
||||
launch_jar
|
||||
await_app http://127.0.0.1:8081/test/
|
||||
curl -s http://127.0.0.1:8081/test/
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
echo 'RUN_ARGS="--server.port=8081 --server.servlet.context-path=/test"' > app.conf
|
||||
launch_jar
|
||||
await_app http://127.0.0.1:8081/test/
|
||||
curl -s http://127.0.0.1:8081/test/
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
launch_jar --server.port=8081
|
||||
await_app http://127.0.0.1:8081/
|
||||
curl -s http://127.0.0.1:8081/
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
echo 'JAVA_OPTS=-Dserver.port=8081' > app.conf
|
||||
launch_jar
|
||||
await_app http://127.0.0.1:8081/
|
||||
curl -s http://127.0.0.1:8081/
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
source ./test-functions.sh
|
||||
echo 'RUN_ARGS=--server.port=8081' > app.conf
|
||||
launch_jar
|
||||
await_app http://127.0.0.1:8081/
|
||||
curl -s http://127.0.0.1:8081/
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
await_app() {
|
||||
if [ -z $1 ]
|
||||
then
|
||||
url=http://127.0.0.1:8080
|
||||
else
|
||||
url=$1
|
||||
fi
|
||||
end=$(date +%s)
|
||||
let "end+=30"
|
||||
until curl -s $url > /dev/null
|
||||
do
|
||||
now=$(date +%s)
|
||||
if [[ $now -ge $end ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
launch_jar() {
|
||||
./app.jar $@ &
|
||||
}
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.loader.tools;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link LaunchScript}. Provides the default Spring Boot launch
|
||||
* script or can load a specific script File. Also support mustache style template
|
||||
* expansion of the form <code>{{name:default}}</code>.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Justin Rosenberg
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class DefaultLaunchScript implements LaunchScript {
|
||||
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{(\\w+)(:.*?)?}}(?!})");
|
||||
|
||||
private static final Set<String> FILE_PATH_KEYS = Collections.singleton("inlinedConfScript");
|
||||
|
||||
private final String content;
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultLaunchScript} instance.
|
||||
* @param file the source script file or {@code null} to use the default
|
||||
* @param properties an optional set of script properties used for variable expansion
|
||||
* @throws IOException if the script cannot be loaded
|
||||
*/
|
||||
public DefaultLaunchScript(@Nullable File file, @Nullable Map<?, ?> properties) throws IOException {
|
||||
String content = loadContent(file);
|
||||
this.content = expandPlaceholders(content, properties);
|
||||
}
|
||||
|
||||
private String loadContent(@Nullable File file) throws IOException {
|
||||
if (file == null) {
|
||||
InputStream stream = getClass().getResourceAsStream("launch.script");
|
||||
Assert.state(stream != null, "Unable to load resource 'launch.script'");
|
||||
return loadContent(stream);
|
||||
}
|
||||
return loadContent(new FileInputStream(file));
|
||||
}
|
||||
|
||||
private String loadContent(InputStream inputStream) throws IOException {
|
||||
try (inputStream) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
copy(inputStream, outputStream);
|
||||
return outputStream.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
private String expandPlaceholders(String content, @Nullable Map<?, ?> properties) throws IOException {
|
||||
StringBuilder expanded = new StringBuilder();
|
||||
Matcher matcher = PLACEHOLDER_PATTERN.matcher(content);
|
||||
while (matcher.find()) {
|
||||
String name = matcher.group(1);
|
||||
final String value;
|
||||
String defaultValue = matcher.group(2);
|
||||
if (properties != null && properties.containsKey(name)) {
|
||||
Object propertyValue = properties.get(name);
|
||||
if (FILE_PATH_KEYS.contains(name)) {
|
||||
value = parseFilePropertyValue(propertyValue);
|
||||
}
|
||||
else {
|
||||
value = propertyValue.toString();
|
||||
}
|
||||
}
|
||||
else {
|
||||
value = (defaultValue != null) ? defaultValue.substring(1) : matcher.group(0);
|
||||
}
|
||||
matcher.appendReplacement(expanded, value.replace("$", "\\$"));
|
||||
}
|
||||
matcher.appendTail(expanded);
|
||||
return expanded.toString();
|
||||
}
|
||||
|
||||
private String parseFilePropertyValue(Object propertyValue) throws IOException {
|
||||
if (propertyValue instanceof File file) {
|
||||
return loadContent(file);
|
||||
}
|
||||
return loadContent(new File(propertyValue.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
return this.content.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
+2
-19
@@ -57,31 +57,14 @@ public class JarWriter extends AbstractJarWriter implements AutoCloseable {
|
||||
/**
|
||||
* Create a new {@link JarWriter} instance.
|
||||
* @param file the file to write
|
||||
* @param launchScript an optional launch script to prepend to the front of the jar
|
||||
* @throws IOException if the file cannot be opened
|
||||
* @throws FileNotFoundException if the file cannot be found
|
||||
*/
|
||||
public JarWriter(File file, @Nullable LaunchScript launchScript) throws FileNotFoundException, IOException {
|
||||
this(file, launchScript, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JarWriter} instance.
|
||||
* @param file the file to write
|
||||
* @param launchScript an optional launch script to prepend to the front of the jar
|
||||
* @param lastModifiedTime an optional last modified time to apply to the written
|
||||
* entries
|
||||
* @throws IOException if the file cannot be opened
|
||||
* @throws FileNotFoundException if the file cannot be found
|
||||
* @since 2.3.0
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public JarWriter(File file, @Nullable LaunchScript launchScript, @Nullable FileTime lastModifiedTime)
|
||||
throws FileNotFoundException, IOException {
|
||||
public JarWriter(File file, @Nullable FileTime lastModifiedTime) throws FileNotFoundException, IOException {
|
||||
this.jarOutputStream = new JarArchiveOutputStream(new FileOutputStream(file));
|
||||
if (launchScript != null) {
|
||||
this.jarOutputStream.writePreamble(launchScript.toByteArray());
|
||||
file.setExecutable(true);
|
||||
}
|
||||
this.jarOutputStream.setEncoding("UTF-8");
|
||||
this.lastModifiedTime = lastModifiedTime;
|
||||
}
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.loader.tools;
|
||||
|
||||
/**
|
||||
* A script that can be prepended to the front of a JAR file to make it executable.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LaunchScript {
|
||||
|
||||
/**
|
||||
* The content of the launch script as a byte array.
|
||||
* @return the script bytes
|
||||
*/
|
||||
byte[] toByteArray();
|
||||
|
||||
}
|
||||
+6
-21
@@ -101,28 +101,13 @@ public class Repackager extends Packager {
|
||||
* {@literal java -jar}'.
|
||||
* @param destination the destination file (may be the same as the source)
|
||||
* @param libraries the libraries required to run the archive
|
||||
* @param launchScript an optional launch script prepended to the front of the jar
|
||||
* @throws IOException if the file cannot be repackaged
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public void repackage(File destination, Libraries libraries, @Nullable LaunchScript launchScript)
|
||||
throws IOException {
|
||||
repackage(destination, libraries, launchScript, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repackage to the given destination so that it can be launched using '
|
||||
* {@literal java -jar}'.
|
||||
* @param destination the destination file (may be the same as the source)
|
||||
* @param libraries the libraries required to run the archive
|
||||
* @param launchScript an optional launch script prepended to the front of the jar
|
||||
* @param lastModifiedTime an optional last modified time to apply to the archive and
|
||||
* its contents
|
||||
* @throws IOException if the file cannot be repackaged
|
||||
* @since 2.3.0
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public void repackage(File destination, Libraries libraries, @Nullable LaunchScript launchScript,
|
||||
@Nullable FileTime lastModifiedTime) throws IOException {
|
||||
public void repackage(File destination, Libraries libraries, @Nullable FileTime lastModifiedTime)
|
||||
throws IOException {
|
||||
Assert.isTrue(destination != null && !destination.isDirectory(), "Invalid destination");
|
||||
getLayout(); // get layout early
|
||||
destination = destination.getAbsoluteFile();
|
||||
@@ -139,7 +124,7 @@ public class Repackager extends Packager {
|
||||
destination.delete();
|
||||
try {
|
||||
try (JarFile sourceJar = new JarFile(workingSource)) {
|
||||
repackage(sourceJar, destination, libraries, launchScript, lastModifiedTime);
|
||||
repackage(sourceJar, destination, libraries, lastModifiedTime);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -150,8 +135,8 @@ public class Repackager extends Packager {
|
||||
}
|
||||
|
||||
private void repackage(JarFile sourceJar, File destination, Libraries libraries,
|
||||
@Nullable LaunchScript launchScript, @Nullable FileTime lastModifiedTime) throws IOException {
|
||||
try (JarWriter writer = new JarWriter(destination, launchScript, lastModifiedTime)) {
|
||||
@Nullable FileTime lastModifiedTime) throws IOException {
|
||||
try (JarWriter writer = new JarWriter(destination, lastModifiedTime)) {
|
||||
write(sourceJar, libraries, writer, lastModifiedTime != null);
|
||||
}
|
||||
if (lastModifiedTime != null) {
|
||||
|
||||
-309
@@ -1,309 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# . ____ _ __ _ _
|
||||
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
# ' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
# =========|_|==============|___/=/_/_/_/
|
||||
# :: Spring Boot Startup Script ::
|
||||
#
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: {{initInfoProvides:spring-boot-application}}
|
||||
# Required-Start: {{initInfoRequiredStart:$remote_fs $syslog $network}}
|
||||
# Required-Stop: {{initInfoRequiredStop:$remote_fs $syslog $network}}
|
||||
# Default-Start: {{initInfoDefaultStart:2 3 4 5}}
|
||||
# Default-Stop: {{initInfoDefaultStop:0 1 6}}
|
||||
# Short-Description: {{initInfoShortDescription:Spring Boot Application}}
|
||||
# Description: {{initInfoDescription:Spring Boot Application}}
|
||||
# chkconfig: {{initInfoChkconfig:2345 99 01}}
|
||||
### END INIT INFO
|
||||
|
||||
[[ -n "$DEBUG" ]] && set -x
|
||||
|
||||
# Initialize variables that cannot be provided by a .conf file
|
||||
WORKING_DIR="$(pwd)"
|
||||
# shellcheck disable=SC2153
|
||||
[[ -n "$JARFILE" ]] && jarfile="$JARFILE"
|
||||
[[ -n "$APP_NAME" ]] && identity="$APP_NAME"
|
||||
|
||||
# Follow symlinks to find the real jar and detect init.d script
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
[[ -z "$jarfile" ]] && jarfile=$(pwd)/$(basename "$0")
|
||||
while [[ -L "$jarfile" ]]; do
|
||||
if [[ "$jarfile" =~ init\.d ]]; then
|
||||
init_script=$(basename "$jarfile")
|
||||
else
|
||||
configfile="${jarfile%.*}.conf"
|
||||
# shellcheck source=/dev/null
|
||||
[[ -r ${configfile} ]] && source "${configfile}"
|
||||
fi
|
||||
jarfile=$(readlink "$jarfile")
|
||||
cd "$(dirname "$jarfile")" || exit 1
|
||||
jarfile=$(pwd)/$(basename "$jarfile")
|
||||
done
|
||||
jarfolder="$( (cd "$(dirname "$jarfile")" && pwd -P) )"
|
||||
cd "$WORKING_DIR" || exit 1
|
||||
|
||||
# Inline script specified in build properties
|
||||
{{inlinedConfScript:}}
|
||||
|
||||
# Source any config file
|
||||
configfile="$(basename "${jarfile%.*}.conf")"
|
||||
|
||||
# Initialize CONF_FOLDER location defaulting to jarfolder
|
||||
[[ -z "$CONF_FOLDER" ]] && CONF_FOLDER="{{confFolder:${jarfolder}}}"
|
||||
# shellcheck source=/dev/null
|
||||
[[ -r "${CONF_FOLDER}/${configfile}" ]] && source "${CONF_FOLDER}/${configfile}"
|
||||
|
||||
# ANSI Colors
|
||||
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
|
||||
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
|
||||
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
|
||||
|
||||
# Initialize PID/LOG locations if they weren't provided by the config file
|
||||
[[ -z "$PID_FOLDER" ]] && PID_FOLDER="{{pidFolder:/var/run}}"
|
||||
[[ -z "$LOG_FOLDER" ]] && LOG_FOLDER="{{logFolder:/var/log}}"
|
||||
! [[ "$PID_FOLDER" == /* ]] && PID_FOLDER="$(dirname "$jarfile")"/"$PID_FOLDER"
|
||||
! [[ "$LOG_FOLDER" == /* ]] && LOG_FOLDER="$(dirname "$jarfile")"/"$LOG_FOLDER"
|
||||
! [[ -x "$PID_FOLDER" ]] && echoYellow "PID_FOLDER $PID_FOLDER does not exist. Falling back to /tmp" && PID_FOLDER="/tmp"
|
||||
! [[ -x "$LOG_FOLDER" ]] && echoYellow "LOG_FOLDER $LOG_FOLDER does not exist. Falling back to /tmp" && LOG_FOLDER="/tmp"
|
||||
|
||||
# Set up defaults
|
||||
[[ -z "$MODE" ]] && MODE="{{mode:auto}}" # modes are "auto", "service" or "run"
|
||||
[[ -z "$USE_START_STOP_DAEMON" ]] && USE_START_STOP_DAEMON="{{useStartStopDaemon:true}}"
|
||||
|
||||
# Create an identity for log/pid files
|
||||
if [[ -z "$identity" ]]; then
|
||||
if [[ -n "$init_script" ]]; then
|
||||
identity="${init_script}"
|
||||
else
|
||||
identity=$(basename "${jarfile%.*}")_${jarfolder//\//}
|
||||
fi
|
||||
fi
|
||||
|
||||
# Initialize log file name if not provided by the config file
|
||||
[[ -z "$LOG_FILENAME" ]] && LOG_FILENAME="{{logFilename:${identity}.log}}"
|
||||
|
||||
# Initialize stop wait time if not provided by the config file
|
||||
[[ -z "$STOP_WAIT_TIME" ]] && STOP_WAIT_TIME="{{stopWaitTime:60}}"
|
||||
|
||||
# Utility functions
|
||||
checkPermissions() {
|
||||
touch "$pid_file" &> /dev/null || { echoRed "Operation not permitted (cannot access pid file)"; return 4; }
|
||||
touch "$log_file" &> /dev/null || { echoRed "Operation not permitted (cannot access log file)"; return 4; }
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
ps -p "$1" &> /dev/null
|
||||
}
|
||||
|
||||
await_file() {
|
||||
end=$(date +%s)
|
||||
let "end+=10"
|
||||
while [[ ! -s "$1" ]]
|
||||
do
|
||||
now=$(date +%s)
|
||||
if [[ $now -ge $end ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
# Determine the script mode
|
||||
action="run"
|
||||
if [[ "$MODE" == "auto" && -n "$init_script" ]] || [[ "$MODE" == "service" ]]; then
|
||||
action="$1"
|
||||
shift
|
||||
fi
|
||||
|
||||
# Build the pid and log filenames
|
||||
PID_FOLDER="$PID_FOLDER/${identity}"
|
||||
pid_file="$PID_FOLDER/{{pidFilename:${identity}.pid}}"
|
||||
log_file="$LOG_FOLDER/$LOG_FILENAME"
|
||||
|
||||
# Determine the user to run as if we are root
|
||||
# shellcheck disable=SC2012
|
||||
[[ $(id -u) == "0" ]] && run_user=$(ls -ld "$jarfile" | awk '{print $3}')
|
||||
|
||||
# Ensure the user actually exists
|
||||
id -u "$run_user" &> /dev/null || unset run_user
|
||||
|
||||
# Run as user specified in RUN_AS_USER
|
||||
if [[ -n "$RUN_AS_USER" ]]; then
|
||||
if ! [[ "$action" =~ ^(status|run)$ ]]; then
|
||||
id -u "$RUN_AS_USER" || {
|
||||
echoRed "Cannot run as '$RUN_AS_USER': no such user"
|
||||
exit 2
|
||||
}
|
||||
[[ $(id -u) == 0 ]] || {
|
||||
echoRed "Cannot run as '$RUN_AS_USER': current user is not root"
|
||||
exit 4
|
||||
}
|
||||
fi
|
||||
run_user="$RUN_AS_USER"
|
||||
fi
|
||||
|
||||
# Issue a warning if the application will run as root
|
||||
[[ $(id -u ${run_user}) == "0" ]] && { echoYellow "Application is running as root (UID 0). This is considered insecure."; }
|
||||
|
||||
# Find Java
|
||||
if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
|
||||
javaexe="$JAVA_HOME/bin/java"
|
||||
elif type -p java > /dev/null 2>&1; then
|
||||
javaexe=$(type -p java)
|
||||
elif [[ -x "/usr/bin/java" ]]; then
|
||||
javaexe="/usr/bin/java"
|
||||
else
|
||||
echo "Unable to find Java"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
arguments=(-Dsun.misc.URLClassPath.disableJarChecking=true $JAVA_OPTS -jar "$jarfile" $RUN_ARGS "$@")
|
||||
|
||||
# Action functions
|
||||
start() {
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
pid=$(cat "$pid_file")
|
||||
isRunning "$pid" && { echoYellow "Already running [$pid]"; return 0; }
|
||||
fi
|
||||
do_start "$@"
|
||||
}
|
||||
|
||||
do_start() {
|
||||
working_dir=$(dirname "$jarfile")
|
||||
pushd "$working_dir" > /dev/null
|
||||
if [[ ! -e "$PID_FOLDER" ]]; then
|
||||
mkdir -p "$PID_FOLDER" &> /dev/null
|
||||
if [[ -n "$run_user" ]]; then
|
||||
chown "$run_user" "$PID_FOLDER"
|
||||
fi
|
||||
fi
|
||||
if [[ ! -e "$log_file" ]]; then
|
||||
touch "$log_file" &> /dev/null
|
||||
if [[ -n "$run_user" ]]; then
|
||||
chown "$run_user" "$log_file"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$run_user" ]]; then
|
||||
checkPermissions || return $?
|
||||
if [ $USE_START_STOP_DAEMON = true ] && type start-stop-daemon > /dev/null 2>&1; then
|
||||
start-stop-daemon --start --quiet \
|
||||
--chuid "$run_user" \
|
||||
--name "$identity" \
|
||||
--make-pidfile --pidfile "$pid_file" \
|
||||
--background --no-close \
|
||||
--startas "$javaexe" \
|
||||
--chdir "$working_dir" \
|
||||
-- "${arguments[@]}" \
|
||||
>> "$log_file" 2>&1
|
||||
await_file "$pid_file"
|
||||
else
|
||||
su -s /bin/sh -c "$javaexe $(printf "\"%s\" " "${arguments[@]}") >> \"$log_file\" 2>&1 & echo \$!" "$run_user" > "$pid_file"
|
||||
fi
|
||||
pid=$(cat "$pid_file")
|
||||
else
|
||||
checkPermissions || return $?
|
||||
"$javaexe" "${arguments[@]}" >> "$log_file" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
echo "$pid" > "$pid_file"
|
||||
fi
|
||||
[[ -z $pid ]] && { echoRed "Failed to start"; return 1; }
|
||||
echoGreen "Started [$pid]"
|
||||
}
|
||||
|
||||
stop() {
|
||||
working_dir=$(dirname "$jarfile")
|
||||
pushd "$working_dir" > /dev/null
|
||||
[[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
|
||||
pid=$(cat "$pid_file")
|
||||
isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
|
||||
do_stop "$pid" "$pid_file"
|
||||
}
|
||||
|
||||
do_stop() {
|
||||
kill "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
|
||||
for ((i = 1; i <= STOP_WAIT_TIME; i++)); do
|
||||
isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
|
||||
[[ $i -eq STOP_WAIT_TIME/2 ]] && kill "$1" &> /dev/null
|
||||
sleep 1
|
||||
done
|
||||
echoRed "Unable to kill process $1";
|
||||
return 1;
|
||||
}
|
||||
|
||||
force_stop() {
|
||||
[[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
|
||||
pid=$(cat "$pid_file")
|
||||
isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
|
||||
do_force_stop "$pid" "$pid_file"
|
||||
}
|
||||
|
||||
do_force_stop() {
|
||||
kill -9 "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
|
||||
for ((i = 1; i <= STOP_WAIT_TIME; i++)); do
|
||||
isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
|
||||
[[ $i -eq STOP_WAIT_TIME/2 ]] && kill -9 "$1" &> /dev/null
|
||||
sleep 1
|
||||
done
|
||||
echoRed "Unable to kill process $1";
|
||||
return 1;
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop && start
|
||||
}
|
||||
|
||||
force_reload() {
|
||||
working_dir=$(dirname "$jarfile")
|
||||
pushd "$working_dir" > /dev/null
|
||||
[[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; return 7; }
|
||||
pid=$(cat "$pid_file")
|
||||
rm -f "$pid_file"
|
||||
isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 7; }
|
||||
do_stop "$pid" "$pid_file"
|
||||
do_start
|
||||
}
|
||||
|
||||
status() {
|
||||
working_dir=$(dirname "$jarfile")
|
||||
pushd "$working_dir" > /dev/null
|
||||
[[ -f "$pid_file" ]] || { echoRed "Not running"; return 3; }
|
||||
pid=$(cat "$pid_file")
|
||||
isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 1; }
|
||||
echoGreen "Running [$pid]"
|
||||
return 0
|
||||
}
|
||||
|
||||
run() {
|
||||
pushd "$(dirname "$jarfile")" > /dev/null
|
||||
"$javaexe" "${arguments[@]}"
|
||||
result=$?
|
||||
popd > /dev/null
|
||||
return "$result"
|
||||
}
|
||||
|
||||
# Call the appropriate action function
|
||||
case "$action" in
|
||||
start)
|
||||
start "$@"; exit $?;;
|
||||
stop)
|
||||
stop "$@"; exit $?;;
|
||||
force-stop)
|
||||
force_stop "$@"; exit $?;;
|
||||
restart)
|
||||
restart "$@"; exit $?;;
|
||||
force-reload)
|
||||
force_reload "$@"; exit $?;;
|
||||
status)
|
||||
status "$@"; exit $?;;
|
||||
run)
|
||||
run "$@"; exit $?;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|force-stop|restart|force-reload|status|run}"; exit 1;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
-238
@@ -1,238 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.loader.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultLaunchScript}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Justin Rosenberg
|
||||
*/
|
||||
class DefaultLaunchScriptTests {
|
||||
|
||||
@TempDir
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
File tempDir;
|
||||
|
||||
@Test
|
||||
void loadsDefaultScript() throws Exception {
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(null, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).contains("Spring Boot Startup Script");
|
||||
}
|
||||
|
||||
@Test
|
||||
void logFilenameCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("logFilename");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pidFilenameCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("pidFilename");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoProvidesCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("initInfoProvides");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoRequiredStartCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("initInfoRequiredStart");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoRequiredStopCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("initInfoRequiredStop");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoDefaultStartCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("initInfoDefaultStart");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoDefaultStopCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("initInfoDefaultStop");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoShortDescriptionCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("initInfoShortDescription");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoDescriptionCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("initInfoDescription");
|
||||
}
|
||||
|
||||
@Test
|
||||
void initInfoChkconfigCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("initInfoChkconfig");
|
||||
}
|
||||
|
||||
@Test
|
||||
void modeCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("mode");
|
||||
}
|
||||
|
||||
@Test
|
||||
void useStartStopDaemonCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("useStartStopDaemon");
|
||||
}
|
||||
|
||||
@Test
|
||||
void logFolderCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("logFolder");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pidFolderCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("pidFolder");
|
||||
}
|
||||
|
||||
@Test
|
||||
void confFolderCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("confFolder");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stopWaitTimeCanBeReplaced() throws Exception {
|
||||
assertThatPlaceholderCanBeReplaced("stopWaitTime");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inlinedConfScriptFileLoad() throws IOException {
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(null,
|
||||
createProperties("inlinedConfScript:src/test/resources/example.script"));
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).contains("FOO=BAR");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultForUseStartStopDaemonIsTrue() throws Exception {
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(null, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).contains("USE_START_STOP_DAEMON=\"true\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultForModeIsAuto() throws Exception {
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(null, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).contains("MODE=\"auto\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultForStopWaitTimeIs60() throws Exception {
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(null, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).contains("STOP_WAIT_TIME=\"60\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadFromFile() throws Exception {
|
||||
File file = new File(this.tempDir, "script");
|
||||
FileCopyUtils.copy("ABC".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).isEqualTo("ABC");
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandVariables() throws Exception {
|
||||
File file = new File(this.tempDir, "script");
|
||||
FileCopyUtils.copy("h{{a}}ll{{b}}".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, createProperties("a:e", "b:o"));
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandVariablesMultiLine() throws Exception {
|
||||
File file = new File(this.tempDir, "script");
|
||||
FileCopyUtils.copy("h{{a}}l\nl{{b}}".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, createProperties("a:e", "b:o"));
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).isEqualTo("hel\nlo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandVariablesWithDefaults() throws Exception {
|
||||
File file = new File(this.tempDir, "script");
|
||||
FileCopyUtils.copy("h{{a:e}}ll{{b:o}}".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandVariablesCanDefaultToBlank() throws Exception {
|
||||
File file = new File(this.tempDir, "script");
|
||||
FileCopyUtils.copy("s{{p:}}{{r:}}ing".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).isEqualTo("sing");
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandVariablesWithDefaultsOverride() throws Exception {
|
||||
File file = new File(this.tempDir, "script");
|
||||
FileCopyUtils.copy("h{{a:e}}ll{{b:o}}".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, createProperties("a:a"));
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).isEqualTo("hallo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandVariablesMissingAreUnchanged() throws Exception {
|
||||
File file = new File(this.tempDir, "script");
|
||||
FileCopyUtils.copy("h{{a}}ll{{b}}".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).isEqualTo("h{{a}}ll{{b}}");
|
||||
}
|
||||
|
||||
private void assertThatPlaceholderCanBeReplaced(String placeholder) throws Exception {
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(null, createProperties(placeholder + ":__test__"));
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content).contains("__test__");
|
||||
}
|
||||
|
||||
private Map<?, ?> createProperties(String... pairs) {
|
||||
Map<Object, Object> properties = new HashMap<>();
|
||||
for (String pair : pairs) {
|
||||
String[] keyValue = pair.split(":");
|
||||
properties.put(keyValue[0], keyValue[1]);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-43
@@ -19,9 +19,7 @@ package org.springframework.boot.loader.tools;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
@@ -156,30 +154,6 @@ class RepackagerTests extends AbstractPackagerTests<Repackager> {
|
||||
assertThat(hasLauncherClasses(this.destination)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addLauncherScript() throws Exception {
|
||||
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
|
||||
File source = this.testJarFile.getFile();
|
||||
Repackager repackager = createRepackager(source, true);
|
||||
LaunchScript script = new MockLauncherScript("ABC");
|
||||
assertThat(this.destination).isNotNull();
|
||||
repackager.repackage(this.destination, NO_LIBRARIES, script);
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(this.destination);
|
||||
assertThat(new String(bytes)).startsWith("ABC");
|
||||
assertThat(hasLauncherClasses(source)).isFalse();
|
||||
assertThat(hasLauncherClasses(this.destination)).isTrue();
|
||||
try (ZipFile zipFile = ZipFile.builder().setFile(this.destination).get()) {
|
||||
assertThat(zipFile.getEntries().hasMoreElements()).isTrue();
|
||||
}
|
||||
try {
|
||||
assertThat(Files.getPosixFilePermissions(this.destination.toPath()))
|
||||
.contains(PosixFilePermission.OWNER_EXECUTE);
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
// Probably running the test on Windows
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void allLoaderDirectoriesAndFilesUseSameTimestamp() throws IOException {
|
||||
this.testJarFile.addClass("A.class", ClassWithMainMethod.class);
|
||||
@@ -208,7 +182,7 @@ class RepackagerTests extends AbstractPackagerTests<Repackager> {
|
||||
Repackager repackager = createRepackager(this.testJarFile.getFile(), true);
|
||||
long timestamp = OffsetDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
assertThat(this.destination).isNotNull();
|
||||
repackager.repackage(this.destination, NO_LIBRARIES, null, FileTime.fromMillis(timestamp));
|
||||
repackager.repackage(this.destination, NO_LIBRARIES, FileTime.fromMillis(timestamp));
|
||||
long offsetTimestamp = DefaultTimeZoneOffset.INSTANCE.removeFrom(timestamp);
|
||||
for (ZipArchiveEntry entry : getAllPackagedEntries()) {
|
||||
assertThat(entry.getTime()).isEqualTo(offsetTimestamp);
|
||||
@@ -223,7 +197,7 @@ class RepackagerTests extends AbstractPackagerTests<Repackager> {
|
||||
ClassWithMainMethod.class);
|
||||
Repackager repackager = createRepackager(this.testJarFile.getFile(), true);
|
||||
assertThat(this.destination).isNotNull();
|
||||
repackager.repackage(this.destination, NO_LIBRARIES, null, null);
|
||||
repackager.repackage(this.destination, NO_LIBRARIES, null);
|
||||
stopWatch.stop();
|
||||
assertThat(stopWatch.getTotalTimeMillis()).isLessThan(5000);
|
||||
}
|
||||
@@ -306,21 +280,6 @@ class RepackagerTests extends AbstractPackagerTests<Repackager> {
|
||||
}
|
||||
}
|
||||
|
||||
static class MockLauncherScript implements LaunchScript {
|
||||
|
||||
private final byte[] bytes;
|
||||
|
||||
MockLauncherScript(String script) {
|
||||
this.bytes = script.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestLayoutFactory implements LayoutFactory {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -472,7 +472,6 @@ include ":smoke-test:spring-boot-smoke-test-xml"
|
||||
include ":integration-test:spring-boot-actuator-integration-tests"
|
||||
include ":integration-test:spring-boot-configuration-processor-integration-tests"
|
||||
include ":integration-test:spring-boot-integration-tests"
|
||||
include ":integration-test:spring-boot-launch-script-integration-tests"
|
||||
include ":integration-test:spring-boot-loader-integration-tests"
|
||||
include ":integration-test:spring-boot-server-integration-tests"
|
||||
include ":integration-test:spring-boot-sni-integration-tests"
|
||||
|
||||
Reference in New Issue
Block a user