Allow image application directory to be configurable

An `applicationDirectory` option on the Maven
`spring-boot:build-image` goal and the Gradle `bootBuildImage` task
can be configured to set the location that will be used to upload
application contents to the builder image, and will contain the
application contents in the generated image.

Closes gh-34786
This commit is contained in:
Scott Frederick
2023-04-07 14:00:00 -05:00
parent df542849be
commit 56bc6d2fa0
16 changed files with 292 additions and 22 deletions

View File

@@ -83,6 +83,8 @@ public class BuildRequest {
private final Instant createdDate;
private final String applicationDirectory;
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(applicationContent, "ApplicationContent must not be null");
@@ -103,13 +105,14 @@ public class BuildRequest {
this.buildCache = null;
this.launchCache = null;
this.createdDate = null;
this.applicationDirectory = null;
}
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
ImageReference runImage, Creator creator, Map<String, String> env, boolean cleanCache,
boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List<BuildpackReference> buildpacks,
List<Binding> bindings, String network, List<ImageReference> tags, Cache buildCache, Cache launchCache,
Instant createdDate) {
Instant createdDate, String applicationDirectory) {
this.name = name;
this.applicationContent = applicationContent;
this.builder = builder;
@@ -127,6 +130,7 @@ public class BuildRequest {
this.buildCache = buildCache;
this.launchCache = launchCache;
this.createdDate = createdDate;
this.applicationDirectory = applicationDirectory;
}
/**
@@ -139,7 +143,7 @@ public class BuildRequest {
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage,
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache,
this.createdDate);
this.createdDate, this.applicationDirectory);
}
/**
@@ -151,7 +155,7 @@ public class BuildRequest {
return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(),
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache,
this.createdDate);
this.createdDate, this.applicationDirectory);
}
/**
@@ -163,7 +167,8 @@ public class BuildRequest {
Assert.notNull(creator, "Creator must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate);
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@@ -180,7 +185,7 @@ public class BuildRequest {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache,
this.createdDate);
this.createdDate, this.applicationDirectory);
}
/**
@@ -195,7 +200,7 @@ public class BuildRequest {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging, this.pullPolicy,
this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildCache,
this.launchCache, this.createdDate);
this.launchCache, this.createdDate, this.applicationDirectory);
}
/**
@@ -206,7 +211,8 @@ public class BuildRequest {
public BuildRequest withCleanCache(boolean cleanCache) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate);
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@@ -217,7 +223,8 @@ public class BuildRequest {
public BuildRequest withVerboseLogging(boolean verboseLogging) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate);
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@@ -228,7 +235,8 @@ public class BuildRequest {
public BuildRequest withPullPolicy(PullPolicy pullPolicy) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate);
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@@ -239,7 +247,8 @@ public class BuildRequest {
public BuildRequest withPublish(boolean publish) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate);
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@@ -263,7 +272,8 @@ public class BuildRequest {
Assert.notNull(buildpacks, "Buildpacks must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate);
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@@ -287,7 +297,8 @@ public class BuildRequest {
Assert.notNull(bindings, "Bindings must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate);
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@@ -299,7 +310,7 @@ public class BuildRequest {
public BuildRequest withNetwork(String network) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
network, this.tags, this.buildCache, this.launchCache, this.createdDate);
network, this.tags, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory);
}
/**
@@ -321,7 +332,7 @@ public class BuildRequest {
Assert.notNull(tags, "Tags must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, tags, this.buildCache, this.launchCache, this.createdDate);
this.network, tags, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory);
}
/**
@@ -333,7 +344,7 @@ public class BuildRequest {
Assert.notNull(buildCache, "BuildCache must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, buildCache, this.launchCache, this.createdDate);
this.network, this.tags, buildCache, this.launchCache, this.createdDate, this.applicationDirectory);
}
/**
@@ -345,7 +356,7 @@ public class BuildRequest {
Assert.notNull(launchCache, "LaunchCache must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, launchCache, this.createdDate);
this.network, this.tags, this.buildCache, launchCache, this.createdDate, this.applicationDirectory);
}
/**
@@ -357,7 +368,8 @@ public class BuildRequest {
Assert.notNull(createdDate, "CreatedDate must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, parseCreatedDate(createdDate));
this.network, this.tags, this.buildCache, this.launchCache, parseCreatedDate(createdDate),
this.applicationDirectory);
}
private Instant parseCreatedDate(String createdDate) {
@@ -372,6 +384,18 @@ public class BuildRequest {
}
}
/**
* Return a new {@link BuildRequest} with an updated application directory.
* @param applicationDirectory the application directory
* @return an updated build request
*/
public BuildRequest withApplicationDirectory(String applicationDirectory) {
Assert.notNull(applicationDirectory, "ApplicationDirectory must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, applicationDirectory);
}
/**
* Return the name of the image that should be created.
* @return the name of the image
@@ -513,6 +537,14 @@ public class BuildRequest {
return this.createdDate;
}
/**
* Return the application directory that should be used by the lifecycle.
* @return the application directory
*/
public String getApplicationDirectory() {
return this.applicationDirectory;
}
/**
* Factory method to create a new {@link BuildRequest} from a JAR file.
* @param jarFile the source jar file

View File

@@ -76,6 +76,8 @@ class Lifecycle implements Closeable {
private final VolumeName launchCacheVolume;
private final String applicationDirectory;
private boolean executed;
private boolean applicationVolumePopulated;
@@ -101,6 +103,7 @@ class Lifecycle implements Closeable {
this.applicationVolume = createRandomVolumeName("pack-app-");
this.buildCacheVolume = getBuildCacheVolumeName(request);
this.launchCacheVolume = getLaunchCacheVolumeName(request);
this.applicationDirectory = getApplicationDirectory(request);
}
protected VolumeName createRandomVolumeName(String prefix) {
@@ -128,6 +131,10 @@ class Lifecycle implements Closeable {
return null;
}
private String getApplicationDirectory(BuildRequest request) {
return (request.getApplicationDirectory() != null) ? request.getApplicationDirectory() : Directory.APPLICATION;
}
private VolumeName createCacheVolumeName(BuildRequest request, String suffix) {
return VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", "." + suffix, 6);
}
@@ -161,7 +168,7 @@ class Lifecycle implements Closeable {
phase.withDaemonAccess();
configureDaemonAccess(phase);
phase.withLogLevelArg();
phase.withArgs("-app", Directory.APPLICATION);
phase.withArgs("-app", this.applicationDirectory);
phase.withArgs("-platform", Directory.PLATFORM);
phase.withArgs("-run-image", this.request.getRunImage());
phase.withArgs("-layers", Directory.LAYERS);
@@ -176,7 +183,7 @@ class Lifecycle implements Closeable {
}
phase.withArgs(this.request.getName());
phase.withBinding(Binding.from(this.layersVolume, Directory.LAYERS));
phase.withBinding(Binding.from(this.applicationVolume, Directory.APPLICATION));
phase.withBinding(Binding.from(this.applicationVolume, this.applicationDirectory));
phase.withBinding(Binding.from(this.buildCacheVolume, Directory.CACHE));
phase.withBinding(Binding.from(this.launchCacheVolume, Directory.LAUNCH_CACHE));
if (this.request.getBindings() != null) {
@@ -245,7 +252,7 @@ class Lifecycle implements Closeable {
try {
TarArchive applicationContent = this.request.getApplicationContent(this.builder.getBuildOwner());
return this.docker.container()
.create(config, ContainerContent.of(applicationContent, Directory.APPLICATION));
.create(config, ContainerContent.of(applicationContent, this.applicationDirectory));
}
finally {
this.applicationVolumePopulated = true;

View File

@@ -292,6 +292,13 @@ class BuildRequestTests {
.withMessageContaining("'not a date'");
}
@Test
void withApplicationDirectorySetsApplicationDirectory() throws Exception {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
BuildRequest withAppDir = request.withApplicationDirectory("/application");
assertThat(withAppDir.getApplicationDirectory()).isEqualTo("/application");
}
private void hasExpectedJarContent(TarArchive archive) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

View File

@@ -229,6 +229,17 @@ class LifecycleTests {
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
}
@Test
void executeWithApplicationDirectoryExecutesPhases() throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withApplicationDirectory("/application");
createLifecycle(request).execute();
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-app-dir.json"));
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
}
@Test
void executeWithDockerHostAndRemoteAddressExecutesPhases() throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());

View File

@@ -0,0 +1,39 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/creator",
"-app",
"/application",
"-platform",
"/platform",
"-run-image",
"docker.io/cloudfoundry/run:latest",
"-layers",
"/layers",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-daemon",
"docker.io/library/my-application:latest"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/application",
"pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache"
],
"SecurityOpt" : [
"label=disable"
]
}
}