Skip to content

Commit

Permalink
Adjust fat jar central directory to account for launch script
Browse files Browse the repository at this point in the history
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
  • Loading branch information
scottfrederick committed Jul 19, 2021
1 parent b5ef5a2 commit 9f001ef
Show file tree
Hide file tree
Showing 11 changed files with 36 additions and 46 deletions.
2 changes: 1 addition & 1 deletion spring-boot-project/spring-boot-parent/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ bom {
]
}
}
library("Commons Compress", "1.20") {
library("Commons Compress", "1.21") {
group("org.apache.commons") {
modules = [
"commons-compress"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,12 @@ public void writeTo(OutputStream outputStream) throws IOException {
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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ private void writeArchive(CopyActionProcessingStream copyActions) throws IOExcep
}

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);
Expand All @@ -148,15 +148,14 @@ private void writeArchive(CopyActionProcessingStream copyActions, OutputStream o
}
}

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,11 +279,14 @@ void launchScriptCanBePrepended() throws IOException {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,16 @@ void buildsImageWithBinding() throws IOException {
}

@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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -20,12 +20,7 @@
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;

Expand All @@ -39,6 +34,7 @@
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Scott Frederick
* @since 1.0.0
*/
public class JarWriter extends AbstractJarWriter implements AutoCloseable {
Expand Down Expand Up @@ -80,28 +76,15 @@ public JarWriter(File file, LaunchScript launchScript) throws FileNotFoundExcept
*/
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -49,6 +49,7 @@
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Scott Frederick
*/
class RepackagerTests extends AbstractPackagerTests<Repackager> {

Expand Down Expand Up @@ -159,6 +160,9 @@ void addLauncherScript() throws Exception {
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -106,7 +106,8 @@ private LaunchScriptTestContainer(String os, String version, String scriptsDir,
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)));
}

Expand Down

0 comments on commit 9f001ef

Please sign in to comment.