Browse Source

Adjust fat jar central directory to account for launch script

An upgrade to Apache Commons Compress allows the build plugins to write
the launch script to the fat jar as a proper preamble, making the file
compatible with more jar and zip tooling.

Fixes gh-22336
pull/27376/head
Scott Frederick 5 years ago
parent
commit
9f001efa29
  1. 2
      spring-boot-project/spring-boot-parent/build.gradle
  2. 9
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/ZipFileTarArchive.java
  3. 3
      spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc
  4. 2
      spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging.adoc
  5. 7
      spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java
  6. 9
      spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java
  7. 12
      spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java
  8. 0
      spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithLaunchScript.gradle
  9. 27
      spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java
  10. 6
      spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java
  11. 5
      spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/AbstractLaunchScriptIntegrationTests.java

2
spring-boot-project/spring-boot-parent/build.gradle

@ -27,7 +27,7 @@ bom { @@ -27,7 +27,7 @@ bom {
]
}
}
library("Commons Compress", "1.20") {
library("Commons Compress", "1.21") {
group("org.apache.commons") {
modules = [
"commons-compress"

9
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/ZipFileTarArchive.java

@ -74,13 +74,12 @@ public class ZipFileTarArchive implements TarArchive { @@ -74,13 +74,12 @@ public class ZipFileTarArchive implements TarArchive {
tar.finish();
}
private void assertArchiveHasEntries(File jarFile) {
try (ZipFile zipFile = new ZipFile(jarFile)) {
Assert.state(zipFile.getEntries().hasMoreElements(), () -> "File '" + jarFile
+ "' is not compatible with buildpacks; ensure jar file is valid and launch script is not enabled");
private void assertArchiveHasEntries(File file) {
try (ZipFile zipFile = new ZipFile(file)) {
Assert.state(zipFile.getEntries().hasMoreElements(), () -> "Archive file '" + file + "' is not valid");
}
catch (IOException ex) {
throw new IllegalStateException("File '" + jarFile + "' is not readable", ex);
throw new IllegalStateException("File '" + file + "' is not readable", ex);
}
}

3
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc

@ -8,9 +8,6 @@ See the {buildpacks-reference}/reference/spec/platform-api/#users[CNB specificat @@ -8,9 +8,6 @@ See the {buildpacks-reference}/reference/spec/platform-api/#users[CNB specificat
The task is automatically created when the `java` or `war` plugin is applied and is an instance of {boot-build-image-javadoc}[`BootBuildImage`].
NOTE: The `bootBuildImage` task can not be used with a <<packaging-executable.configuring.launch-script, fully executable Spring Boot archive>> that includes a launch script.
Disable launch script configuration in the `bootJar` task when building a jar file that is intended to be used with `bootBuildImage`.
[[build-image.docker-daemon]]

2
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging.adoc

@ -206,7 +206,7 @@ On Unix-like platforms, this launch script allows the archive to be run directly @@ -206,7 +206,7 @@ On Unix-like platforms, this launch script allows the archive to be run directly
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`, deploying it to a servlet container, or including it in an OCI image.
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:

7
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java

@ -135,8 +135,8 @@ class BootZipCopyAction implements CopyAction { @@ -135,8 +135,8 @@ class BootZipCopyAction implements CopyAction {
}
private void writeArchive(CopyActionProcessingStream copyActions, OutputStream output) throws IOException {
writeLaunchScriptIfNecessary(output);
ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(output);
writeLaunchScriptIfNecessary(zipOutput);
try {
setEncodingIfNecessary(zipOutput);
Processor processor = new Processor(zipOutput);
@ -148,15 +148,14 @@ class BootZipCopyAction implements CopyAction { @@ -148,15 +148,14 @@ class BootZipCopyAction implements CopyAction {
}
}
private void writeLaunchScriptIfNecessary(OutputStream outputStream) {
private void writeLaunchScriptIfNecessary(ZipArchiveOutputStream outputStream) {
if (this.launchScript == null) {
return;
}
try {
File file = this.launchScript.getScript();
Map<String, String> properties = this.launchScript.getProperties();
outputStream.write(new DefaultLaunchScript(file, properties).toByteArray());
outputStream.flush();
outputStream.writePreamble(new DefaultLaunchScript(file, properties).toByteArray());
this.output.setExecutable(true);
}
catch (IOException ex) {

9
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java

@ -279,11 +279,14 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> { @@ -279,11 +279,14 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
properties.put("initInfoProvides", this.task.getArchiveBaseName().get());
properties.put("initInfoShortDescription", this.project.getDescription());
properties.put("initInfoDescription", this.project.getDescription());
assertThat(Files.readAllBytes(this.task.getArchiveFile().get().getAsFile().toPath()))
File archiveFile = this.task.getArchiveFile().get().getAsFile();
assertThat(Files.readAllBytes(archiveFile.toPath()))
.startsWith(new DefaultLaunchScript(null, properties).toByteArray());
try (ZipFile zipFile = new ZipFile(archiveFile)) {
assertThat(zipFile.getEntries().hasMoreElements()).isTrue();
}
try {
Set<PosixFilePermission> permissions = Files
.getPosixFilePermissions(this.task.getArchiveFile().get().getAsFile().toPath());
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(archiveFile.toPath());
assertThat(permissions).contains(PosixFilePermission.OWNER_EXECUTE);
}
catch (UnsupportedOperationException ex) {

12
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java

@ -235,12 +235,16 @@ class BootBuildImageIntegrationTests { @@ -235,12 +235,16 @@ class BootBuildImageIntegrationTests {
}
@TestTemplate
void failsWithLaunchScript() throws IOException {
void buildsImageWithLaunchScript() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage");
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.FAILED);
assertThat(result.getOutput()).contains("not compatible with buildpacks");
BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
String projectName = this.gradleBuild.getProjectDir().getName();
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
assertThat(result.getOutput()).contains("---> Test Info buildpack building");
assertThat(result.getOutput()).contains("---> Test Info buildpack done");
removeImage(projectName);
}
@TestTemplate

0
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithLaunchScript.gradle → spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithLaunchScript.gradle

27
spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -20,12 +20,7 @@ import java.io.File; @@ -20,12 +20,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
@ -39,6 +34,7 @@ import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream; @@ -39,6 +34,7 @@ import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Scott Frederick
* @since 1.0.0
*/
public class JarWriter extends AbstractJarWriter implements AutoCloseable {
@ -80,28 +76,15 @@ public class JarWriter extends AbstractJarWriter implements AutoCloseable { @@ -80,28 +76,15 @@ public class JarWriter extends AbstractJarWriter implements AutoCloseable {
*/
public JarWriter(File file, LaunchScript launchScript, FileTime lastModifiedTime)
throws FileNotFoundException, IOException {
FileOutputStream fileOutputStream = new FileOutputStream(file);
this.jarOutputStream = new JarArchiveOutputStream(new FileOutputStream(file));
if (launchScript != null) {
fileOutputStream.write(launchScript.toByteArray());
setExecutableFilePermission(file);
this.jarOutputStream.writePreamble(launchScript.toByteArray());
file.setExecutable(true);
}
this.jarOutputStream = new JarArchiveOutputStream(fileOutputStream);
this.jarOutputStream.setEncoding("UTF-8");
this.lastModifiedTime = lastModifiedTime;
}
private void setExecutableFilePermission(File file) {
try {
Path path = file.toPath();
Set<PosixFilePermission> permissions = new HashSet<>(Files.getPosixFilePermissions(path));
permissions.add(PosixFilePermission.OWNER_EXECUTE);
Files.setPosixFilePermissions(path, permissions);
}
catch (Throwable ex) {
// Ignore and continue creating the jar
}
}
@Override
protected void writeToArchive(ZipEntry entry, EntryWriter entryWriter) throws IOException {
JarArchiveEntry jarEntry = asJarArchiveEntry(entry);

6
spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -49,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException @@ -49,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Scott Frederick
*/
class RepackagerTests extends AbstractPackagerTests<Repackager> {
@ -159,6 +160,9 @@ class RepackagerTests extends AbstractPackagerTests<Repackager> { @@ -159,6 +160,9 @@ class RepackagerTests extends AbstractPackagerTests<Repackager> {
assertThat(new String(bytes)).startsWith("ABC");
assertThat(hasLauncherClasses(source)).isFalse();
assertThat(hasLauncherClasses(this.destination)).isTrue();
try (ZipFile zipFile = new ZipFile(this.destination)) {
assertThat(zipFile.getEntries().hasMoreElements()).isTrue();
}
try {
assertThat(Files.getPosixFilePermissions(this.destination.toPath()))
.contains(PosixFilePermission.OWNER_EXECUTE);

5
spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/AbstractLaunchScriptIntegrationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -106,7 +106,8 @@ abstract class AbstractLaunchScriptIntegrationTests { @@ -106,7 +106,8 @@ abstract class AbstractLaunchScriptIntegrationTests {
withCopyFileToContainer(
MountableFile.forHostPath("src/intTest/resources/scripts/" + scriptsDir + testScript),
"/" + testScript);
withCommand("/bin/bash", "-c", "chmod +x " + 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)));
}

Loading…
Cancel
Save