Skip to content

Commit e4fa39d

Browse files
Fail fast when finalName is misconfigured
When the `finalName` parameter is incorrectly set in the Spring Boot Maven plugin configuration instead of in the `build` configuration, the repackaged and original archive files are not named as expected. Prior to this commit, the image building goal would detect this error condition and throw an exception late in the process of creating the build container, leaving the container in an unstable state. This commit changes the image building goal to detect this condition early, before attempting to create the container. Fixes gh-25590
1 parent 4358d9b commit e4fa39d

File tree

6 files changed

+92
-17
lines changed

6 files changed

+92
-17
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
2828
* Utility class that can be used to export a fully packaged archive to an OCI image.
2929
*
3030
* @author Phillip Webb
31+
* @author Scott Frederick
3132
* @since 2.3.0
3233
*/
3334
public class ImagePackager extends Packager {
@@ -38,10 +39,14 @@ public class ImagePackager extends Packager {
3839
*/
3940
public ImagePackager(File source) {
4041
super(source, null);
42+
if (isAlreadyPackaged()) {
43+
Assert.isTrue(getBackupFile().exists() && getBackupFile().isFile(),
44+
"Original source '" + getBackupFile() + "' is required for building an image");
45+
}
4146
}
4247

4348
/**
44-
* Create an packaged image.
49+
* Create a packaged image.
4550
* @param libraries the contained libraries
4651
* @param exporter the exporter used to write the image
4752
* @throws IOException on IO error
@@ -52,8 +57,8 @@ public void packageImage(Libraries libraries, BiConsumer<ZipEntry, EntryWriter>
5257

5358
private void packageImage(Libraries libraries, AbstractJarWriter writer) throws IOException {
5459
File source = isAlreadyPackaged() ? getBackupFile() : getSource();
55-
Assert.state(source.exists() && source.isFile(), () -> "Unable to read jar file " + source);
56-
Assert.state(!isAlreadyPackaged(source), () -> "Repackaged jar file " + source + " cannot be exported");
60+
Assert.state(!isAlreadyPackaged(source),
61+
() -> "Repackaged archive file " + source + " cannot be used to build an image");
5762
try (JarFile sourceJar = new JarFile(source)) {
5863
write(sourceJar, libraries, writer);
5964
}

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
* @author Andy Wilkinson
4646
* @author Stephane Nicoll
4747
* @author Madhura Bhave
48+
* @author Scott Frederick
4849
* @since 2.3.0
4950
*/
5051
public abstract class Packager {
@@ -69,7 +70,7 @@ public abstract class Packager {
6970

7071
private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";
7172

72-
private List<MainClassTimeoutWarningListener> mainClassTimeoutListeners = new ArrayList<>();
73+
private final List<MainClassTimeoutWarningListener> mainClassTimeoutListeners = new ArrayList<>();
7374

7475
private String mainClass;
7576

@@ -153,15 +154,18 @@ public void setIncludeRelevantJarModeJars(boolean includeRelevantJarModeJars) {
153154
this.includeRelevantJarModeJars = includeRelevantJarModeJars;
154155
}
155156

156-
protected final boolean isAlreadyPackaged() throws IOException {
157+
protected final boolean isAlreadyPackaged() {
157158
return isAlreadyPackaged(this.source);
158159
}
159160

160-
protected final boolean isAlreadyPackaged(File file) throws IOException {
161+
protected final boolean isAlreadyPackaged(File file) {
161162
try (JarFile jarFile = new JarFile(file)) {
162163
Manifest manifest = jarFile.getManifest();
163164
return (manifest != null && manifest.getMainAttributes().getValue(BOOT_VERSION_ATTRIBUTE) != null);
164165
}
166+
catch (IOException ex) {
167+
throw new IllegalStateException("Error reading archive file", ex);
168+
}
165169
}
166170

167171
protected final void write(JarFile sourceJar, Libraries libraries, AbstractJarWriter writer) throws IOException {
@@ -285,8 +289,7 @@ protected String findMainMethod(JarFile source) throws IOException {
285289
* @return the file to use to backup the original source
286290
*/
287291
public final File getBackupFile() {
288-
File source = getSource();
289-
return new File(source.getParentFile(), source.getName() + ".original");
292+
return new File(this.source.getParentFile(), this.source.getName() + ".original");
290293
}
291294

292295
protected final File getSource() {

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -74,9 +74,7 @@ void whenBuildImageIsInvokedWithRepackageTheExistingArchiveIsUsed(MavenBuild mav
7474
File original = new File(project,
7575
"target/build-image-with-repackage-0.0.1.BUILD-SNAPSHOT.jar.original");
7676
assertThat(original).isFile();
77-
String log = buildLog(project);
78-
System.out.println(log);
79-
assertThat(log).contains("Building image").contains("paketo-buildpacks/builder")
77+
assertThat(buildLog(project)).contains("Building image").contains("paketo-buildpacks/builder")
8078
.contains("docker.io/library/build-image-with-repackage:0.0.1.BUILD-SNAPSHOT")
8179
.contains("Successfully built image");
8280
ImageReference imageReference = ImageReference.of(ImageName.of("build-image-with-repackage"),
@@ -183,6 +181,13 @@ void failsWithWarPackaging(MavenBuild mavenBuild) {
183181
(project) -> assertThat(buildLog(project)).contains("Executable jar file required for building image"));
184182
}
185183

184+
@TestTemplate
185+
void failsWhenFinalNameIsMisconfigured(MavenBuild mavenBuild) {
186+
mavenBuild.project("build-image-final-name").goals("package")
187+
.executeAndFail((project) -> assertThat(buildLog(project)).contains("final-name.jar.original")
188+
.contains("is required for building an image"));
189+
}
190+
186191
private void writeLongNameResource(File project) {
187192
StringBuilder name = new StringBuilder();
188193
new Random().ints('a', 'z' + 1).limit(128).forEach((i) -> name.append((char) i));
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>org.springframework.boot.maven.it</groupId>
6+
<artifactId>build-image-final-name</artifactId>
7+
<version>0.0.1.BUILD-SNAPSHOT</version>
8+
<properties>
9+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
10+
<maven.compiler.source>@java.version@</maven.compiler.source>
11+
<maven.compiler.target>@java.version@</maven.compiler.target>
12+
</properties>
13+
<build>
14+
<plugins>
15+
<plugin>
16+
<groupId>@project.groupId@</groupId>
17+
<artifactId>@project.artifactId@</artifactId>
18+
<version>@project.version@</version>
19+
<executions>
20+
<execution>
21+
<goals>
22+
<goal>repackage</goal>
23+
<goal>build-image</goal>
24+
</goals>
25+
<configuration>
26+
<finalName>final-name</finalName>
27+
</configuration>
28+
</execution>
29+
</executions>
30+
</plugin>
31+
</plugins>
32+
</build>
33+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.test;
18+
19+
public class SampleApplication {
20+
21+
public static void main(String[] args) throws Exception {
22+
System.out.println("Launched");
23+
synchronized(args) {
24+
args.wait(); // Prevent exit"
25+
}
26+
}
27+
28+
}

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -153,7 +153,8 @@ private void buildImage() throws MojoExecutionException {
153153
}
154154

155155
private BuildRequest getBuildRequest(Libraries libraries) {
156-
Function<Owner, TarArchive> content = (owner) -> getApplicationContent(owner, libraries);
156+
ImagePackager imagePackager = new ImagePackager(getJarFile());
157+
Function<Owner, TarArchive> content = (owner) -> getApplicationContent(owner, libraries, imagePackager);
157158
Image image = (this.image != null) ? this.image : new Image();
158159
if (image.name == null && this.imageName != null) {
159160
image.setName(this.imageName);
@@ -167,8 +168,8 @@ private BuildRequest getBuildRequest(Libraries libraries) {
167168
return customize(image.getBuildRequest(this.project.getArtifact(), content));
168169
}
169170

170-
private TarArchive getApplicationContent(Owner owner, Libraries libraries) {
171-
ImagePackager packager = getConfiguredPackager(() -> new ImagePackager(getJarFile()));
171+
private TarArchive getApplicationContent(Owner owner, Libraries libraries, ImagePackager imagePackager) {
172+
ImagePackager packager = getConfiguredPackager(() -> imagePackager);
172173
return new PackagedTarArchive(owner, libraries, packager);
173174
}
174175

0 commit comments

Comments
 (0)