Merge branch '2.3.x'
Closes gh-21933
This commit is contained in:
@@ -46,6 +46,8 @@ public class BuildRequest {
|
||||
|
||||
private final ImageReference builder;
|
||||
|
||||
private final ImageReference runImage;
|
||||
|
||||
private final Creator creator;
|
||||
|
||||
private final Map<String, String> env;
|
||||
@@ -60,6 +62,7 @@ public class BuildRequest {
|
||||
this.name = name.inTaggedForm();
|
||||
this.applicationContent = applicationContent;
|
||||
this.builder = DEFAULT_BUILDER;
|
||||
this.runImage = null;
|
||||
this.env = Collections.emptyMap();
|
||||
this.cleanCache = false;
|
||||
this.verboseLogging = false;
|
||||
@@ -67,10 +70,12 @@ public class BuildRequest {
|
||||
}
|
||||
|
||||
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
|
||||
Creator creator, Map<String, String> env, boolean cleanCache, boolean verboseLogging) {
|
||||
ImageReference runImage, Creator creator, Map<String, String> env, boolean cleanCache,
|
||||
boolean verboseLogging) {
|
||||
this.name = name;
|
||||
this.applicationContent = applicationContent;
|
||||
this.builder = builder;
|
||||
this.runImage = runImage;
|
||||
this.creator = creator;
|
||||
this.env = env;
|
||||
this.cleanCache = cleanCache;
|
||||
@@ -84,20 +89,29 @@ public class BuildRequest {
|
||||
*/
|
||||
public BuildRequest withBuilder(ImageReference builder) {
|
||||
Assert.notNull(builder, "Builder must not be null");
|
||||
builder = (builder.getDigest() != null) ? builder : builder.inTaggedForm();
|
||||
return new BuildRequest(this.name, this.applicationContent, builder, this.creator, this.env, this.cleanCache,
|
||||
this.verboseLogging);
|
||||
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage,
|
||||
this.creator, this.env, this.cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link BuildRequest} with an updated builder.
|
||||
* Return a new {@link BuildRequest} with an updated run image.
|
||||
* @param runImageName the run image to use
|
||||
* @return an updated build request
|
||||
*/
|
||||
public BuildRequest withRunImage(ImageReference runImageName) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(),
|
||||
this.creator, this.env, this.cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link BuildRequest} with an updated creator.
|
||||
* @param creator the new {@code Creator} to use
|
||||
* @return an updated build request
|
||||
*/
|
||||
public BuildRequest withCreator(Creator creator) {
|
||||
Assert.notNull(creator, "Creator must not be null");
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, creator, this.env, this.cleanCache,
|
||||
this.verboseLogging);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env,
|
||||
this.cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,12 +125,12 @@ public class BuildRequest {
|
||||
Assert.hasText(value, "Value must not be empty");
|
||||
Map<String, String> env = new LinkedHashMap<>(this.env);
|
||||
env.put(name, value);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator,
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
|
||||
Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link BuildRequest} with an additional env variables.
|
||||
* Return a new {@link BuildRequest} with additional env variables.
|
||||
* @param env the additional variables
|
||||
* @return an updated build request
|
||||
*/
|
||||
@@ -124,27 +138,27 @@ public class BuildRequest {
|
||||
Assert.notNull(env, "Env must not be null");
|
||||
Map<String, String> updatedEnv = new LinkedHashMap<>(this.env);
|
||||
updatedEnv.putAll(env);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator,
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
|
||||
Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link BuildRequest} with an specific clean cache settings.
|
||||
* Return a new {@link BuildRequest} with an updated clean cache setting.
|
||||
* @param cleanCache if the cache should be cleaned
|
||||
* @return an updated build request
|
||||
*/
|
||||
public BuildRequest withCleanCache(boolean cleanCache) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, this.env, cleanCache,
|
||||
this.verboseLogging);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link BuildRequest} with an specific verbose logging settings.
|
||||
* Return a new {@link BuildRequest} with an updated verbose logging setting.
|
||||
* @param verboseLogging if verbose logging should be used
|
||||
* @return an updated build request
|
||||
*/
|
||||
public BuildRequest withVerboseLogging(boolean verboseLogging) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, this.env,
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
this.cleanCache, verboseLogging);
|
||||
}
|
||||
|
||||
@@ -175,6 +189,14 @@ public class BuildRequest {
|
||||
return this.builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the run image that should be used, if provided.
|
||||
* @return the run image
|
||||
*/
|
||||
public ImageReference getRunImage() {
|
||||
return this.runImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Creator} the builder should use.
|
||||
* @return the {@code Creator}
|
||||
|
||||
@@ -63,15 +63,12 @@ public class Builder {
|
||||
Image builderImage = pullBuilder(request);
|
||||
BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage);
|
||||
BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv());
|
||||
StackId stackId = StackId.fromImage(builderImage);
|
||||
ImageReference runImageReference = getRunImageReference(builderMetadata.getStack());
|
||||
Image runImage = pullRunImage(request, runImageReference);
|
||||
assertHasExpectedStackId(runImage, stackId);
|
||||
request = determineRunImage(request, builderImage, builderMetadata.getStack());
|
||||
EphemeralBuilder builder = new EphemeralBuilder(buildOwner, builderImage, builderMetadata, request.getCreator(),
|
||||
request.getEnv());
|
||||
this.docker.image().load(builder.getArchive(), UpdateListener.none());
|
||||
try {
|
||||
executeLifecycle(request, runImageReference, builder);
|
||||
executeLifecycle(request, builder);
|
||||
}
|
||||
finally {
|
||||
this.docker.image().remove(builder.getName(), true);
|
||||
@@ -87,29 +84,41 @@ public class Builder {
|
||||
return builderImage;
|
||||
}
|
||||
|
||||
private ImageReference getRunImageReference(Stack stack) {
|
||||
String name = stack.getRunImage().getImage();
|
||||
Assert.state(StringUtils.hasText(name), "Run image must be specified");
|
||||
return ImageReference.of(name).inTaggedForm();
|
||||
private BuildRequest determineRunImage(BuildRequest request, Image builderImage, Stack builderStack)
|
||||
throws IOException {
|
||||
if (request.getRunImage() == null) {
|
||||
ImageReference runImage = getRunImageReferenceForStack(builderStack);
|
||||
request = request.withRunImage(runImage);
|
||||
}
|
||||
Image runImage = pullRunImage(request);
|
||||
assertStackIdsMatch(runImage, builderImage);
|
||||
return request;
|
||||
}
|
||||
|
||||
private Image pullRunImage(BuildRequest request, ImageReference name) throws IOException {
|
||||
Consumer<TotalProgressEvent> progressConsumer = this.log.pullingRunImage(request, name);
|
||||
private ImageReference getRunImageReferenceForStack(Stack stack) {
|
||||
String name = stack.getRunImage().getImage();
|
||||
Assert.state(StringUtils.hasText(name), "Run image must be specified in the builder image stack");
|
||||
return ImageReference.of(name).inTaggedOrDigestForm();
|
||||
}
|
||||
|
||||
private Image pullRunImage(BuildRequest request) throws IOException {
|
||||
ImageReference runImage = request.getRunImage();
|
||||
Consumer<TotalProgressEvent> progressConsumer = this.log.pullingRunImage(request, runImage);
|
||||
TotalProgressPullListener listener = new TotalProgressPullListener(progressConsumer);
|
||||
Image image = this.docker.image().pull(name, listener);
|
||||
Image image = this.docker.image().pull(runImage, listener);
|
||||
this.log.pulledRunImage(request, image);
|
||||
return image;
|
||||
}
|
||||
|
||||
private void assertHasExpectedStackId(Image image, StackId stackId) {
|
||||
StackId pulledStackId = StackId.fromImage(image);
|
||||
Assert.state(pulledStackId.equals(stackId),
|
||||
"Run image stack '" + pulledStackId + "' does not match builder stack '" + stackId + "'");
|
||||
private void assertStackIdsMatch(Image runImage, Image builderImage) {
|
||||
StackId runImageStackId = StackId.fromImage(runImage);
|
||||
StackId builderImageStackId = StackId.fromImage(builderImage);
|
||||
Assert.state(runImageStackId.equals(builderImageStackId),
|
||||
"Run image stack '" + runImageStackId + "' does not match builder stack '" + builderImageStackId + "'");
|
||||
}
|
||||
|
||||
private void executeLifecycle(BuildRequest request, ImageReference runImageReference, EphemeralBuilder builder)
|
||||
throws IOException {
|
||||
try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, request, runImageReference, builder)) {
|
||||
private void executeLifecycle(BuildRequest request, EphemeralBuilder builder) throws IOException {
|
||||
try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, request, builder)) {
|
||||
lifecycle.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,6 @@ class Lifecycle implements Closeable {
|
||||
|
||||
private final BuildRequest request;
|
||||
|
||||
private final ImageReference runImageReference;
|
||||
|
||||
private final EphemeralBuilder builder;
|
||||
|
||||
private final LifecycleVersion lifecycleVersion;
|
||||
@@ -73,15 +71,12 @@ class Lifecycle implements Closeable {
|
||||
* @param log build output log
|
||||
* @param docker the Docker API
|
||||
* @param request the request to process
|
||||
* @param runImageReference a reference to run image that should be used
|
||||
* @param builder the ephemeral builder used to run the phases
|
||||
*/
|
||||
Lifecycle(BuildLog log, DockerApi docker, BuildRequest request, ImageReference runImageReference,
|
||||
EphemeralBuilder builder) {
|
||||
Lifecycle(BuildLog log, DockerApi docker, BuildRequest request, EphemeralBuilder builder) {
|
||||
this.log = log;
|
||||
this.docker = docker;
|
||||
this.request = request;
|
||||
this.runImageReference = runImageReference;
|
||||
this.builder = builder;
|
||||
this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion());
|
||||
this.platformVersion = ApiVersion.parse(builder.getBuilderMetadata().getLifecycle().getApi().getPlatform());
|
||||
@@ -125,7 +120,7 @@ class Lifecycle implements Closeable {
|
||||
phase.withLogLevelArg();
|
||||
phase.withArgs("-app", Directory.APPLICATION);
|
||||
phase.withArgs("-platform", Directory.PLATFORM);
|
||||
phase.withArgs("-run-image", this.runImageReference);
|
||||
phase.withArgs("-run-image", this.request.getRunImage());
|
||||
phase.withArgs("-layers", Directory.LAYERS);
|
||||
phase.withArgs("-cache-dir", Directory.CACHE);
|
||||
phase.withArgs("-launch-cache", Directory.LAUNCH_CACHE);
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* A reference to a Docker image of the form {@code "imagename[:tag|@digest]"}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @since 2.3.0
|
||||
* @see ImageName
|
||||
* @see <a href=
|
||||
@@ -152,7 +153,19 @@ public final class ImageReference {
|
||||
*/
|
||||
public ImageReference inTaggedForm() {
|
||||
Assert.state(this.digest == null, () -> "Image reference '" + this + "' cannot contain a digest");
|
||||
return new ImageReference(this.name, (this.tag != null) ? this.tag : LATEST, this.digest);
|
||||
return new ImageReference(this.name, (this.tag != null) ? this.tag : LATEST, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an {@link ImageReference} containing either a tag or a digest. If neither
|
||||
* the digest or the tag has been defined then tag {@code latest} is used.
|
||||
* @return the image reference in tagged or digest form
|
||||
*/
|
||||
public ImageReference inTaggedOrDigestForm() {
|
||||
if (this.digest != null) {
|
||||
return this;
|
||||
}
|
||||
return inTaggedForm();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,7 +82,6 @@ public class BuildRequestTests {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> BuildRequest.forJarFile(new File(this.tempDir, "missing.jar")))
|
||||
.withMessage("JarFile must exist");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,6 +105,21 @@ public class BuildRequestTests {
|
||||
"docker.io/spring/builder:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRunImageUpdatesRunImage() throws IOException {
|
||||
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"))
|
||||
.withRunImage(ImageReference.of("example.com/custom/run-image:latest"));
|
||||
assertThat(request.getRunImage().toString()).isEqualTo("example.com/custom/run-image:latest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRunImageWhenHasDigestUpdatesRunImage() throws IOException {
|
||||
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withRunImage(ImageReference
|
||||
.of("example.com/custom/run-image:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"));
|
||||
assertThat(request.getRunImage().toString()).isEqualTo(
|
||||
"example.com/custom/run-image:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withCreatorUpdatesCreator() throws IOException {
|
||||
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
|
||||
|
||||
@@ -106,6 +106,47 @@ class BuilderTests {
|
||||
verify(docker.image()).remove(archive.getValue().getTag(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildInvokesBuilderWithRunImageInDigestForm() throws Exception {
|
||||
TestPrintStream out = new TestPrintStream();
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image-with-run-image-digest.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of(
|
||||
"docker.io/cloudfoundry/run:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")),
|
||||
any())).willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker);
|
||||
BuildRequest request = getTestRequest();
|
||||
builder.build(request);
|
||||
assertThat(out.toString()).contains("Running creator");
|
||||
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
verify(docker.image()).load(archive.capture(), any());
|
||||
verify(docker.image()).remove(archive.getValue().getTag(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildInvokesBuilderWithRunImageFromRequest() throws Exception {
|
||||
TestPrintStream out = new TestPrintStream();
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("example.com/custom/run:latest")), any()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker);
|
||||
BuildRequest request = getTestRequest().withRunImage(ImageReference.of("example.com/custom/run:latest"));
|
||||
builder.build(request);
|
||||
assertThat(out.toString()).contains("Running creator");
|
||||
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
verify(docker.image()).load(archive.capture(), any());
|
||||
verify(docker.image()).remove(archive.getValue().getTag(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenStackIdDoesNotMatchThrowsException() throws Exception {
|
||||
TestPrintStream out = new TestPrintStream();
|
||||
@@ -175,8 +216,7 @@ class BuilderTests {
|
||||
private BuildRequest getTestRequest() {
|
||||
TarArchive content = mock(TarArchive.class);
|
||||
ImageReference name = ImageReference.of("my-application");
|
||||
BuildRequest request = BuildRequest.of(name, (owner) -> content);
|
||||
return request;
|
||||
return BuildRequest.of(name, (owner) -> content);
|
||||
}
|
||||
|
||||
private Image loadImage(String name) throws IOException {
|
||||
|
||||
@@ -150,7 +150,7 @@ class LifecycleTests {
|
||||
private BuildRequest getTestRequest() {
|
||||
TarArchive content = mock(TarArchive.class);
|
||||
ImageReference name = ImageReference.of("my-application");
|
||||
return BuildRequest.of(name, (owner) -> content);
|
||||
return BuildRequest.of(name, (owner) -> content).withRunImage(ImageReference.of("cloudfoundry/run"));
|
||||
}
|
||||
|
||||
private Lifecycle createLifecycle() throws IOException {
|
||||
@@ -159,8 +159,7 @@ class LifecycleTests {
|
||||
|
||||
private Lifecycle createLifecycle(BuildRequest request) throws IOException {
|
||||
EphemeralBuilder builder = mockEphemeralBuilder();
|
||||
return new TestLifecycle(BuildLog.to(this.out), this.docker, request, ImageReference.of("cloudfoundry/run"),
|
||||
builder);
|
||||
return new TestLifecycle(BuildLog.to(this.out), this.docker, request, builder);
|
||||
}
|
||||
|
||||
private EphemeralBuilder mockEphemeralBuilder() throws IOException {
|
||||
@@ -208,9 +207,8 @@ class LifecycleTests {
|
||||
|
||||
static class TestLifecycle extends Lifecycle {
|
||||
|
||||
TestLifecycle(BuildLog log, DockerApi docker, BuildRequest request, ImageReference runImageReference,
|
||||
EphemeralBuilder builder) {
|
||||
super(log, docker, request, runImageReference, builder);
|
||||
TestLifecycle(BuildLog log, DockerApi docker, BuildRequest request, EphemeralBuilder builder) {
|
||||
super(log, docker, request, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
* Tests for {@link ImageReference}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class ImageReferenceTests {
|
||||
|
||||
@@ -223,6 +224,26 @@ class ImageReferenceTests {
|
||||
assertThat(reference.inTaggedForm().toString()).isEqualTo("docker.io/library/ubuntu:bionic");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inTaggedOrDigestFormWhenHasDigestUsesDigest() {
|
||||
ImageReference reference = ImageReference
|
||||
.of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
|
||||
assertThat(reference.inTaggedOrDigestForm().toString()).isEqualTo(
|
||||
"docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inTaggedOrDigestFormWhenHasTagUsesTag() {
|
||||
ImageReference reference = ImageReference.of("ubuntu:bionic");
|
||||
assertThat(reference.inTaggedOrDigestForm().toString()).isEqualTo("docker.io/library/ubuntu:bionic");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inTaggedOrDigestFormWhenHasNoTagOrDigestUsesLatest() {
|
||||
ImageReference reference = ImageReference.of("ubuntu");
|
||||
assertThat(reference.inTaggedOrDigestForm().toString()).isEqualTo("docker.io/library/ubuntu:latest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsAndHashCode() {
|
||||
ImageReference r1 = ImageReference.of("ubuntu:bionic");
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-skip-restore", "docker.io/library/my-application:latest" ],
|
||||
"Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-skip-restore", "docker.io/library/my-application:latest" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ],
|
||||
"Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-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" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
|
||||
@@ -50,6 +50,11 @@ The following table summarizes the available properties and their default values
|
||||
| Name of the Builder image to use.
|
||||
| `gcr.io/paketo-buildpacks/builder:base-platform-api-0.3`
|
||||
|
||||
| `runImage`
|
||||
| `--runImage`
|
||||
| Name of the run image to use.
|
||||
| No default value, indicating the run image specified in Builder metadata should be used.
|
||||
|
||||
| `imageName`
|
||||
| `--imageName`
|
||||
| {spring-boot-api}/buildpack/platform/docker/type/ImageReference.html#of-java.lang.String-[Image name] for the generated image.
|
||||
@@ -79,8 +84,8 @@ The following table summarizes the available properties and their default values
|
||||
|
||||
|
||||
[[build-image-example-custom-image-builder]]
|
||||
==== Custom Image Builder
|
||||
If you need to customize the builder used to create the image, configure the task as shown in the following example:
|
||||
==== Custom Image Builder and Run Image
|
||||
If you need to customize the builder used to create the image or the run image used to launch the built image, configure the task as shown in the following example:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Groovy
|
||||
@@ -94,13 +99,13 @@ include::../gradle/packaging/boot-build-image-builder.gradle[tags=builder]
|
||||
include::../gradle/packaging/boot-build-image-builder.gradle.kts[tags=builder]
|
||||
----
|
||||
|
||||
This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`.
|
||||
This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`, and the run image named `mine/java-cnb-run` and the tag `latest`.
|
||||
|
||||
The builder can be specified on the command line as well, as shown in this example:
|
||||
The builder and run image can be specified on the command line as well, as shown in this example:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ gradle bootBuildImage --builder=mine/java-cnb-builder
|
||||
$ gradle bootBuildImage --builder=mine/java-cnb-builder --runImage=mine/java-cnb-run
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -10,5 +10,6 @@ bootJar {
|
||||
// tag::builder[]
|
||||
bootBuildImage {
|
||||
builder = "mine/java-cnb-builder"
|
||||
runImage = "mine/java-cnb-run"
|
||||
}
|
||||
// end::builder[]
|
||||
|
||||
@@ -12,5 +12,6 @@ tasks.getByName<BootJar>("bootJar") {
|
||||
// tag::builder[]
|
||||
tasks.getByName<BootBuildImage>("bootBuildImage") {
|
||||
builder = "mine/java-cnb-builder"
|
||||
runImage = "mine/java-cnb-run"
|
||||
}
|
||||
// end::builder[]
|
||||
|
||||
@@ -61,6 +61,8 @@ public class BootBuildImage extends DefaultTask {
|
||||
|
||||
private String builder;
|
||||
|
||||
private String runImage;
|
||||
|
||||
private Map<String, String> environment = new HashMap<>();
|
||||
|
||||
private boolean cleanCache;
|
||||
@@ -133,6 +135,26 @@ public class BootBuildImage extends DefaultTask {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the run image that will be included in the built image. When {@code null},
|
||||
* the run image bundled with the builder will be used.
|
||||
* @return the run image
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public String getRunImage() {
|
||||
return this.runImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the run image that will be included in the built image.
|
||||
* @param runImage the run image
|
||||
*/
|
||||
@Option(option = "runImage", description = "The name of the run image to use")
|
||||
public void setRunImage(String runImage) {
|
||||
this.runImage = runImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the environment that will be used when building the image.
|
||||
* @return the environment
|
||||
@@ -228,6 +250,7 @@ public class BootBuildImage extends DefaultTask {
|
||||
|
||||
private BuildRequest customize(BuildRequest request) {
|
||||
request = customizeBuilder(request);
|
||||
request = customizeRunImage(request);
|
||||
request = customizeEnvironment(request);
|
||||
request = customizeCreator(request);
|
||||
request = request.withCleanCache(this.cleanCache);
|
||||
@@ -242,6 +265,13 @@ public class BootBuildImage extends DefaultTask {
|
||||
return request;
|
||||
}
|
||||
|
||||
private BuildRequest customizeRunImage(BuildRequest request) {
|
||||
if (StringUtils.hasText(this.runImage)) {
|
||||
return request.withRunImage(ImageReference.of(this.runImage));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private BuildRequest customizeEnvironment(BuildRequest request) {
|
||||
if (this.environment != null && !this.environment.isEmpty()) {
|
||||
request = request.withEnv(this.environment);
|
||||
|
||||
@@ -89,13 +89,14 @@ class BootBuildImageIntegrationTests {
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void buildsImageWithCustomBuilder() throws IOException {
|
||||
void buildsImageWithCustomBuilderAndRunImage() throws IOException {
|
||||
writeMainClass();
|
||||
writeLongNameResource();
|
||||
BuildResult result = this.gradleBuild.build("bootBuildImage");
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(result.getOutput()).contains("example/test-image-custom");
|
||||
assertThat(result.getOutput()).contains("paketo-buildpacks/builder:full-cf-platform-api-0.3");
|
||||
assertThat(result.getOutput()).contains("paketo-buildpacks/run:full-cnb-cf");
|
||||
ImageReference imageReference = ImageReference.of(ImageName.of("example/test-image-custom"));
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
|
||||
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
|
||||
@@ -110,10 +111,12 @@ class BootBuildImageIntegrationTests {
|
||||
writeMainClass();
|
||||
writeLongNameResource();
|
||||
BuildResult result = this.gradleBuild.build("bootBuildImage", "--imageName=example/test-image-cmd",
|
||||
"--builder=gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3");
|
||||
"--builder=gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3",
|
||||
"--runImage=gcr.io/paketo-buildpacks/run:full-cnb-cf");
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(result.getOutput()).contains("example/test-image-cmd");
|
||||
assertThat(result.getOutput()).contains("paketo-buildpacks/builder:full-cf-platform-api-0.3");
|
||||
assertThat(result.getOutput()).contains("paketo-buildpacks/run:full-cnb-cf");
|
||||
ImageReference imageReference = ImageReference.of(ImageName.of("example/test-image-cmd"));
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
|
||||
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
|
||||
|
||||
@@ -183,4 +183,15 @@ class BootBuildImageTests {
|
||||
assertThat(this.buildImage.createRequest().getBuilder().getName()).isEqualTo("test/builder");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoRunImageIsConfiguredThenRequestUsesDefaultRunImage() {
|
||||
assertThat(this.buildImage.createRequest().getRunImage()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRunImageIsConfiguredThenRequestUsesSpecifiedRunImage() {
|
||||
this.buildImage.setRunImage("example.com/test/run:1.0");
|
||||
assertThat(this.buildImage.createRequest().getRunImage().getName()).isEqualTo("test/run");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
|
||||
bootBuildImage {
|
||||
imageName = "example/test-image-custom"
|
||||
builder = "gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
|
||||
bootBuildImage {
|
||||
imageName = "example/test-image-custom"
|
||||
builder = "gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3"
|
||||
runImage = "gcr.io/paketo-buildpacks/run:full-cnb-cf"
|
||||
}
|
||||
@@ -6,6 +6,6 @@ plugins {
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
|
||||
bootBuildImage {
|
||||
imageName = "example/test-image-name"
|
||||
}
|
||||
bootBuildImage {
|
||||
imageName = "example/test-image-name"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ plugins {
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
|
||||
bootBuildImage {
|
||||
environment = ["BP_JVM_VERSION" : "13.9.9"]
|
||||
}
|
||||
bootBuildImage {
|
||||
environment = ["BP_JVM_VERSION": "13.9.9"]
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@ The following table shows the environment variables and their values:
|
||||
|===
|
||||
| Environment variable | Description
|
||||
|
||||
| DOCKER_HOST
|
||||
| DOCKER_HOST
|
||||
| URL containing the host and port for the Docker daemon - e.g. `tcp://192.168.99.100:2376`
|
||||
|
||||
| DOCKER_TLS_VERIFY
|
||||
| DOCKER_TLS_VERIFY
|
||||
| Enable secure HTTPS protocol when set to `1` (optional)
|
||||
|
||||
| DOCKER_CERT_PATH
|
||||
| DOCKER_CERT_PATH
|
||||
| Path to certificate and key files for HTTPS (required if `DOCKER_TLS_VERIFY=1`, ignored otherwise)
|
||||
|===
|
||||
|
||||
@@ -64,7 +64,7 @@ The builder includes multiple {buildpacks-reference}/concepts/components/buildpa
|
||||
By default, the plugin chooses a builder image.
|
||||
The name of the generated image is deduced from project properties.
|
||||
|
||||
The `image` parameter allows to configure how the builder should operate on the project.
|
||||
The `image` parameter allows configuration of the builder and how it should operate on the project.
|
||||
The following table summarizes the available parameters and their default values:
|
||||
|
||||
|===
|
||||
@@ -75,6 +75,11 @@ The following table summarizes the available parameters and their default values
|
||||
| `spring-boot.build-image.builder`
|
||||
| `gcr.io/paketo-buildpacks/builder:base-platform-api-0.3`
|
||||
|
||||
| `runImage`
|
||||
| Name of the run image to use.
|
||||
| `spring-boot.build-image.runImage`
|
||||
| No default value, indicating the run image specified in Builder metadata should be used.
|
||||
|
||||
| `name`
|
||||
| {spring-boot-api}/buildpack/platform/docker/type/ImageReference.html#of-java.lang.String-[Image name] for the generated image.
|
||||
| `spring-boot.build-image.imageName`
|
||||
@@ -109,7 +114,7 @@ include::goals/build-image.adoc[leveloffset=+1]
|
||||
|
||||
[[build-image-example-custom-image-builder]]
|
||||
==== Custom Image Builder
|
||||
If you need to customize the builder used to create the image, configure the plugin as shown in the following example:
|
||||
If you need to customize the builder used to create the image or the run image used to launch the built image, configure the plugin as shown in the following example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,attributes"]
|
||||
----
|
||||
@@ -123,6 +128,7 @@ If you need to customize the builder used to create the image, configure the plu
|
||||
<configuration>
|
||||
<image>
|
||||
<builder>mine/java-cnb-builder</builder>
|
||||
<runImage>mine/java-cnb-run</runImage>
|
||||
</image>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -131,13 +137,13 @@ If you need to customize the builder used to create the image, configure the plu
|
||||
</project>
|
||||
----
|
||||
|
||||
This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`.
|
||||
This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`, and the run image named `mine/java-cnb-run` and the tag `latest`.
|
||||
|
||||
The builder can be specified on the command line as well, as shown in this example:
|
||||
The builder and run image can be specified on the command line as well, as shown in this example:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ mvn spring-boot:build-image -Dspring-boot.build-image.builder=mine/java-cnb-builder
|
||||
$ mvn spring-boot:build-image -Dspring-boot.build-image.builder=mine/java-cnb-builder -Dspring-boot.build-image.runImage=mine/java-cnb-run
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -95,11 +95,12 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
|
||||
.systemProperty("spring-boot.build-image.imageName", "example.com/test/cmd-property-name:v1")
|
||||
.systemProperty("spring-boot.build-image.builder",
|
||||
"gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3")
|
||||
.systemProperty("spring-boot.build-image.runImage", "gcr.io/paketo-buildpacks/run:full-cnb-cf")
|
||||
.execute((project) -> {
|
||||
assertThat(buildLog(project)).contains("Building image")
|
||||
.contains("example.com/test/cmd-property-name:v1")
|
||||
.contains("paketo-buildpacks/builder:full-cf-platform-api-0.3")
|
||||
.contains("Successfully built image");
|
||||
.contains("paketo-buildpacks/run:full-cnb-cf").contains("Successfully built image");
|
||||
ImageReference imageReference = ImageReference.of("example.com/test/cmd-property-name:v1");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
|
||||
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
|
||||
@@ -111,10 +112,11 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void whenBuildImageIsInvokedWithCustomBuilderImage(MavenBuild mavenBuild) {
|
||||
void whenBuildImageIsInvokedWithCustomBuilderImageAndRunImage(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("build-image-custom-builder").goals("package").execute((project) -> {
|
||||
assertThat(buildLog(project)).contains("Building image")
|
||||
.contains("paketo-buildpacks/builder:full-cf-platform-api-0.3")
|
||||
.contains("paketo-buildpacks/run:full-cnb-cf")
|
||||
.contains("docker.io/library/build-image-v2-builder:0.0.1.BUILD-SNAPSHOT")
|
||||
.contains("Successfully built image");
|
||||
ImageReference imageReference = ImageReference
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<configuration>
|
||||
<image>
|
||||
<builder>gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3</builder>
|
||||
<runImage>gcr.io/paketo-buildpacks/run:full-cnb-cf</runImage>
|
||||
</image>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
@@ -94,7 +94,7 @@ public class BuildImageMojo extends AbstractPackagerMojo {
|
||||
private String classifier;
|
||||
|
||||
/**
|
||||
* Image configuration, with `builder`, `name`, `env`, `cleanCache` and
|
||||
* Image configuration, with `builder`, `runImage`, `name`, `env`, `cleanCache` and
|
||||
* `verboseLogging` options.
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@@ -115,6 +115,14 @@ public class BuildImageMojo extends AbstractPackagerMojo {
|
||||
@Parameter(property = "spring-boot.build-image.builder", readonly = true)
|
||||
String imageBuilder;
|
||||
|
||||
/**
|
||||
* Alias for {@link Image#runImage} to support configuration via command-line
|
||||
* property.
|
||||
* @since 2.3.1
|
||||
*/
|
||||
@Parameter(property = "spring-boot.build-image.runImage", readonly = true)
|
||||
String runImage;
|
||||
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException {
|
||||
if (this.project.getPackaging().equals("pom")) {
|
||||
@@ -149,6 +157,9 @@ public class BuildImageMojo extends AbstractPackagerMojo {
|
||||
if (image.builder == null && this.imageBuilder != null) {
|
||||
image.setBuilder(this.imageBuilder);
|
||||
}
|
||||
if (image.runImage == null && this.runImage != null) {
|
||||
image.setRunImage(this.runImage);
|
||||
}
|
||||
return customize(image.getBuildRequest(this.project.getArtifact(), content));
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ public class Image {
|
||||
*/
|
||||
String builder;
|
||||
|
||||
/**
|
||||
* The run image used to launch the built image.
|
||||
*/
|
||||
String runImage;
|
||||
|
||||
/**
|
||||
* Environment properties that should be passed to the builder.
|
||||
*/
|
||||
@@ -70,6 +75,10 @@ public class Image {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
void setRunImage(String runImage) {
|
||||
this.runImage = runImage;
|
||||
}
|
||||
|
||||
BuildRequest getBuildRequest(Artifact artifact, Function<Owner, TarArchive> applicationContent) {
|
||||
return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent));
|
||||
}
|
||||
@@ -86,6 +95,9 @@ public class Image {
|
||||
if (StringUtils.hasText(this.builder)) {
|
||||
request = request.withBuilder(ImageReference.of(this.builder));
|
||||
}
|
||||
if (StringUtils.hasText(this.runImage)) {
|
||||
request = request.withRunImage(ImageReference.of(this.runImage));
|
||||
}
|
||||
if (this.env != null && !this.env.isEmpty()) {
|
||||
request = request.withEnv(this.env);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.entry;
|
||||
* Tests for {@link Image}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class ImageTests {
|
||||
|
||||
@@ -58,6 +59,7 @@ class ImageTests {
|
||||
BuildRequest request = new Image().getBuildRequest(createArtifact(), mockApplicationContent());
|
||||
assertThat(request.getName().toString()).isEqualTo("docker.io/library/my-app:0.0.1-SNAPSHOT");
|
||||
assertThat(request.getBuilder().toString()).contains("paketo-buildpacks/builder");
|
||||
assertThat(request.getRunImage()).isNull();
|
||||
assertThat(request.getEnv()).isEmpty();
|
||||
assertThat(request.isCleanCache()).isFalse();
|
||||
assertThat(request.isVerboseLogging()).isFalse();
|
||||
@@ -71,6 +73,14 @@ class ImageTests {
|
||||
assertThat(request.getBuilder().toString()).isEqualTo("docker.io/springboot/builder:2.2.x");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBuildRequestWhenHasRunImageUsesRunImage() {
|
||||
Image image = new Image();
|
||||
image.runImage = "springboot/run:latest";
|
||||
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
|
||||
assertThat(request.getRunImage().toString()).isEqualTo("docker.io/springboot/run:latest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBuildRequestWhenHasEnvUsesEnv() {
|
||||
Image image = new Image();
|
||||
|
||||
Reference in New Issue
Block a user