Add build-image support to the maven plugin

Add a new `build-image` goal to the Maven plugin to allow Docker images
to be create via using the cloud native buildpack.

See gh-19830
pull/19835/head
Phillip Webb 5 years ago
parent 16e6bc89ed
commit cb4928ad51

@ -15,6 +15,7 @@ configurations {
dependencies {
api platform(project(':spring-boot-project:spring-boot-parent'))
api project(':spring-boot-project:spring-boot-tools:spring-boot-cloudnativebuildpack')
compileOnly 'org.apache.maven.plugin-tools:maven-plugin-annotations'
compileOnly 'org.sonatype.plexus:plexus-build-api'

@ -0,0 +1,199 @@
/*
* 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.springframework.boot.maven;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.LayoutFactory;
import org.springframework.boot.loader.tools.Layouts.Expanded;
import org.springframework.boot.loader.tools.Layouts.Jar;
import org.springframework.boot.loader.tools.Layouts.LayeredJar;
import org.springframework.boot.loader.tools.Layouts.None;
import org.springframework.boot.loader.tools.Layouts.War;
import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Packager;
/**
* Abstract base class for classes that work with an {@link Packager}.
*
* @author Phillip Webb
* @since 2.3.0
*/
public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo {
/**
* The Maven project.
* @since 1.0.0
*/
@Parameter(defaultValue = "${project}", readonly = true, required = true)
protected MavenProject project;
/**
* Maven project helper utils.
* @since 1.0.0
*/
@Component
protected MavenProjectHelper projectHelper;
/**
* The name of the main class. If not specified the first compiled class found that
* contains a 'main' method will be used.
* @since 1.0.0
*/
@Parameter
private String mainClass;
/**
* The type of archive (which corresponds to how the dependencies are laid out inside
* it). Possible values are JAR, LAYERED_JAR, WAR, ZIP, DIR, NONE. Defaults to a guess
* based on the archive type.
* @since 1.0.0
*/
@Parameter(property = "spring-boot.repackage.layout")
private LayoutType layout;
/**
* The layout factory that will be used to create the executable archive if no
* explicit layout is set. Alternative layouts implementations can be provided by 3rd
* parties.
* @since 1.5.0
*/
@Parameter
private LayoutFactory layoutFactory;
/**
* Exclude Spring Boot devtools from the repackaged archive.
* @since 1.3.0
*/
@Parameter(property = "spring-boot.repackage.excludeDevtools", defaultValue = "true")
private boolean excludeDevtools = true;
/**
* Include system scoped dependencies.
* @since 1.4.0
*/
@Parameter(defaultValue = "false")
public boolean includeSystemScope;
/**
* Return a {@link Packager} configured for this MOJO.
* @param <P> the packager type
* @param supplier a packager supplier
* @return a configured packager
*/
protected <P extends Packager> P getConfiguredPackager(Supplier<P> supplier) {
P packager = supplier.get();
packager.setLayoutFactory(this.layoutFactory);
packager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener(this::getLog));
packager.setMainClass(this.mainClass);
if (this.layout != null) {
getLog().info("Layout: " + this.layout);
packager.setLayout(this.layout.layout());
}
return packager;
}
/**
* Return {@link Libraries} that the packager can use.
* @param unpacks any libraries that require unpack
* @return the libraries to use
* @throws MojoExecutionException on execution error
*/
protected final Libraries getLibraries(Collection<Dependency> unpacks) throws MojoExecutionException {
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters()));
Libraries libraries = new ArtifactsLibraries(artifacts, unpacks, getLog());
return libraries;
}
private ArtifactsFilter[] getAdditionalFilters() {
List<ArtifactsFilter> filters = new ArrayList<>();
if (this.excludeDevtools) {
Exclude exclude = new Exclude();
exclude.setGroupId("org.springframework.boot");
exclude.setArtifactId("spring-boot-devtools");
ExcludeFilter filter = new ExcludeFilter(exclude);
filters.add(filter);
}
if (!this.includeSystemScope) {
filters.add(new ScopeFilter(null, Artifact.SCOPE_SYSTEM));
}
return filters.toArray(new ArtifactsFilter[0]);
}
/**
* Archive layout types.
*/
public enum LayoutType {
/**
* Jar Layout.
*/
JAR(new Jar()),
/**
* Layered Jar Layout.
*/
LAYERED_JAR(new LayeredJar()),
/**
* War Layout.
*/
WAR(new War()),
/**
* Zip Layout.
*/
ZIP(new Expanded()),
/**
* Dir Layout.
*/
DIR(new Expanded()),
/**
* No Layout.
*/
NONE(new None());
private final Layout layout;
LayoutType(Layout layout) {
this.layout = layout;
}
public Layout layout() {
return this.layout;
}
}
}

@ -0,0 +1,250 @@
/*
* 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.springframework.boot.maven;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.time.Duration;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.archivers.tar.TarConstants;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.springframework.boot.cloudnativebuildpack.build.AbstractBuildLog;
import org.springframework.boot.cloudnativebuildpack.build.BuildLog;
import org.springframework.boot.cloudnativebuildpack.build.BuildRequest;
import org.springframework.boot.cloudnativebuildpack.build.Builder;
import org.springframework.boot.cloudnativebuildpack.docker.TotalProgressEvent;
import org.springframework.boot.cloudnativebuildpack.io.Owner;
import org.springframework.boot.cloudnativebuildpack.io.TarArchive;
import org.springframework.boot.loader.tools.EntryWriter;
import org.springframework.boot.loader.tools.ImagePackager;
import org.springframework.boot.loader.tools.Libraries;
import org.springframework.util.StringUtils;
/**
* Package an application into a OCI image using a
* <a href="https://buildpacks.io">buildpack</a>.
*
* @author Phillip Webb
* @since 2.3.0
*/
@Mojo(name = "build-image", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true,
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
@Execute(phase = LifecyclePhase.PACKAGE)
public class BuildImageMojo extends AbstractPackagerMojo {
/**
* Directory containing the JAR.
* @since 2.3.0
*/
@Parameter(defaultValue = "${project.build.directory}", required = true)
private File sourceDirectory;
/**
* Name of the JAR.
* @since 2.3.0
*/
@Parameter(defaultValue = "${project.build.finalName}", readonly = true)
private String finalName;
/**
* Skip the execution.
* @since 2.3.0
*/
@Parameter(property = "spring-boot.build-image.skip", defaultValue = "false")
private boolean skip;
/**
* Classifier used when finding the source jar.
* @since 2.3.0
*/
@Parameter
private String classifier;
/**
* Image configuration operations.
* @since 2.3.0
*/
@Parameter
private Image image;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.project.getPackaging().equals("pom")) {
getLog().debug("build-image goal could not be applied to pom project.");
return;
}
if (this.skip) {
getLog().debug("skipping build-image as per configuration.");
return;
}
buildImage();
}
private void buildImage() throws MojoExecutionException {
Libraries libraries = getLibraries(Collections.emptySet());
try {
Builder builder = new Builder(new MojoBuildLog(this::getLog));
BuildRequest request = getBuildRequest(libraries);
builder.build(request);
}
catch (IOException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
}
private BuildRequest getBuildRequest(Libraries libraries) {
Function<Owner, TarArchive> content = (owner) -> getApplicationContent(owner, libraries);
return ((this.image != null) ? this.image : new Image()).getBuildRequest(this.project.getArtifact(), content);
}
private TarArchive getApplicationContent(Owner owner, Libraries libraries) {
ImagePackager packager = getConfiguredPackager(() -> new ImagePackager(getJarFile()));
return new PackagedTarArchive(owner, libraries, packager);
}
private File getJarFile() {
// We can use 'project.getArtifact().getFile()' because that was done in a
// forked lifecyle and is now null
StringBuilder name = new StringBuilder(this.finalName);
if (StringUtils.hasText(this.classifier)) {
name.append("-").append(this.classifier);
}
name.append(".jar");
return new File(this.sourceDirectory, name.toString());
}
/**
* {@link BuildLog} backed by Mojo logging.
*/
private static class MojoBuildLog extends AbstractBuildLog {
private static final long THRESHOLD = Duration.ofSeconds(2).toMillis();
private final Supplier<Log> log;
MojoBuildLog(Supplier<Log> log) {
this.log = log;
}
@Override
protected void log(String message) {
this.log.get().info(message);
}
@Override
protected Consumer<TotalProgressEvent> getProgressConsumer(String message) {
return new ProgressLog(message);
}
private class ProgressLog implements Consumer<TotalProgressEvent> {
private final String message;
private long last;
ProgressLog(String message) {
this.message = message;
this.last = System.currentTimeMillis();
}
@Override
public void accept(TotalProgressEvent progress) {
log(progress.getPercent());
}
private void log(int percent) {
if (percent == 100 || (System.currentTimeMillis() - this.last) > THRESHOLD) {
MojoBuildLog.this.log.get().info(this.message + " " + percent + "%");
this.last = System.currentTimeMillis();
}
}
}
}
/**
* Adapter class to expose the packaged jar as a {@link TarArchive}.
*/
static class PackagedTarArchive implements TarArchive {
static final long NORMALIZED_MOD_TIME = TarArchive.NORMALIZED_TIME.toEpochMilli();
private final Owner owner;
private final Libraries libraries;
private final ImagePackager packager;
PackagedTarArchive(Owner owner, Libraries libraries, ImagePackager packager) {
this.owner = owner;
this.libraries = libraries;
this.packager = packager;
}
@Override
public void writeTo(OutputStream outputStream) throws IOException {
TarArchiveOutputStream tar = new TarArchiveOutputStream(outputStream);
this.packager.packageImage(this.libraries, (entry, entryWriter) -> write(entry, entryWriter, tar));
}
private void write(ZipEntry jarEntry, EntryWriter entryWriter, TarArchiveOutputStream tar) {
try {
TarArchiveEntry tarEntry = convert(jarEntry);
tar.putArchiveEntry(tarEntry);
if (tarEntry.isFile()) {
entryWriter.write(tar);
}
tar.closeArchiveEntry();
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private TarArchiveEntry convert(ZipEntry entry) {
byte linkFlag = (entry.isDirectory()) ? TarConstants.LF_DIR : TarConstants.LF_NORMAL;
TarArchiveEntry tarEntry = new TarArchiveEntry(entry.getName(), linkFlag, true);
tarEntry.setUserId(this.owner.getUid());
tarEntry.setGroupId(this.owner.getGid());
tarEntry.setModTime(NORMALIZED_MOD_TIME);
if (!entry.isDirectory()) {
tarEntry.setSize(entry.getSize());
}
return tarEntry;
}
}
}

@ -0,0 +1,88 @@
/*
* 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.springframework.boot.maven;
import java.util.Map;
import java.util.function.Function;
import org.apache.maven.artifact.Artifact;
import org.springframework.boot.cloudnativebuildpack.build.BuildRequest;
import org.springframework.boot.cloudnativebuildpack.docker.type.ImageName;
import org.springframework.boot.cloudnativebuildpack.docker.type.ImageReference;
import org.springframework.boot.cloudnativebuildpack.io.Owner;
import org.springframework.boot.cloudnativebuildpack.io.TarArchive;
import org.springframework.util.StringUtils;
/**
* Image configuration options.
*
* @author Phillip Webb
* @since 2.3.0
*/
public class Image {
/**
* The name of the created image.
*/
String name;
/**
* The builder used to create the image.
*/
String builder;
/**
* Environment properties that should be passed to the builder.
*/
Map<String, String> env;
/**
* If the cache should be cleaned before building.
*/
boolean cleanCache;
/**
* If verbose logging is required.
*/
boolean verboseLogging;
BuildRequest getBuildRequest(Artifact artifact, Function<Owner, TarArchive> applicationContent) {
return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent));
}
private ImageReference getOrDeduceName(Artifact artifact) {
if (StringUtils.hasText(this.name)) {
return ImageReference.of(this.name);
}
ImageName imageName = ImageName.of(artifact.getArtifactId());
return ImageReference.of(imageName, artifact.getVersion());
}
private BuildRequest customize(BuildRequest request) {
if (StringUtils.hasText(this.builder)) {
request = request.withBuilder(ImageReference.of(this.builder));
}
if (this.env != null && !this.env.isEmpty()) {
request = request.withEnv(this.env);
}
request = request.withCleanCache(this.cleanCache);
request = request.withVerboseLogging(this.verboseLogging);
return request;
}
}

@ -0,0 +1,68 @@
/*
* 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.springframework.boot.maven;
import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Layouts.Expanded;
import org.springframework.boot.loader.tools.Layouts.Jar;
import org.springframework.boot.loader.tools.Layouts.None;
import org.springframework.boot.loader.tools.Layouts.War;
/**
* Archive layout types.
*
* @author Phillip Webb
* @since 2.3.0
*/
public enum LayoutType {
/**
* Jar Layout.
*/
JAR(new Jar()),
/**
* War Layout.
*/
WAR(new War()),
/**
* Zip Layout.
*/
ZIP(new Expanded()),
/**
* Dir Layout.
*/
DIR(new Expanded()),
/**
* No Layout.
*/
NONE(new None());
private final Layout layout;
LayoutType(Layout layout) {
this.layout = layout;
}
public Layout layout() {
return this.layout;
}
}

@ -0,0 +1,44 @@
/*
* 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.springframework.boot.maven;
import java.util.function.Supplier;
import org.apache.maven.plugin.logging.Log;
import org.springframework.boot.loader.tools.Packager.MainClassTimeoutWarningListener;
/**
* {@link MainClassTimeoutWarningListener} backed by a supplied Maven {@link Log}.
*
* @author Phillip Webb
*/
class LoggingMainClassTimeoutWarningListener implements MainClassTimeoutWarningListener {
private final Supplier<Log> log;
LoggingMainClassTimeoutWarningListener(Supplier<Log> log) {
this.log = log;
}
@Override
public void handleTimeoutWarning(long duration, String mainMethod) {
this.log.get().warn("Searching for the main-class is taking some time, "
+ "consider using the mainClass configuration parameter");
}
}

@ -18,38 +18,23 @@ package org.springframework.boot.maven;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.springframework.boot.loader.tools.DefaultLaunchScript;
import org.springframework.boot.loader.tools.LaunchScript;
import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.LayoutFactory;
import org.springframework.boot.loader.tools.Layouts.Expanded;
import org.springframework.boot.loader.tools.Layouts.Jar;
import org.springframework.boot.loader.tools.Layouts.LayeredJar;
import org.springframework.boot.loader.tools.Layouts.None;
import org.springframework.boot.loader.tools.Layouts.War;
import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Repackager;
import org.springframework.boot.loader.tools.Repackager.MainClassTimeoutWarningListener;
/**
* Repackage existing JAR and WAR archives so that they can be executed from the command
@ -65,24 +50,10 @@ import org.springframework.boot.loader.tools.Repackager.MainClassTimeoutWarningL
@Mojo(name = "repackage", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true,
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class RepackageMojo extends AbstractDependencyFilterMojo {
public class RepackageMojo extends AbstractPackagerMojo {
private static final Pattern WHITE_SPACE_PATTERN = Pattern.compile("\\s+");
/**
* The Maven project.
* @since 1.0.0
*/
@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;
/**
* Maven project helper utils.
* @since 1.0.0
*/
@Component
private MavenProjectHelper projectHelper;
/**
* Directory containing the generated archive.
* @since 1.0.0
@ -131,32 +102,6 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
@Parameter(defaultValue = "true")
private boolean attach = true;
/**
* The name of the main class. If not specified the first compiled class found that
* contains a 'main' method will be used.
* @since 1.0.0
*/
@Parameter
private String mainClass;
/**
* The type of archive (which corresponds to how the dependencies are laid out inside
* it). Possible values are JAR, LAYERED_JAR, WAR, ZIP, DIR, NONE. Defaults to a guess
* based on the archive type.
* @since 1.0.0
*/
@Parameter(property = "spring-boot.repackage.layout")
private LayoutType layout;
/**
* The layout factory that will be used to create the executable archive if no
* explicit layout is set. Alternative layouts implementations can be provided by 3rd
* parties.
* @since 1.5.0
*/
@Parameter
private LayoutFactory layoutFactory;
/**
* A list of the libraries that must be unpacked from fat jars in order to run.
* Specify each library as a {@code <dependency>} with a {@code <groupId>} and a
@ -195,20 +140,6 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
@Parameter
private Properties embeddedLaunchScriptProperties;
/**
* Exclude Spring Boot devtools from the repackaged archive.
* @since 1.3.0
*/
@Parameter(property = "spring-boot.repackage.excludeDevtools", defaultValue = "true")
private boolean excludeDevtools = true;
/**
* Include system scoped dependencies.
* @since 1.4.0
*/
@Parameter(defaultValue = "false")
public boolean includeSystemScope;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.project.getPackaging().equals("pom")) {
@ -226,8 +157,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
Artifact source = getSourceArtifact();
File target = getTargetFile();
Repackager repackager = getRepackager(source.getFile());
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters()));
Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog());
Libraries libraries = getLibraries(this.requiresUnpack);
try {
LaunchScript launchScript = getLaunchScript();
repackager.repackage(target, libraries, launchScript);
@ -274,29 +204,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
}
private Repackager getRepackager(File source) {
Repackager repackager = new Repackager(source, this.layoutFactory);
repackager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener());
repackager.setMainClass(this.mainClass);
if (this.layout != null) {
getLog().info("Layout: " + this.layout);
repackager.setLayout(this.layout.layout());
}
return repackager;
}
private ArtifactsFilter[] getAdditionalFilters() {
List<ArtifactsFilter> filters = new ArrayList<>();
if (this.excludeDevtools) {
Exclude exclude = new Exclude();
exclude.setGroupId("org.springframework.boot");
exclude.setArtifactId("spring-boot-devtools");
ExcludeFilter filter = new ExcludeFilter(exclude);
filters.add(filter);
}
if (!this.includeSystemScope) {
filters.add(new ScopeFilter(null, Artifact.SCOPE_SYSTEM));
}
return filters.toArray(new ArtifactsFilter[0]);
return getConfiguredPackager(() -> new Repackager(source));
}
private LaunchScript getLaunchScript() throws IOException {
@ -361,61 +269,4 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
}
}
private class LoggingMainClassTimeoutWarningListener implements MainClassTimeoutWarningListener {
@Override
public void handleTimeoutWarning(long duration, String mainMethod) {
getLog().warn("Searching for the main-class is taking some time, "
+ "consider using the mainClass configuration parameter");
}
}
/**
* Archive layout types.
*/
public enum LayoutType {
/**
* Jar Layout.
*/
JAR(new Jar()),
/**
* Layered Jar Layout.
*/
LAYERED_JAR(new LayeredJar()),
/**
* War Layout.
*/
WAR(new War()),
/**
* Zip Layout.
*/
ZIP(new Expanded()),
/**
* Dir Layout.
*/
DIR(new Expanded()),
/**
* No Layout.
*/
NONE(new None());
private final Layout layout;
LayoutType(Layout layout) {
this.layout = layout;
}
public Layout layout() {
return this.layout;
}
}
}

@ -0,0 +1,107 @@
/*
* 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.springframework.boot.maven;
import java.util.Collections;
import java.util.function.Function;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.artifact.versioning.VersionRange;
import org.junit.jupiter.api.Test;
import org.springframework.boot.cloudnativebuildpack.build.BuildRequest;
import org.springframework.boot.cloudnativebuildpack.io.Owner;
import org.springframework.boot.cloudnativebuildpack.io.TarArchive;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link Image}.
*
* @author Phillip Webb
*/
class ImageTests {
@Test
void getBuildRequestWhenNameIsNullDeducesName() {
BuildRequest request = new Image().getBuildRequest(createArtifact(), mockAplicationContent());
assertThat(request.getName().toString()).isEqualTo("docker.io/library/my-app:0.0.1-SNAPSHOT");
}
@Test
void getBuildEquestWhenNameIsSetUsesName() {
Image image = new Image();
image.name = "demo";
BuildRequest request = image.getBuildRequest(createArtifact(), mockAplicationContent());
assertThat(request.getName().toString()).isEqualTo("docker.io/library/demo:latest");
}
@Test
void getBuildRequestWhenNoCustomizationsUsesDefaults() {
BuildRequest request = new Image().getBuildRequest(createArtifact(), mockAplicationContent());
assertThat(request.getName().toString()).isEqualTo("docker.io/library/my-app:0.0.1-SNAPSHOT");
assertThat(request.getBuilder().toString()).isEqualTo("docker.io/cloudfoundry/cnb:0.0.43-bionic");
assertThat(request.getEnv()).isEmpty();
assertThat(request.isCleanCache()).isFalse();
assertThat(request.isVerboseLogging()).isFalse();
}
@Test
void getBuildRequestWhenHasBuilderUsesBuilder() {
Image image = new Image();
image.builder = "springboot/builder:2.2.x";
BuildRequest request = image.getBuildRequest(createArtifact(), mockAplicationContent());
assertThat(request.getBuilder().toString()).isEqualTo("docker.io/springboot/builder:2.2.x");
}
@Test
void getBuildRequestWhenHasEnvUsesEnv() {
Image image = new Image();
image.env = Collections.singletonMap("test", "test");
BuildRequest request = image.getBuildRequest(createArtifact(), mockAplicationContent());
assertThat(request.getEnv()).containsExactly(entry("test", "test"));
}
@Test
void getBuildRequestWhenHasCleanCacheUsesCleanCache() {
Image image = new Image();
image.cleanCache = true;
BuildRequest request = image.getBuildRequest(createArtifact(), mockAplicationContent());
assertThat(request.isCleanCache()).isTrue();
}
@Test
void getBuildRequestWhenHasVerboseLoggingUsesVerboseLogging() {
Image image = new Image();
image.verboseLogging = true;
BuildRequest request = image.getBuildRequest(createArtifact(), mockAplicationContent());
assertThat(request.isVerboseLogging()).isTrue();
}
private Artifact createArtifact() {
return new DefaultArtifact("com.example", "my-app", VersionRange.createFromVersion("0.0.1-SNAPSHOT"), "compile",
"jar", null, new DefaultArtifactHandler());
}
private Function<Owner, TarArchive> mockAplicationContent() {
return (owner) -> null;
}
}
Loading…
Cancel
Save