Commit e4fa39df authored by Scott Frederick's avatar Scott Frederick

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
parent 4358d9bc
/*
* 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.
......@@ -28,6 +28,7 @@ import org.springframework.util.Assert;
* Utility class that can be used to export a fully packaged archive to an OCI image.
*
* @author Phillip Webb
* @author Scott Frederick
* @since 2.3.0
*/
public class ImagePackager extends Packager {
......@@ -38,10 +39,14 @@ public class ImagePackager extends Packager {
*/
public ImagePackager(File source) {
super(source, null);
if (isAlreadyPackaged()) {
Assert.isTrue(getBackupFile().exists() && getBackupFile().isFile(),
"Original source '" + getBackupFile() + "' is required for building an image");
}
}
/**
* Create an packaged image.
* Create a packaged image.
* @param libraries the contained libraries
* @param exporter the exporter used to write the image
* @throws IOException on IO error
......@@ -52,8 +57,8 @@ public class ImagePackager extends Packager {
private void packageImage(Libraries libraries, AbstractJarWriter writer) throws IOException {
File source = isAlreadyPackaged() ? getBackupFile() : getSource();
Assert.state(source.exists() && source.isFile(), () -> "Unable to read jar file " + source);
Assert.state(!isAlreadyPackaged(source), () -> "Repackaged jar file " + source + " cannot be exported");
Assert.state(!isAlreadyPackaged(source),
() -> "Repackaged archive file " + source + " cannot be used to build an image");
try (JarFile sourceJar = new JarFile(source)) {
write(sourceJar, libraries, writer);
}
......
......@@ -45,6 +45,7 @@ import org.springframework.util.StringUtils;
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
* @author Scott Frederick
* @since 2.3.0
*/
public abstract class Packager {
......@@ -69,7 +70,7 @@ public abstract class Packager {
private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";
private List<MainClassTimeoutWarningListener> mainClassTimeoutListeners = new ArrayList<>();
private final List<MainClassTimeoutWarningListener> mainClassTimeoutListeners = new ArrayList<>();
private String mainClass;
......@@ -153,15 +154,18 @@ public abstract class Packager {
this.includeRelevantJarModeJars = includeRelevantJarModeJars;
}
protected final boolean isAlreadyPackaged() throws IOException {
protected final boolean isAlreadyPackaged() {
return isAlreadyPackaged(this.source);
}
protected final boolean isAlreadyPackaged(File file) throws IOException {
protected final boolean isAlreadyPackaged(File file) {
try (JarFile jarFile = new JarFile(file)) {
Manifest manifest = jarFile.getManifest();
return (manifest != null && manifest.getMainAttributes().getValue(BOOT_VERSION_ATTRIBUTE) != null);
}
catch (IOException ex) {
throw new IllegalStateException("Error reading archive file", ex);
}
}
protected final void write(JarFile sourceJar, Libraries libraries, AbstractJarWriter writer) throws IOException {
......@@ -285,8 +289,7 @@ public abstract class Packager {
* @return the file to use to backup the original source
*/
public final File getBackupFile() {
File source = getSource();
return new File(source.getParentFile(), source.getName() + ".original");
return new File(this.source.getParentFile(), this.source.getName() + ".original");
}
protected final File getSource() {
......
/*
* 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.
......@@ -74,9 +74,7 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
File original = new File(project,
"target/build-image-with-repackage-0.0.1.BUILD-SNAPSHOT.jar.original");
assertThat(original).isFile();
String log = buildLog(project);
System.out.println(log);
assertThat(log).contains("Building image").contains("paketo-buildpacks/builder")
assertThat(buildLog(project)).contains("Building image").contains("paketo-buildpacks/builder")
.contains("docker.io/library/build-image-with-repackage:0.0.1.BUILD-SNAPSHOT")
.contains("Successfully built image");
ImageReference imageReference = ImageReference.of(ImageName.of("build-image-with-repackage"),
......@@ -183,6 +181,13 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
(project) -> assertThat(buildLog(project)).contains("Executable jar file required for building image"));
}
@TestTemplate
void failsWhenFinalNameIsMisconfigured(MavenBuild mavenBuild) {
mavenBuild.project("build-image-final-name").goals("package")
.executeAndFail((project) -> assertThat(buildLog(project)).contains("final-name.jar.original")
.contains("is required for building an image"));
}
private void writeLongNameResource(File project) {
StringBuilder name = new StringBuilder();
new Random().ints('a', 'z' + 1).limit(128).forEach((i) -> name.append((char) i));
......
<?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>build-image-final-name</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>
<goal>build-image</goal>
</goals>
<configuration>
<finalName>final-name</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
/*
* Copyright 2012-2020 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) throws Exception {
System.out.println("Launched");
synchronized(args) {
args.wait(); // Prevent exit"
}
}
}
/*
* 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.
......@@ -153,7 +153,8 @@ public class BuildImageMojo extends AbstractPackagerMojo {
}
private BuildRequest getBuildRequest(Libraries libraries) {
Function<Owner, TarArchive> content = (owner) -> getApplicationContent(owner, libraries);
ImagePackager imagePackager = new ImagePackager(getJarFile());
Function<Owner, TarArchive> content = (owner) -> getApplicationContent(owner, libraries, imagePackager);
Image image = (this.image != null) ? this.image : new Image();
if (image.name == null && this.imageName != null) {
image.setName(this.imageName);
......@@ -167,8 +168,8 @@ public class BuildImageMojo extends AbstractPackagerMojo {
return customize(image.getBuildRequest(this.project.getArtifact(), content));
}
private TarArchive getApplicationContent(Owner owner, Libraries libraries) {
ImagePackager packager = getConfiguredPackager(() -> new ImagePackager(getJarFile()));
private TarArchive getApplicationContent(Owner owner, Libraries libraries, ImagePackager imagePackager) {
ImagePackager packager = getConfiguredPackager(() -> imagePackager);
return new PackagedTarArchive(owner, libraries, packager);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment